Merge branch 'mkirk/pick-color'

pull/1/head
Michael Kirk 7 years ago
commit ea7ec99489

@ -411,6 +411,7 @@
45FBC5C81DF8575700E9B410 /* CallKitCallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FBC59A1DF8575700E9B410 /* CallKitCallManager.swift */; }; 45FBC5C81DF8575700E9B410 /* CallKitCallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FBC59A1DF8575700E9B410 /* CallKitCallManager.swift */; };
45FBC5D11DF8592E00E9B410 /* SignalCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FBC5D01DF8592E00E9B410 /* SignalCall.swift */; }; 45FBC5D11DF8592E00E9B410 /* SignalCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FBC5D01DF8592E00E9B410 /* SignalCall.swift */; };
4AC4EA13C8A444455DAB351F /* Pods_SignalMessaging.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 264242150E87D10A357DB07B /* Pods_SignalMessaging.framework */; }; 4AC4EA13C8A444455DAB351F /* Pods_SignalMessaging.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 264242150E87D10A357DB07B /* Pods_SignalMessaging.framework */; };
4C13C9F620E57BA30089A98B /* ColorPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C13C9F520E57BA30089A98B /* ColorPickerViewController.swift */; };
4C20B2B720CA0034001BAC90 /* ThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542DF51208B82E9007B4E76 /* ThreadViewModel.swift */; }; 4C20B2B720CA0034001BAC90 /* ThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542DF51208B82E9007B4E76 /* ThreadViewModel.swift */; };
4C20B2B920CA10DE001BAC90 /* ConversationSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */; }; 4C20B2B920CA10DE001BAC90 /* ConversationSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */; };
70377AAB1918450100CAF501 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70377AAA1918450100CAF501 /* MobileCoreServices.framework */; }; 70377AAB1918450100CAF501 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70377AAA1918450100CAF501 /* MobileCoreServices.framework */; };
@ -1066,6 +1067,7 @@
45FBC59A1DF8575700E9B410 /* CallKitCallManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallKitCallManager.swift; sourceTree = "<group>"; }; 45FBC59A1DF8575700E9B410 /* CallKitCallManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallKitCallManager.swift; sourceTree = "<group>"; };
45FBC5D01DF8592E00E9B410 /* SignalCall.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalCall.swift; sourceTree = "<group>"; }; 45FBC5D01DF8592E00E9B410 /* SignalCall.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalCall.swift; sourceTree = "<group>"; };
45FDA43420A4D22700396358 /* OWSNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSNavigationBar.swift; sourceTree = "<group>"; }; 45FDA43420A4D22700396358 /* OWSNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSNavigationBar.swift; sourceTree = "<group>"; };
4C13C9F520E57BA30089A98B /* ColorPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerViewController.swift; sourceTree = "<group>"; };
4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSearchViewController.swift; sourceTree = "<group>"; }; 4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSearchViewController.swift; sourceTree = "<group>"; };
69349DE607F5BA6036C9AC60 /* Pods-SignalShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalShareExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SignalShareExtension/Pods-SignalShareExtension.debug.xcconfig"; sourceTree = "<group>"; }; 69349DE607F5BA6036C9AC60 /* Pods-SignalShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalShareExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SignalShareExtension/Pods-SignalShareExtension.debug.xcconfig"; sourceTree = "<group>"; };
70377AAA1918450100CAF501 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; 70377AAA1918450100CAF501 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; };
@ -1690,6 +1692,7 @@
340FC897204DAC8D007AEB0F /* ThreadSettings */, 340FC897204DAC8D007AEB0F /* ThreadSettings */,
34D1F0BE1F8EC1760066283D /* Utils */, 34D1F0BE1F8EC1760066283D /* Utils */,
452B998F20A34B6B006F2F9E /* AddContactShareToExistingContactViewController.swift */, 452B998F20A34B6B006F2F9E /* AddContactShareToExistingContactViewController.swift */,
4C13C9F520E57BA30089A98B /* ColorPickerViewController.swift */,
); );
path = ViewControllers; path = ViewControllers;
sourceTree = "<group>"; sourceTree = "<group>";
@ -3272,6 +3275,7 @@
348BB25D20A0C5530047AEC2 /* ContactShareViewHelper.swift in Sources */, 348BB25D20A0C5530047AEC2 /* ContactShareViewHelper.swift in Sources */,
34B3F8801E8DF1700035BE1A /* InviteFlow.swift in Sources */, 34B3F8801E8DF1700035BE1A /* InviteFlow.swift in Sources */,
457C87B82032645C008D52D6 /* DebugUINotifications.swift in Sources */, 457C87B82032645C008D52D6 /* DebugUINotifications.swift in Sources */,
4C13C9F620E57BA30089A98B /* ColorPickerViewController.swift in Sources */,
340FC8D0205BF2FA007AEB0F /* OWSBackupIO.m in Sources */, 340FC8D0205BF2FA007AEB0F /* OWSBackupIO.m in Sources */,
458E38371D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m in Sources */, 458E38371D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m in Sources */,
4517642B1DE939FD00EDB8B9 /* ContactCell.swift in Sources */, 4517642B1DE939FD00EDB8B9 /* ContactCell.swift in Sources */,

@ -0,0 +1,145 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
let colorSwatchHeight: CGFloat = 60
class ColorView: UIView {
let color: UIColor
let swatchView: UIView
required init(color: UIColor) {
self.color = color
self.swatchView = UIView()
super.init(frame: .zero)
swatchView.backgroundColor = color
self.swatchView.layer.cornerRadius = colorSwatchHeight / 2
self.addSubview(swatchView)
swatchView.autoVCenterInSuperview()
swatchView.autoSetDimension(.height, toSize: colorSwatchHeight)
swatchView.autoPinEdge(toSuperviewMargin: .top, relation: .greaterThanOrEqual)
swatchView.autoPinEdge(toSuperviewMargin: .bottom, relation: .greaterThanOrEqual)
swatchView.autoPinLeadingToSuperviewMargin()
swatchView.autoPinTrailingToSuperviewMargin()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
@objc
protocol ColorPickerDelegate: class {
func colorPickerDidCancel(_ colorPicker: ColorPickerViewController)
func colorPicker(_ colorPicker: ColorPickerViewController, didPickColorName colorName: String)
}
@objc
class ColorPickerViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
private let pickerView: UIPickerView
private let thread: TSThread
private let colors: [UIColor]
@objc public weak var delegate: ColorPickerDelegate?
@objc
required init(thread: TSThread) {
self.thread = thread
self.pickerView = UIPickerView()
self.colors = UIColor.ows_conversationColors
super.init(nibName: nil, bundle: nil)
self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(didTapCancel))
self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(didTapSave))
pickerView.dataSource = self
pickerView.delegate = self
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
self.view = UIView()
view.backgroundColor = .white
view.addSubview(pickerView)
pickerView.autoVCenterInSuperview()
pickerView.autoPinLeadingToSuperviewMargin()
pickerView.autoPinTrailingToSuperviewMargin()
}
override func viewDidLoad() {
super.viewDidLoad()
if let colorName = thread.conversationColorName,
let currentColor = UIColor.ows_conversationColor(colorName: colorName),
let index = colors.index(of: currentColor) {
pickerView.selectRow(index, inComponent: 0, animated: false)
}
}
// MARK: UIPickerViewDataSource
public func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return self.colors.count
}
// MARK: UIPickerViewDelegate
public func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
let vMargin: CGFloat = 2
return colorSwatchHeight + vMargin * 2
}
public func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
guard let color = colors[safe: row] else {
owsFail("\(logTag) in \(#function) color was unexpectedly nil")
return ColorView(color: .white)
}
return ColorView(color: color)
}
// MARK: Actions
var currentColor: UIColor {
let index = pickerView.selectedRow(inComponent: 0)
guard let color = self.colors[safe: index] else {
owsFail("\(self.logTag) in \(#function) index was unexpectedly nil")
return UIColor.white
}
return color
}
@objc
public func didTapSave() {
guard let colorName = UIColor.ows_conversationColorName(color: self.currentColor) else {
owsFail("\(self.logTag) in \(#function) colorName was unexpectedly nil")
self.delegate?.colorPickerDidCancel(self)
return
}
self.delegate?.colorPicker(self, didPickColorName: colorName)
}
@objc
public func didTapCancel() {
self.delegate?.colorPickerDidCancel(self)
}
}

@ -277,6 +277,7 @@ NS_ASSUME_NONNULL_BEGIN
OWSQuotedMessageView *quotedMessageView = OWSQuotedMessageView *quotedMessageView =
[OWSQuotedMessageView quotedMessageViewForConversation:self.viewItem.quotedReply [OWSQuotedMessageView quotedMessageViewForConversation:self.viewItem.quotedReply
displayableQuotedText:displayableQuotedText displayableQuotedText:displayableQuotedText
conversationStyle:self.conversationStyle
isOutgoing:isOutgoing]; isOutgoing:isOutgoing];
quotedMessageView.delegate = self; quotedMessageView.delegate = self;
@ -494,7 +495,7 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssert([self.viewItem.interaction isKindOfClass:[TSMessage class]]); OWSAssert([self.viewItem.interaction isKindOfClass:[TSMessage class]]);
TSMessage *message = (TSMessage *)self.viewItem.interaction; TSMessage *message = (TSMessage *)self.viewItem.interaction;
return [ConversationStyle bubbleColorWithMessage:message]; return [self.conversationStyle bubbleColorWithMessage:message];
} }
- (BOOL)hasBodyMediaWithThumbnail - (BOOL)hasBodyMediaWithThumbnail
@ -1084,6 +1085,7 @@ NS_ASSUME_NONNULL_BEGIN
OWSQuotedMessageView *quotedMessageView = OWSQuotedMessageView *quotedMessageView =
[OWSQuotedMessageView quotedMessageViewForConversation:self.viewItem.quotedReply [OWSQuotedMessageView quotedMessageViewForConversation:self.viewItem.quotedReply
displayableQuotedText:displayableQuotedText displayableQuotedText:displayableQuotedText
conversationStyle:self.conversationStyle
isOutgoing:isOutgoing]; isOutgoing:isOutgoing];
CGSize result = [quotedMessageView sizeForMaxWidth:self.conversationStyle.maxMessageWidth]; CGSize result = [quotedMessageView sizeForMaxWidth:self.conversationStyle.maxMessageWidth];
return [NSValue valueWithCGSize:CGSizeCeil(result)]; return [NSValue valueWithCGSize:CGSizeCeil(result)];
@ -1214,7 +1216,7 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssert([self.viewItem.interaction isKindOfClass:[TSMessage class]]); OWSAssert([self.viewItem.interaction isKindOfClass:[TSMessage class]]);
TSMessage *message = (TSMessage *)self.viewItem.interaction; TSMessage *message = (TSMessage *)self.viewItem.interaction;
return [ConversationStyle bubbleTextColorWithMessage:message]; return [self.conversationStyle bubbleTextColorWithMessage:message];
} }
- (BOOL)isMediaBeingSent - (BOOL)isMediaBeingSent

@ -296,6 +296,7 @@ NS_ASSUME_NONNULL_BEGIN
TSIncomingMessage *incomingMessage = (TSIncomingMessage *)self.viewItem.interaction; TSIncomingMessage *incomingMessage = (TSIncomingMessage *)self.viewItem.interaction;
OWSAvatarBuilder *avatarBuilder = [[OWSContactAvatarBuilder alloc] initWithSignalId:incomingMessage.authorId OWSAvatarBuilder *avatarBuilder = [[OWSContactAvatarBuilder alloc] initWithSignalId:incomingMessage.authorId
color:self.conversationStyle.primaryColor
diameter:self.avatarSize diameter:self.avatarSize
contactsManager:contactsManager]; contactsManager:contactsManager];
self.avatarView.image = [avatarBuilder build]; self.avatarView.image = [avatarBuilder build];

@ -4,6 +4,7 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@class ConversationStyle;
@class DisplayableText; @class DisplayableText;
@class OWSBubbleShapeView; @class OWSBubbleShapeView;
@class OWSQuotedReplyModel; @class OWSQuotedReplyModel;
@ -33,10 +34,12 @@ NS_ASSUME_NONNULL_BEGIN
// Factory method for "message bubble" views. // Factory method for "message bubble" views.
+ (OWSQuotedMessageView *)quotedMessageViewForConversation:(OWSQuotedReplyModel *)quotedMessage + (OWSQuotedMessageView *)quotedMessageViewForConversation:(OWSQuotedReplyModel *)quotedMessage
displayableQuotedText:(nullable DisplayableText *)displayableQuotedText displayableQuotedText:(nullable DisplayableText *)displayableQuotedText
conversationStyle:(ConversationStyle *)conversationStyle
isOutgoing:(BOOL)isOutgoing; isOutgoing:(BOOL)isOutgoing;
// Factory method for "message compose" views. // Factory method for "message compose" views.
+ (OWSQuotedMessageView *)quotedMessageViewForPreview:(OWSQuotedReplyModel *)quotedMessage; + (OWSQuotedMessageView *)quotedMessageViewForPreview:(OWSQuotedReplyModel *)quotedMessage
conversationStyle:(ConversationStyle *)conversationStyle;
@end @end

@ -21,6 +21,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly) OWSQuotedReplyModel *quotedMessage; @property (nonatomic, readonly) OWSQuotedReplyModel *quotedMessage;
@property (nonatomic, nullable, readonly) DisplayableText *displayableQuotedText; @property (nonatomic, nullable, readonly) DisplayableText *displayableQuotedText;
@property (nonatomic, readonly) ConversationStyle *conversationStyle;
@property (nonatomic, nullable) OWSBubbleShapeView *boundsStrokeView; @property (nonatomic, nullable) OWSBubbleShapeView *boundsStrokeView;
@property (nonatomic, readonly) BOOL isForPreview; @property (nonatomic, readonly) BOOL isForPreview;
@ -37,17 +38,20 @@ NS_ASSUME_NONNULL_BEGIN
+ (OWSQuotedMessageView *)quotedMessageViewForConversation:(OWSQuotedReplyModel *)quotedMessage + (OWSQuotedMessageView *)quotedMessageViewForConversation:(OWSQuotedReplyModel *)quotedMessage
displayableQuotedText:(nullable DisplayableText *)displayableQuotedText displayableQuotedText:(nullable DisplayableText *)displayableQuotedText
conversationStyle:(ConversationStyle *)conversationStyle
isOutgoing:(BOOL)isOutgoing isOutgoing:(BOOL)isOutgoing
{ {
OWSAssert(quotedMessage); OWSAssert(quotedMessage);
return [[OWSQuotedMessageView alloc] initWithQuotedMessage:quotedMessage return [[OWSQuotedMessageView alloc] initWithQuotedMessage:quotedMessage
displayableQuotedText:displayableQuotedText displayableQuotedText:displayableQuotedText
conversationStyle:conversationStyle
isForPreview:NO isForPreview:NO
isOutgoing:isOutgoing]; isOutgoing:isOutgoing];
} }
+ (OWSQuotedMessageView *)quotedMessageViewForPreview:(OWSQuotedReplyModel *)quotedMessage + (OWSQuotedMessageView *)quotedMessageViewForPreview:(OWSQuotedReplyModel *)quotedMessage
conversationStyle:(ConversationStyle *)conversationStyle
{ {
OWSAssert(quotedMessage); OWSAssert(quotedMessage);
@ -58,6 +62,7 @@ NS_ASSUME_NONNULL_BEGIN
OWSQuotedMessageView *instance = [[OWSQuotedMessageView alloc] initWithQuotedMessage:quotedMessage OWSQuotedMessageView *instance = [[OWSQuotedMessageView alloc] initWithQuotedMessage:quotedMessage
displayableQuotedText:displayableQuotedText displayableQuotedText:displayableQuotedText
conversationStyle:conversationStyle
isForPreview:YES isForPreview:YES
isOutgoing:YES]; isOutgoing:YES];
[instance createContents]; [instance createContents];
@ -66,6 +71,7 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithQuotedMessage:(OWSQuotedReplyModel *)quotedMessage - (instancetype)initWithQuotedMessage:(OWSQuotedReplyModel *)quotedMessage
displayableQuotedText:(nullable DisplayableText *)displayableQuotedText displayableQuotedText:(nullable DisplayableText *)displayableQuotedText
conversationStyle:(ConversationStyle *)conversationStyle
isForPreview:(BOOL)isForPreview isForPreview:(BOOL)isForPreview
isOutgoing:(BOOL)isOutgoing isOutgoing:(BOOL)isOutgoing
{ {
@ -80,6 +86,7 @@ NS_ASSUME_NONNULL_BEGIN
_quotedMessage = quotedMessage; _quotedMessage = quotedMessage;
_displayableQuotedText = displayableQuotedText; _displayableQuotedText = displayableQuotedText;
_isForPreview = isForPreview; _isForPreview = isForPreview;
_conversationStyle = conversationStyle;
_isOutgoing = isOutgoing; _isOutgoing = isOutgoing;
_quotedAuthorLabel = [UILabel new]; _quotedAuthorLabel = [UILabel new];
@ -104,7 +111,7 @@ NS_ASSUME_NONNULL_BEGIN
- (UIColor *)highlightColor - (UIColor *)highlightColor
{ {
BOOL isQuotingSelf = [NSObject isNullableObject:self.quotedMessage.authorId equalTo:TSAccountManager.localNumber]; BOOL isQuotingSelf = [NSObject isNullableObject:self.quotedMessage.authorId equalTo:TSAccountManager.localNumber];
return (isQuotingSelf ? ConversationStyle.bubbleColorOutgoingSent : [UIColor colorWithRGBHex:0xB5B5B5]); return (isQuotingSelf ? self.conversationStyle.bubbleColorOutgoingSent : [UIColor colorWithRGBHex:0xB5B5B5]);
} }
#pragma mark - #pragma mark -
@ -120,7 +127,7 @@ NS_ASSUME_NONNULL_BEGIN
self.clipsToBounds = YES; self.clipsToBounds = YES;
self.boundsStrokeView = [OWSBubbleShapeView new]; self.boundsStrokeView = [OWSBubbleShapeView new];
self.boundsStrokeView.strokeColor = ConversationStyle.bubbleColorIncoming; self.boundsStrokeView.strokeColor = [self.conversationStyle primaryColor];
self.boundsStrokeView.strokeThickness = 1.f; self.boundsStrokeView.strokeThickness = 1.f;
[self addSubview:self.boundsStrokeView]; [self addSubview:self.boundsStrokeView];
[self.boundsStrokeView autoPinToSuperviewEdges]; [self.boundsStrokeView autoPinToSuperviewEdges];

@ -53,7 +53,7 @@ public class ConversationHeaderView: UIStackView {
private let titleLabel: UILabel private let titleLabel: UILabel
private let subtitleLabel: UILabel private let subtitleLabel: UILabel
private let avatarView: AvatarImageView private let avatarView: ConversationAvatarImageView
@objc @objc
public required init(thread: TSThread, contactsManager: OWSContactsManager) { public required init(thread: TSThread, contactsManager: OWSContactsManager) {
@ -115,6 +115,11 @@ public class ConversationHeaderView: UIStackView {
return UILayoutFittingExpandedSize return UILayoutFittingExpandedSize
} }
@objc
public func updateAvatar() {
self.avatarView.updateImage()
}
// MARK: Delegate Methods // MARK: Delegate Methods
@objc func didTapView(tapGesture: UITapGestureRecognizer) { @objc func didTapView(tapGesture: UITapGestureRecognizer) {

@ -4,6 +4,7 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@class ConversationStyle;
@class OWSQuotedReplyModel; @class OWSQuotedReplyModel;
@class SignalAttachment; @class SignalAttachment;
@ -33,6 +34,8 @@ NS_ASSUME_NONNULL_BEGIN
@interface ConversationInputToolbar : UIView @interface ConversationInputToolbar : UIView
- (instancetype)initWithConversationStyle:(ConversationStyle *)conversationStyle NS_DESIGNATED_INITIALIZER;
@property (nonatomic, weak) id<ConversationInputToolbarDelegate> inputToolbarDelegate; @property (nonatomic, weak) id<ConversationInputToolbarDelegate> inputToolbarDelegate;
- (void)beginEditingTextMessage; - (void)beginEditingTextMessage;

@ -27,6 +27,8 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5;
ConversationTextViewToolbarDelegate, ConversationTextViewToolbarDelegate,
QuotedReplyPreviewDelegate> QuotedReplyPreviewDelegate>
@property (nonatomic, readonly) ConversationStyle *conversationStyle;
@property (nonatomic, readonly) UIView *composeContainer; @property (nonatomic, readonly) UIView *composeContainer;
@property (nonatomic, readonly) ConversationInputTextView *inputTextView; @property (nonatomic, readonly) ConversationInputTextView *inputTextView;
@property (nonatomic, readonly) UIStackView *contentStackView; @property (nonatomic, readonly) UIStackView *contentStackView;
@ -66,13 +68,17 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5;
@implementation ConversationInputToolbar @implementation ConversationInputToolbar
- (instancetype)init - (instancetype)initWithConversationStyle:(ConversationStyle *)conversationStyle
{ {
self = [super init]; self = [super init];
_conversationStyle = conversationStyle;
if (self) { if (self) {
[self createContents]; [self createContents];
} }
return self; return self;
} }
@ -268,7 +274,7 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5;
return; return;
} }
self.quotedMessagePreview = [[QuotedReplyPreview alloc] initWithQuotedReply:quotedReply]; self.quotedMessagePreview = [[QuotedReplyPreview alloc] initWithQuotedReply:quotedReply conversationStyle:self.conversationStyle];
self.quotedMessagePreview.delegate = self; self.quotedMessagePreview.delegate = self;
// TODO animate // TODO animate

@ -564,7 +564,7 @@ typedef enum : NSUInteger {
[self.collectionView applyScrollViewInsetsFix]; [self.collectionView applyScrollViewInsetsFix];
_inputToolbar = [ConversationInputToolbar new]; _inputToolbar = [[ConversationInputToolbar alloc] initWithConversationStyle:self.conversationStyle];
self.inputToolbar.inputToolbarDelegate = self; self.inputToolbar.inputToolbarDelegate = self;
self.inputToolbar.inputTextViewDelegate = self; self.inputToolbar.inputTextViewDelegate = self;
[self.collectionView autoPinToBottomLayoutGuideOfViewController:self withInset:0]; [self.collectionView autoPinToBottomLayoutGuideOfViewController:self withInset:0];
@ -4326,6 +4326,13 @@ typedef enum : NSUInteger {
}]; }];
} }
- (void)conversationColorWasUpdated
{
[self.conversationStyle updateProperties];
[self.headerView updateAvatar];
[self.collectionView reloadData];
}
- (void)groupWasUpdated:(TSGroupModel *)groupModel - (void)groupWasUpdated:(TSGroupModel *)groupModel
{ {
OWSAssert(groupModel); OWSAssert(groupModel);

@ -1297,6 +1297,8 @@ NS_ASSUME_NONNULL_BEGIN
messageState:TSOutgoingMessageStateSent messageState:TSOutgoingMessageStateSent
text:@"⚠️ Outgoing Reserved Color Png ⚠️"]]; text:@"⚠️ Outgoing Reserved Color Png ⚠️"]];
} }
ConversationStyle *conversationStyle = [[ConversationStyle alloc] initWithThread:thread];
[actions addObjectsFromArray:@[ [actions addObjectsFromArray:@[
[self fakeOutgoingPngAction:thread [self fakeOutgoingPngAction:thread
actionLabel:@"Fake Outgoing White Png" actionLabel:@"Fake Outgoing White Png"
@ -1326,7 +1328,7 @@ NS_ASSUME_NONNULL_BEGIN
[self fakeOutgoingPngAction:thread [self fakeOutgoingPngAction:thread
actionLabel:@"Fake Outgoing 'Outgoing Unsent' Png" actionLabel:@"Fake Outgoing 'Outgoing Unsent' Png"
imageSize:CGSizeMake(200.f, 200.f) imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[ConversationStyle bubbleColorOutgoingUnsent] backgroundColor:[conversationStyle bubbleColorOutgoingUnsent]
textColor:[UIColor whiteColor] textColor:[UIColor whiteColor]
imageLabel:@"W" imageLabel:@"W"
messageState:TSOutgoingMessageStateFailed messageState:TSOutgoingMessageStateFailed
@ -1334,7 +1336,7 @@ NS_ASSUME_NONNULL_BEGIN
[self fakeOutgoingPngAction:thread [self fakeOutgoingPngAction:thread
actionLabel:@"Fake Outgoing 'Outgoing Unsent' Png" actionLabel:@"Fake Outgoing 'Outgoing Unsent' Png"
imageSize:CGSizeMake(200.f, 200.f) imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[ConversationStyle bubbleColorOutgoingUnsent] backgroundColor:[conversationStyle bubbleColorOutgoingUnsent]
textColor:[UIColor whiteColor] textColor:[UIColor whiteColor]
imageLabel:@"W" imageLabel:@"W"
messageState:TSOutgoingMessageStateSending messageState:TSOutgoingMessageStateSending
@ -1342,7 +1344,7 @@ NS_ASSUME_NONNULL_BEGIN
[self fakeOutgoingPngAction:thread [self fakeOutgoingPngAction:thread
actionLabel:@"Fake Outgoing 'Outgoing Unsent' Png" actionLabel:@"Fake Outgoing 'Outgoing Unsent' Png"
imageSize:CGSizeMake(200.f, 200.f) imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[ConversationStyle bubbleColorOutgoingUnsent] backgroundColor:[conversationStyle bubbleColorOutgoingUnsent]
textColor:[UIColor whiteColor] textColor:[UIColor whiteColor]
imageLabel:@"W" imageLabel:@"W"
messageState:TSOutgoingMessageStateSent messageState:TSOutgoingMessageStateSent
@ -1351,7 +1353,7 @@ NS_ASSUME_NONNULL_BEGIN
[self fakeOutgoingPngAction:thread [self fakeOutgoingPngAction:thread
actionLabel:@"Fake Outgoing 'Outgoing Sending' Png" actionLabel:@"Fake Outgoing 'Outgoing Sending' Png"
imageSize:CGSizeMake(200.f, 200.f) imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[ConversationStyle bubbleColorOutgoingSending] backgroundColor:[conversationStyle bubbleColorOutgoingSending]
textColor:[UIColor whiteColor] textColor:[UIColor whiteColor]
imageLabel:@"W" imageLabel:@"W"
messageState:TSOutgoingMessageStateFailed messageState:TSOutgoingMessageStateFailed
@ -1359,7 +1361,7 @@ NS_ASSUME_NONNULL_BEGIN
[self fakeOutgoingPngAction:thread [self fakeOutgoingPngAction:thread
actionLabel:@"Fake Outgoing 'Outgoing Sending' Png" actionLabel:@"Fake Outgoing 'Outgoing Sending' Png"
imageSize:CGSizeMake(200.f, 200.f) imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[ConversationStyle bubbleColorOutgoingSending] backgroundColor:[conversationStyle bubbleColorOutgoingSending]
textColor:[UIColor whiteColor] textColor:[UIColor whiteColor]
imageLabel:@"W" imageLabel:@"W"
messageState:TSOutgoingMessageStateSending messageState:TSOutgoingMessageStateSending
@ -1367,7 +1369,7 @@ NS_ASSUME_NONNULL_BEGIN
[self fakeOutgoingPngAction:thread [self fakeOutgoingPngAction:thread
actionLabel:@"Fake Outgoing 'Outgoing Sending' Png" actionLabel:@"Fake Outgoing 'Outgoing Sending' Png"
imageSize:CGSizeMake(200.f, 200.f) imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[ConversationStyle bubbleColorOutgoingSending] backgroundColor:[conversationStyle bubbleColorOutgoingSending]
textColor:[UIColor whiteColor] textColor:[UIColor whiteColor]
imageLabel:@"W" imageLabel:@"W"
messageState:TSOutgoingMessageStateSent messageState:TSOutgoingMessageStateSent
@ -1376,7 +1378,7 @@ NS_ASSUME_NONNULL_BEGIN
[self fakeOutgoingPngAction:thread [self fakeOutgoingPngAction:thread
actionLabel:@"Fake Outgoing 'Outgoing Sent' Png" actionLabel:@"Fake Outgoing 'Outgoing Sent' Png"
imageSize:CGSizeMake(200.f, 200.f) imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[ConversationStyle bubbleColorOutgoingSent] backgroundColor:[conversationStyle bubbleColorOutgoingSent]
textColor:[UIColor whiteColor] textColor:[UIColor whiteColor]
imageLabel:@"W" imageLabel:@"W"
messageState:TSOutgoingMessageStateFailed messageState:TSOutgoingMessageStateFailed
@ -1384,7 +1386,7 @@ NS_ASSUME_NONNULL_BEGIN
[self fakeOutgoingPngAction:thread [self fakeOutgoingPngAction:thread
actionLabel:@"Fake Outgoing 'Outgoing Sent' Png" actionLabel:@"Fake Outgoing 'Outgoing Sent' Png"
imageSize:CGSizeMake(200.f, 200.f) imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[ConversationStyle bubbleColorOutgoingSent] backgroundColor:[conversationStyle bubbleColorOutgoingSent]
textColor:[UIColor whiteColor] textColor:[UIColor whiteColor]
imageLabel:@"W" imageLabel:@"W"
messageState:TSOutgoingMessageStateSending messageState:TSOutgoingMessageStateSending
@ -1392,7 +1394,7 @@ NS_ASSUME_NONNULL_BEGIN
[self fakeOutgoingPngAction:thread [self fakeOutgoingPngAction:thread
actionLabel:@"Fake Outgoing 'Outgoing Sent' Png" actionLabel:@"Fake Outgoing 'Outgoing Sent' Png"
imageSize:CGSizeMake(200.f, 200.f) imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[ConversationStyle bubbleColorOutgoingSent] backgroundColor:[conversationStyle bubbleColorOutgoingSent]
textColor:[UIColor whiteColor] textColor:[UIColor whiteColor]
imageLabel:@"W" imageLabel:@"W"
messageState:TSOutgoingMessageStateSent messageState:TSOutgoingMessageStateSent
@ -1560,7 +1562,7 @@ NS_ASSUME_NONNULL_BEGIN
[self fakeIncomingPngAction:thread [self fakeIncomingPngAction:thread
actionLabel:@"Fake Incoming 'Incoming' Png" actionLabel:@"Fake Incoming 'Incoming' Png"
imageSize:CGSizeMake(200.f, 200.f) imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[ConversationStyle bubbleColorIncoming] backgroundColor:[conversationStyle primaryColor]
textColor:[UIColor whiteColor] textColor:[UIColor whiteColor]
imageLabel:@"W" imageLabel:@"W"
isAttachmentDownloaded:YES isAttachmentDownloaded:YES
@ -1568,7 +1570,7 @@ NS_ASSUME_NONNULL_BEGIN
[self fakeIncomingPngAction:thread [self fakeIncomingPngAction:thread
actionLabel:@"Fake Incoming 'Incoming' Png" actionLabel:@"Fake Incoming 'Incoming' Png"
imageSize:CGSizeMake(200.f, 200.f) imageSize:CGSizeMake(200.f, 200.f)
backgroundColor:[ConversationStyle bubbleColorIncoming] backgroundColor:[conversationStyle primaryColor]
textColor:[UIColor whiteColor] textColor:[UIColor whiteColor]
imageLabel:@"W" imageLabel:@"W"
isAttachmentDownloaded:NO isAttachmentDownloaded:NO

@ -37,10 +37,13 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@interface OWSConversationSettingsViewController () <ContactEditingDelegate, ContactsViewHelperDelegate> @interface OWSConversationSettingsViewController () <ContactEditingDelegate,
ContactsViewHelperDelegate,
ColorPickerDelegate>
@property (nonatomic) TSThread *thread; @property (nonatomic) TSThread *thread;
@property (nonatomic) YapDatabaseConnection *uiDatabaseConnection; @property (nonatomic) YapDatabaseConnection *uiDatabaseConnection;
@property (nonatomic, readonly) YapDatabaseConnection *editingDatabaseConnection;
@property (nonatomic) NSArray<NSNumber *> *disappearingMessagesDurations; @property (nonatomic) NSArray<NSNumber *> *disappearingMessagesDurations;
@property (nonatomic) OWSDisappearingMessagesConfiguration *disappearingMessagesConfiguration; @property (nonatomic) OWSDisappearingMessagesConfiguration *disappearingMessagesConfiguration;
@ -123,6 +126,11 @@ NS_ASSUME_NONNULL_BEGIN
object:nil]; object:nil];
} }
- (YapDatabaseConnection *)editingDatabaseConnection
{
return [OWSPrimaryStorage sharedManager].dbReadWriteConnection;
}
- (NSString *)threadName - (NSString *)threadName
{ {
NSString *threadName = self.thread.name; NSString *threadName = self.thread.name;
@ -271,6 +279,19 @@ NS_ASSUME_NONNULL_BEGIN
[weakSelf showMediaGallery]; [weakSelf showMediaGallery];
}]]; }]];
[mainSection addItem:[OWSTableItem
itemWithCustomCellBlock:^{
NSString *colorName = self.thread.conversationColorName;
UIColor *currentColor = [UIColor ows_conversationColorForColorName:colorName];
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];
}
actionBlock:^{
[weakSelf showColorPicker];
}]];
if ([self.thread isKindOfClass:[TSContactThread class]] && self.contactsManager.supportsContactEditing if ([self.thread isKindOfClass:[TSContactThread class]] && self.contactsManager.supportsContactEditing
&& !self.hasExistingContact) { && !self.hasExistingContact) {
[mainSection addItem:[OWSTableItem itemWithCustomCellBlock:^{ [mainSection addItem:[OWSTableItem itemWithCustomCellBlock:^{
@ -623,6 +644,39 @@ NS_ASSUME_NONNULL_BEGIN
return 12.f; return 12.f;
} }
- (UITableViewCell *)disclosureCellWithName:(NSString *)name iconColor:(UIColor *)iconColor
{
OWSAssert(name.length > 0);
UITableViewCell *cell = [UITableViewCell new];
cell.preservesSuperviewLayoutMargins = YES;
cell.contentView.preservesSuperviewLayoutMargins = YES;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
UIView *swatchView = [NeverClearView new];
const CGFloat kSwatchWidth = 24;
[swatchView autoSetDimension:ALDimensionWidth toSize:kSwatchWidth];
[swatchView autoSetDimension:ALDimensionHeight toSize:kSwatchWidth];
swatchView.layer.cornerRadius = kSwatchWidth / 2;
swatchView.backgroundColor = iconColor;
[cell.contentView addSubview:swatchView];
[swatchView autoVCenterInSuperview];
[swatchView autoPinLeadingToSuperviewMargin];
UILabel *rowLabel = [UILabel new];
rowLabel.text = name;
rowLabel.textColor = [UIColor blackColor];
rowLabel.font = [UIFont ows_regularFontWithSize:17.f];
rowLabel.lineBreakMode = NSLineBreakByTruncatingTail;
[cell.contentView addSubview:rowLabel];
[rowLabel autoVCenterInSuperview];
[rowLabel autoPinLeadingToTrailingEdgeOfView:swatchView offset:self.iconSpacing];
[rowLabel autoPinTrailingToSuperviewMargin];
return cell;
}
- (UITableViewCell *)cellWithName:(NSString *)name iconName:(NSString *)iconName - (UITableViewCell *)cellWithName:(NSString *)name iconName:(NSString *)iconName
{ {
OWSAssert(name.length > 0); OWSAssert(name.length > 0);
@ -1158,7 +1212,10 @@ NS_ASSUME_NONNULL_BEGIN
- (void)setThreadMutedUntilDate:(nullable NSDate *)value - (void)setThreadMutedUntilDate:(nullable NSDate *)value
{ {
[self.thread updateWithMutedUntilDate:value]; [self.editingDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull transaction) {
[self.thread updateWithMutedUntilDate:value transaction:transaction];
}];
[self updateTableContents]; [self updateTableContents];
} }
@ -1200,6 +1257,36 @@ NS_ASSUME_NONNULL_BEGIN
} }
} }
#pragma mark - ColorPickerDelegate
- (void)showColorPicker
{
ColorPickerViewController *pickerController = [[ColorPickerViewController alloc] initWithThread:self.thread];
pickerController.delegate = self;
OWSNavigationController *modal = [[OWSNavigationController alloc] initWithRootViewController:pickerController];
[self presentViewController:modal animated:YES completion:nil];
}
- (void)colorPicker:(ColorPickerViewController *)colorPicker didPickColorName:(NSString *)colorName
{
DDLogDebug(@"%@ in %s picked color: %@", self.logTag, __PRETTY_FUNCTION__, colorName);
[self.editingDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[self.thread updateConversationColorName:colorName transaction:transaction];
[self.contactsManager.avatarCache removeAllImages];
[self updateTableContents];
[self.conversationSettingsViewDelegate conversationColorWasUpdated];
[self dismissViewControllerAnimated:YES completion:nil];
}];
}
- (void)colorPickerDidCancel:(ColorPickerViewController *)colorPicker
{
[self dismissViewControllerAnimated:YES completion:nil];
}
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

@ -8,6 +8,7 @@ NS_ASSUME_NONNULL_BEGIN
@protocol OWSConversationSettingsViewDelegate <NSObject> @protocol OWSConversationSettingsViewDelegate <NSObject>
- (void)conversationColorWasUpdated;
- (void)groupWasUpdated:(TSGroupModel *)groupModel; - (void)groupWasUpdated:(TSGroupModel *)groupModel;
- (void)popAllConversationSettingsViews; - (void)popAllConversationSettingsViews;

@ -15,6 +15,7 @@ class QuotedReplyPreview: UIView {
public weak var delegate: QuotedReplyPreviewDelegate? public weak var delegate: QuotedReplyPreviewDelegate?
private let quotedReply: OWSQuotedReplyModel private let quotedReply: OWSQuotedReplyModel
private let conversationStyle: ConversationStyle
private var quotedMessageView: OWSQuotedMessageView? private var quotedMessageView: OWSQuotedMessageView?
private var heightConstraint: NSLayoutConstraint! private var heightConstraint: NSLayoutConstraint!
@ -24,8 +25,9 @@ class QuotedReplyPreview: UIView {
} }
@objc @objc
init(quotedReply: OWSQuotedReplyModel) { init(quotedReply: OWSQuotedReplyModel, conversationStyle: ConversationStyle) {
self.quotedReply = quotedReply self.quotedReply = quotedReply
self.conversationStyle = conversationStyle
super.init(frame: .zero) super.init(frame: .zero)
@ -42,7 +44,7 @@ class QuotedReplyPreview: UIView {
// We instantiate quotedMessageView late to ensure that it is updated // We instantiate quotedMessageView late to ensure that it is updated
// every time contentSizeCategoryDidChange (i.e. when dynamic type // every time contentSizeCategoryDidChange (i.e. when dynamic type
// sizes changes). // sizes changes).
let quotedMessageView = OWSQuotedMessageView(forPreview: quotedReply) let quotedMessageView = OWSQuotedMessageView(forPreview: quotedReply, conversationStyle: conversationStyle)
self.quotedMessageView = quotedMessageView self.quotedMessageView = quotedMessageView
quotedMessageView.backgroundColor = .clear quotedMessageView.backgroundColor = .clear

@ -511,6 +511,9 @@
/* Navbar title when viewing settings for a 1-on-1 thread */ /* Navbar title when viewing settings for a 1-on-1 thread */
"CONVERSATION_SETTINGS_CONTACT_INFO_TITLE" = "Contact Info"; "CONVERSATION_SETTINGS_CONTACT_INFO_TITLE" = "Contact Info";
/* Indicates that user's profile has been shared with a group. */
"CONVERSATION_SETTINGS_CONVERSATION_COLOR" = "Color";
/* Navbar title when viewing settings for a group thread */ /* Navbar title when viewing settings for a group thread */
"CONVERSATION_SETTINGS_GROUP_INFO_TITLE" = "Group Info"; "CONVERSATION_SETTINGS_GROUP_INFO_TITLE" = "Group Info";

@ -150,7 +150,7 @@ public class ConversationAvatarImageView: AvatarImageView {
self.updateImage() self.updateImage()
} }
func updateImage() { public func updateImage() {
Logger.debug("\(self.logTag) in \(#function) updateImage") Logger.debug("\(self.logTag) in \(#function) updateImage")
self.image = OWSAvatarBuilder.buildImage(thread: thread, diameter: diameter, contactsManager: contactsManager) self.image = OWSAvatarBuilder.buildImage(thread: thread, diameter: diameter, contactsManager: contactsManager)

@ -29,6 +29,7 @@ const CGFloat kContactCellAvatarTextMargin = 12;
@property (nonatomic) UIView *accessoryViewContainer; @property (nonatomic) UIView *accessoryViewContainer;
@property (nonatomic) OWSContactsManager *contactsManager; @property (nonatomic) OWSContactsManager *contactsManager;
@property (nonatomic) TSThread *thread;
@property (nonatomic) NSString *recipientId; @property (nonatomic) NSString *recipientId;
@end @end
@ -139,6 +140,7 @@ const CGFloat kContactCellAvatarTextMargin = 12;
- (void)configureWithThread:(TSThread *)thread contactsManager:(OWSContactsManager *)contactsManager - (void)configureWithThread:(TSThread *)thread contactsManager:(OWSContactsManager *)contactsManager
{ {
OWSAssert(thread); OWSAssert(thread);
self.thread = thread;
// Update fonts to reflect changes to dynamic type. // Update fonts to reflect changes to dynamic type.
[self configureFonts]; [self configureFonts];
@ -194,7 +196,11 @@ const CGFloat kContactCellAvatarTextMargin = 12;
return; return;
} }
NSString *colorName = self.thread.conversationColorName;
UIColor *color = [UIColor ows_conversationColorForColorName:colorName];
self.avatarView.image = [[[OWSContactAvatarBuilder alloc] initWithSignalId:recipientId self.avatarView.image = [[[OWSContactAvatarBuilder alloc] initWithSignalId:recipientId
color:color
diameter:kContactCellAvatarSize diameter:kContactCellAvatarSize
contactsManager:contactsManager] build]; contactsManager:contactsManager] build];
} }
@ -230,6 +236,7 @@ const CGFloat kContactCellAvatarTextMargin = 12;
{ {
[[NSNotificationCenter defaultCenter] removeObserver:self]; [[NSNotificationCenter defaultCenter] removeObserver:self];
self.thread = nil;
self.accessoryMessage = nil; self.accessoryMessage = nil;
self.nameLabel.text = nil; self.nameLabel.text = nil;
self.subtitleLabel.text = nil; self.subtitleLabel.text = nil;

@ -24,9 +24,17 @@ NS_ASSUME_NONNULL_BEGIN
@property (class, readonly, nonatomic) UIColor *ows_toolbarBackgroundColor; @property (class, readonly, nonatomic) UIColor *ows_toolbarBackgroundColor;
@property (class, readonly, nonatomic) UIColor *ows_messageBubbleLightGrayColor; @property (class, readonly, nonatomic) UIColor *ows_messageBubbleLightGrayColor;
+ (UIColor *)backgroundColorForContact:(NSString *)contactIdentifier;
+ (UIColor *)colorWithRGBHex:(unsigned long)value; + (UIColor *)colorWithRGBHex:(unsigned long)value;
#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:));
@property (class, readonly, nonatomic) NSArray<NSString *> *ows_conversationColorNames;
@property (class, readonly, nonatomic) NSArray<UIColor *> *ows_conversationColors;
- (UIColor *)blendWithColor:(UIColor *)otherColor alpha:(CGFloat)alpha; - (UIColor *)blendWithColor:(UIColor *)otherColor alpha:(CGFloat)alpha;
#pragma mark - #pragma mark -

@ -2,8 +2,8 @@
// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// //
#import "OWSMath.h"
#import "UIColor+OWS.h" #import "UIColor+OWS.h"
#import "OWSMath.h"
#import <SignalServiceKit/Cryptography.h> #import <SignalServiceKit/Cryptography.h>
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@ -98,40 +98,6 @@ NS_ASSUME_NONNULL_BEGIN
return [UIColor colorWithHue:240.0f / 360.0f saturation:0.02f brightness:0.92f alpha:1.0f]; return [UIColor colorWithHue:240.0f / 360.0f saturation:0.02f brightness:0.92f alpha:1.0f];
} }
+ (UIColor *)backgroundColorForContact:(NSString *)contactIdentifier
{
NSArray *colors = @[
[UIColor colorWithRed:204.f / 255.f green:148.f / 255.f blue:102.f / 255.f alpha:1.f],
[UIColor colorWithRed:187.f / 255.f green:104.f / 255.f blue:62.f / 255.f alpha:1.f],
[UIColor colorWithRed:145.f / 255.f green:78.f / 255.f blue:48.f / 255.f alpha:1.f],
[UIColor colorWithRed:122.f / 255.f green:63.f / 255.f blue:41.f / 255.f alpha:1.f],
[UIColor colorWithRed:80.f / 255.f green:46.f / 255.f blue:27.f / 255.f alpha:1.f],
[UIColor colorWithRed:57.f / 255.f green:45.f / 255.f blue:19.f / 255.f alpha:1.f],
[UIColor colorWithRed:37.f / 255.f green:38.f / 255.f blue:13.f / 255.f alpha:1.f],
[UIColor colorWithRed:23.f / 255.f green:31.f / 255.f blue:10.f / 255.f alpha:1.f],
[UIColor colorWithRed:6.f / 255.f green:19.f / 255.f blue:10.f / 255.f alpha:1.f],
[UIColor colorWithRed:13.f / 255.f green:4.f / 255.f blue:16.f / 255.f alpha:1.f],
[UIColor colorWithRed:27.f / 255.f green:12.f / 255.f blue:44.f / 255.f alpha:1.f],
[UIColor colorWithRed:18.f / 255.f green:17.f / 255.f blue:64.f / 255.f alpha:1.f],
[UIColor colorWithRed:20.f / 255.f green:42.f / 255.f blue:77.f / 255.f alpha:1.f],
[UIColor colorWithRed:18.f / 255.f green:55.f / 255.f blue:68.f / 255.f alpha:1.f],
[UIColor colorWithRed:18.f / 255.f green:68.f / 255.f blue:61.f / 255.f alpha:1.f],
[UIColor colorWithRed:19.f / 255.f green:73.f / 255.f blue:26.f / 255.f alpha:1.f],
[UIColor colorWithRed:13.f / 255.f green:48.f / 255.f blue:15.f / 255.f alpha:1.f],
[UIColor colorWithRed:44.f / 255.f green:165.f / 255.f blue:137.f / 255.f alpha:1.f],
[UIColor colorWithRed:137.f / 255.f green:181.f / 255.f blue:48.f / 255.f alpha:1.f],
[UIColor colorWithRed:208.f / 255.f green:204.f / 255.f blue:78.f / 255.f alpha:1.f],
[UIColor colorWithRed:227.f / 255.f green:162.f / 255.f blue:150.f / 255.f alpha:1.f]
];
NSData *contactData = [contactIdentifier dataUsingEncoding:NSUTF8StringEncoding];
NSUInteger hashingLength = 8;
unsigned long long choose;
NSData *hashData = [Cryptography computeSHA256Digest:contactData truncatedToBytes:hashingLength];
[hashData getBytes:&choose length:hashingLength];
return [colors objectAtIndex:(choose % [colors count])];
}
+ (UIColor *)colorWithRGBHex:(unsigned long)value + (UIColor *)colorWithRGBHex:(unsigned long)value
{ {
CGFloat red = ((value >> 16) & 0xff) / 255.f; CGFloat red = ((value >> 16) & 0xff) / 255.f;
@ -304,6 +270,50 @@ NS_ASSUME_NONNULL_BEGIN
return [UIColor colorWithRGBHex:0x757575]; return [UIColor colorWithRGBHex:0x757575];
} }
+ (NSDictionary<NSString *, UIColor *> *)ows_conversationColorMap
{
static NSDictionary<NSString *, UIColor *> *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<NSString *> *)ows_conversationColorNames
{
return self.ows_conversationColorMap.allKeys;
}
+ (NSArray<UIColor *> *)ows_conversationColors
{
return self.ows_conversationColorMap.allValues;
}
+ (nullable UIColor *)ows_conversationColorForColorName:(NSString *)colorName
{
OWSAssert(colorName.length > 0);
return [self.ows_conversationColorMap objectForKey:colorName];
}
+ (nullable NSString *)ows_conversationColorNameForColor:(UIColor *)color
{
return [self.ows_conversationColorMap allKeysForObject:color].firstObject;
}
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

@ -55,6 +55,7 @@ public class ConversationStyle: NSObject {
self.thread = thread self.thread = thread
self.isRTL = CurrentAppContext().isRTL self.isRTL = CurrentAppContext().isRTL
self.primaryColor = ConversationStyle.primaryColor(thread: thread)
super.init() super.init()
@ -78,7 +79,8 @@ public class ConversationStyle: NSObject {
// MARK: - // MARK: -
private func updateProperties() { @objc
public func updateProperties() {
if thread.isGroupThread() { if thread.isGroupThread() {
gutterLeading = 40 gutterLeading = 40
gutterTrailing = 20 gutterTrailing = 20
@ -106,37 +108,52 @@ public class ConversationStyle: NSObject {
textInsetHorizontal = 12 textInsetHorizontal = 12
lastTextLineAxis = CGFloat(round(12 + messageTextFont.capHeight * 0.5)) lastTextLineAxis = CGFloat(round(12 + messageTextFont.capHeight * 0.5))
self.primaryColor = ConversationStyle.primaryColor(thread: thread)
} }
// MARK: Colors // MARK: Colors
// TODO: Remove this! Incoming bubble colors are now dynamic. private class func primaryColor(thread: TSThread) -> UIColor {
@objc guard let colorName = thread.conversationColorName else {
public static let bubbleColorIncoming = UIColor.ows_messageBubbleLightGray return self.defaultBubbleColorIncoming
}
guard let color = UIColor.ows_conversationColor(colorName: colorName) else {
return self.defaultBubbleColorIncoming
}
return color
}
private static let defaultBubbleColorIncoming = UIColor.ows_messageBubbleLightGray
// TODO: // TODO:
@objc @objc
public static let bubbleColorOutgoingUnsent = UIColor.ows_red public let bubbleColorOutgoingUnsent = UIColor.ows_red
// TODO: // TODO:
@objc @objc
public static let bubbleColorOutgoingSending = UIColor.ows_light35 public let bubbleColorOutgoingSending = UIColor.ows_light35
@objc
public let bubbleColorOutgoingSent = UIColor.ows_light10
@objc @objc
public static let bubbleColorOutgoingSent = UIColor.ows_light10 public var primaryColor: UIColor
@objc @objc
public static func bubbleColor(message: TSMessage) -> UIColor { public func bubbleColor(message: TSMessage) -> UIColor {
if message is TSIncomingMessage { if message is TSIncomingMessage {
return ConversationStyle.bubbleColorIncoming return primaryColor
} else if let outgoingMessage = message as? TSOutgoingMessage { } else if let outgoingMessage = message as? TSOutgoingMessage {
switch outgoingMessage.messageState { switch outgoingMessage.messageState {
case .failed: case .failed:
return ConversationStyle.bubbleColorOutgoingUnsent return self.bubbleColorOutgoingUnsent
case .sending: case .sending:
return ConversationStyle.bubbleColorOutgoingSending return self.bubbleColorOutgoingSending
default: default:
return ConversationStyle.bubbleColorOutgoingSent return self.bubbleColorOutgoingSent
} }
} else { } else {
owsFail("Unexpected message type: \(message)") owsFail("Unexpected message type: \(message)")
@ -145,7 +162,7 @@ public class ConversationStyle: NSObject {
} }
@objc @objc
public static func bubbleTextColor(message: TSMessage) -> UIColor { public func bubbleTextColor(message: TSMessage) -> UIColor {
if message is TSIncomingMessage { if message is TSIncomingMessage {
return UIColor.ows_white return UIColor.ows_white
} else if let outgoingMessage = message as? TSOutgoingMessage { } else if let outgoingMessage = message as? TSOutgoingMessage {

@ -27,7 +27,10 @@ NS_ASSUME_NONNULL_BEGIN
OWSAvatarBuilder *avatarBuilder; OWSAvatarBuilder *avatarBuilder;
if ([thread isKindOfClass:[TSContactThread class]]) { if ([thread isKindOfClass:[TSContactThread class]]) {
TSContactThread *contactThread = (TSContactThread *)thread; TSContactThread *contactThread = (TSContactThread *)thread;
NSString *colorName = thread.conversationColorName;
UIColor *color = [UIColor ows_conversationColorForColorName:colorName];
avatarBuilder = [[OWSContactAvatarBuilder alloc] initWithSignalId:contactThread.contactIdentifier avatarBuilder = [[OWSContactAvatarBuilder alloc] initWithSignalId:contactThread.contactIdentifier
color:color
diameter:diameter diameter:diameter
contactsManager:contactsManager]; contactsManager:contactsManager];
} else if ([thread isKindOfClass:[TSGroupThread class]]) { } else if ([thread isKindOfClass:[TSGroupThread class]]) {

@ -14,7 +14,9 @@ NS_ASSUME_NONNULL_BEGIN
/** /**
* Build an avatar for a Signal recipient * Build an avatar for a Signal recipient
*/ */
- (instancetype)initWithSignalId:(NSString *)signalId - (instancetype)initWithSignalId:(NSString *)signalId
color:(UIColor *)color
diameter:(NSUInteger)diameter diameter:(NSUInteger)diameter
contactsManager:(OWSContactsManager *)contactsManager; contactsManager:(OWSContactsManager *)contactsManager;
@ -26,7 +28,6 @@ NS_ASSUME_NONNULL_BEGIN
diameter:(NSUInteger)diameter diameter:(NSUInteger)diameter
contactsManager:(OWSContactsManager *)contactsManager; contactsManager:(OWSContactsManager *)contactsManager;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

@ -22,6 +22,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly) OWSContactsManager *contactsManager; @property (nonatomic, readonly) OWSContactsManager *contactsManager;
@property (nonatomic, readonly) NSString *signalId; @property (nonatomic, readonly) NSString *signalId;
@property (nonatomic, readonly) NSString *contactName; @property (nonatomic, readonly) NSString *contactName;
@property (nonatomic, readonly) UIColor *color;
@property (nonatomic, readonly) NSUInteger diameter; @property (nonatomic, readonly) NSUInteger diameter;
@end @end
@ -32,6 +33,7 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithContactId:(NSString *)contactId - (instancetype)initWithContactId:(NSString *)contactId
name:(NSString *)name name:(NSString *)name
color:(UIColor *)color
diameter:(NSUInteger)diameter diameter:(NSUInteger)diameter
contactsManager:(OWSContactsManager *)contactsManager contactsManager:(OWSContactsManager *)contactsManager
{ {
@ -42,6 +44,7 @@ NS_ASSUME_NONNULL_BEGIN
_signalId = contactId; _signalId = contactId;
_contactName = name; _contactName = name;
_color = color;
_diameter = diameter; _diameter = diameter;
_contactsManager = contactsManager; _contactsManager = contactsManager;
@ -49,6 +52,7 @@ NS_ASSUME_NONNULL_BEGIN
} }
- (instancetype)initWithSignalId:(NSString *)signalId - (instancetype)initWithSignalId:(NSString *)signalId
color:(UIColor *)color
diameter:(NSUInteger)diameter diameter:(NSUInteger)diameter
contactsManager:(OWSContactsManager *)contactsManager contactsManager:(OWSContactsManager *)contactsManager
{ {
@ -60,7 +64,7 @@ NS_ASSUME_NONNULL_BEGIN
if (name.length == 0) { if (name.length == 0) {
name = signalId; name = signalId;
} }
return [self initWithContactId:signalId name:name diameter:diameter contactsManager:contactsManager]; return [self initWithContactId:signalId name:name color:color diameter:diameter contactsManager:contactsManager];
} }
- (instancetype)initWithNonSignalName:(NSString *)nonSignalName - (instancetype)initWithNonSignalName:(NSString *)nonSignalName
@ -68,7 +72,15 @@ NS_ASSUME_NONNULL_BEGIN
diameter:(NSUInteger)diameter diameter:(NSUInteger)diameter
contactsManager:(OWSContactsManager *)contactsManager contactsManager:(OWSContactsManager *)contactsManager
{ {
return [self initWithContactId:colorSeed name:nonSignalName diameter:diameter contactsManager:contactsManager];
NSString *colorName = [TSThread stableConversationColorNameForString:colorSeed];
UIColor *color = [UIColor ows_conversationColorForColorName:colorName];
OWSAssert(color);
return [self initWithContactId:colorSeed
name:nonSignalName
color:color
diameter:diameter
contactsManager:contactsManager];
} }
#pragma mark - Instance methods #pragma mark - Instance methods
@ -113,9 +125,9 @@ NS_ASSUME_NONNULL_BEGIN
} }
CGFloat fontSize = (CGFloat)self.diameter / 2.8; CGFloat fontSize = (CGFloat)self.diameter / 2.8;
UIColor *backgroundColor = [UIColor backgroundColorForContact:self.signalId];
UIImage *image = [[JSQMessagesAvatarImageFactory avatarImageWithUserInitials:initials UIImage *image = [[JSQMessagesAvatarImageFactory avatarImageWithUserInitials:initials
backgroundColor:backgroundColor backgroundColor:self.color
textColor:[UIColor whiteColor] textColor:[UIColor whiteColor]
font:[UIFont ows_boldFontWithSize:fontSize] font:[UIFont ows_boldFontWithSize:fontSize]
diameter:self.diameter] avatarImage]; diameter:self.diameter] avatarImage];

@ -33,6 +33,10 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
- (NSString *)name; - (NSString *)name;
@property (readonly, nullable) NSString *conversationColorName;
- (void)updateConversationColorName:(NSString *)colorName transaction:(YapDatabaseReadWriteTransaction *)transaction;
+ (NSString *)stableConversationColorNameForString:(NSString *)colorSeed;
/** /**
* @returns * @returns
* Signal Id (e164) of the contact if it's a contact thread. * Signal Id (e164) of the contact if it's a contact thread.
@ -154,7 +158,7 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Update With... Methods #pragma mark - Update With... Methods
- (void)updateWithMutedUntilDate:(NSDate *)mutedUntilDate; - (void)updateWithMutedUntilDate:(NSDate *)mutedUntilDate transaction:(YapDatabaseReadWriteTransaction *)transaction;
@end @end

@ -3,6 +3,7 @@
// //
#import "TSThread.h" #import "TSThread.h"
#import "Cryptography.h"
#import "NSDate+OWS.h" #import "NSDate+OWS.h"
#import "NSString+SSK.h" #import "NSString+SSK.h"
#import "OWSDisappearingMessagesConfiguration.h" #import "OWSDisappearingMessagesConfiguration.h"
@ -21,9 +22,10 @@ NS_ASSUME_NONNULL_BEGIN
@interface TSThread () @interface TSThread ()
@property (nonatomic) NSDate *creationDate; @property (nonatomic) NSDate *creationDate;
@property (nonatomic, copy) NSDate *archivalDate; @property (nonatomic, copy, nullable) NSDate *archivalDate;
@property (nonatomic) NSDate *lastMessageDate; @property (nonatomic, nullable) NSString *conversationColorName;
@property (nonatomic, copy) NSString *messageDraft; @property (nonatomic, nullable) NSDate *lastMessageDate;
@property (nonatomic, copy, nullable) NSString *messageDraft;
@property (atomic, nullable) NSDate *mutedUntilDate; @property (atomic, nullable) NSDate *mutedUntilDate;
@end @end
@ -45,6 +47,21 @@ NS_ASSUME_NONNULL_BEGIN
_lastMessageDate = nil; _lastMessageDate = nil;
_creationDate = [NSDate date]; _creationDate = [NSDate date];
_messageDraft = nil; _messageDraft = nil;
_conversationColorName = [self.class randomConversationColorName];
}
return self;
}
- (nullable instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (!self) {
return self;
}
if (!_conversationColorName) {
_conversationColorName = [self.class stableConversationColorNameForString:self.uniqueId];
} }
return self; return self;
@ -395,16 +412,58 @@ NS_ASSUME_NONNULL_BEGIN
[mutedUntilDate timeIntervalSinceDate:now] > 0); [mutedUntilDate timeIntervalSinceDate:now] > 0);
} }
#pragma mark - Update With... Methods - (void)updateWithMutedUntilDate:(NSDate *)mutedUntilDate transaction:(YapDatabaseReadWriteTransaction *)transaction
{
[self applyChangeToSelfAndLatestCopy:transaction
changeBlock:^(TSThread *thread) {
[thread setMutedUntilDate:mutedUntilDate];
}];
}
#pragma mark - Conversation Color
- (void)updateWithMutedUntilDate:(NSDate *)mutedUntilDate + (NSString *)randomConversationColorName
{ {
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { NSUInteger count = self.conversationColorNames.count;
[self applyChangeToSelfAndLatestCopy:transaction NSUInteger index = arc4random_uniform((uint32_t)count);
changeBlock:^(TSThread *thread) { return [self.conversationColorNames objectAtIndex:index];
[thread setMutedUntilDate:mutedUntilDate]; }
}];
}]; + (NSString *)stableConversationColorNameForString:(NSString *)colorSeed
{
NSData *contactData = [colorSeed dataUsingEncoding:NSUTF8StringEncoding];
NSUInteger hashingLength = sizeof(unsigned long long);
unsigned long long choose;
NSData *hashData = [Cryptography computeSHA256Digest:contactData truncatedToBytes:hashingLength];
[hashData getBytes:&choose length:hashingLength];
NSUInteger index = (choose % [self.conversationColorNames count]);
return [self.conversationColorNames objectAtIndex:index];
}
+ (NSArray<NSString *> *)conversationColorNames
{
return @[
@"red",
@"pink",
@"purple",
@"indigo",
@"blue",
@"cyan",
@"teal",
@"green",
@"deep_orange",
@"grey"
];
}
- (void)updateConversationColorName:(NSString *)colorName transaction:(YapDatabaseReadWriteTransaction *)transaction
{
[self applyChangeToSelfAndLatestCopy:transaction
changeBlock:^(TSThread *thread) {
thread.conversationColorName = colorName;
}];
} }
@end @end

Loading…
Cancel
Save