From b3ad6e27dcf23ff7f07fa1b3fa80978c14fec1b3 Mon Sep 17 00:00:00 2001
From: Matthew Chen <matthew@signal.org>
Date: Wed, 26 Sep 2018 14:27:30 -0400
Subject: [PATCH] Rework conversation message bubble colors; add "conversation
 colors" class.

---
 .../DomainFrontingCountryViewController.m     |  1 +
 .../ColorPickerViewController.swift           | 11 ++-
 .../Cells/OWSQuotedMessageView.m              |  2 +-
 .../ConversationScrollButton.m                |  1 +
 .../ViewControllers/DebugUI/DebugUIMessages.m | 82 ++++++-------------
 .../OWSConversationSettingsViewController.m   |  7 +-
 .../CountryCodeViewController.m               |  1 +
 .../ViewControllers/OWSTableViewController.m  |  1 +
 SignalMessaging/categories/Theme.h            |  9 ++
 SignalMessaging/categories/Theme.m            | 11 +++
 SignalMessaging/categories/UIColor+OWS.h      | 32 +++++---
 SignalMessaging/categories/UIColor+OWS.m      | 73 +++++++++++++----
 .../categories/UIViewController+OWS.m         |  3 +-
 SignalMessaging/utils/ConversationStyle.swift | 55 ++++---------
 SignalMessaging/utils/OWSAvatarBuilder.m      |  1 +
 .../utils/OWSContactAvatarBuilder.m           |  5 +-
 SignalMessaging/utils/UIUtil.m                |  1 +
 SignalServiceKit/src/Contacts/TSThread.h      |  2 +-
 SignalServiceKit/src/Contacts/TSThread.m      |  2 +-
 19 files changed, 155 insertions(+), 145 deletions(-)

diff --git a/Signal/src/ViewControllers/AppSettings/DomainFrontingCountryViewController.m b/Signal/src/ViewControllers/AppSettings/DomainFrontingCountryViewController.m
index 0f0be7ea9..6c49443cb 100644
--- a/Signal/src/ViewControllers/AppSettings/DomainFrontingCountryViewController.m
+++ b/Signal/src/ViewControllers/AppSettings/DomainFrontingCountryViewController.m
@@ -8,6 +8,7 @@
 #import "UIColor+OWS.h"
 #import "UIFont+OWS.h"
 #import "UIView+OWS.h"
+#import <SignalMessaging/Theme.h>
 #import <SignalServiceKit/OWSSignalService.h>
 
 NS_ASSUME_NONNULL_BEGIN
diff --git a/Signal/src/ViewControllers/ColorPickerViewController.swift b/Signal/src/ViewControllers/ColorPickerViewController.swift
index 6af04991a..089e9988a 100644
--- a/Signal/src/ViewControllers/ColorPickerViewController.swift
+++ b/Signal/src/ViewControllers/ColorPickerViewController.swift
@@ -82,8 +82,8 @@ class ColorPickerViewController: UIViewController, UIPickerViewDelegate, UIPicke
     override func viewDidLoad() {
         super.viewDidLoad()
 
-        if let colorName = thread.conversationColorName,
-            let index = colorNames.index(of: colorName) {
+        let colorName = thread.conversationColorName
+        if let index = colorNames.index(of: colorName) {
             pickerView.selectRow(index, inComponent: 0, animated: false)
         }
     }
@@ -110,12 +110,11 @@ class ColorPickerViewController: UIViewController, UIPickerViewDelegate, UIPicke
             owsFailDebug("color was unexpectedly nil")
             return ColorView(color: .white)
         }
-        guard let color = UIColor.ows_conversationColor(colorName: colorName,
-                                                        mode: Theme.isDarkThemeEnabled ? .shade : .default) else {
+        guard let colors = UIColor.ows_conversationColors(colorName: colorName) else {
             owsFailDebug("unknown color name")
-            return ColorView(color: .white)
+            return ColorView(color: UIColor.ows_defaultConversationColors().themeColor)
         }
-        return ColorView(color: color)
+        return ColorView(color: colors.themeColor)
     }
 
     // MARK: Actions
diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m
index 18eafdcae..6ca3fa67b 100644
--- a/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m
+++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m
@@ -122,7 +122,7 @@ const CGFloat kRemotelySourcedContentRowSpacing = 3;
 - (UIColor *)highlightColor
 {
     BOOL isQuotingSelf = [NSObject isNullableObject:self.quotedMessage.authorId equalTo:TSAccountManager.localNumber];
-    return (isQuotingSelf ? self.conversationStyle.bubbleColorOutgoingSent
+    return (isQuotingSelf ? [self.conversationStyle bubbleColorWithIsIncoming:NO]
                           : [self.conversationStyle quotingSelfHighlightColor]);
 }
 
diff --git a/Signal/src/ViewControllers/ConversationView/ConversationScrollButton.m b/Signal/src/ViewControllers/ConversationView/ConversationScrollButton.m
index 2b6ba024f..e06fe01a5 100644
--- a/Signal/src/ViewControllers/ConversationView/ConversationScrollButton.m
+++ b/Signal/src/ViewControllers/ConversationView/ConversationScrollButton.m
@@ -6,6 +6,7 @@
 #import "UIColor+OWS.h"
 #import "UIFont+OWS.h"
 #import "UIView+OWS.h"
+#import <SignalMessaging/Theme.h>
 
 NS_ASSUME_NONNULL_BEGIN
 
diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m
index addbfcda4..da8ef94a2 100644
--- a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m
+++ b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m
@@ -1342,75 +1342,25 @@ NS_ASSUME_NONNULL_BEGIN
                          hasCaption:YES],
 
         [self fakeOutgoingPngAction:thread
-                        actionLabel:@"Fake Outgoing 'Outgoing Unsent' Png"
+                        actionLabel:@"Fake Outgoing 'Outgoing' Png"
                           imageSize:CGSizeMake(200.f, 200.f)
-                    backgroundColor:[conversationStyle bubbleColorOutgoingFailed]
+                    backgroundColor:[conversationStyle bubbleColorWithIsIncoming:NO]
                           textColor:[UIColor whiteColor]
                          imageLabel:@"W"
                        messageState:TSOutgoingMessageStateFailed
                          hasCaption:YES],
         [self fakeOutgoingPngAction:thread
-                        actionLabel:@"Fake Outgoing 'Outgoing Unsent' Png"
+                        actionLabel:@"Fake Outgoing 'Outgoing' Png"
                           imageSize:CGSizeMake(200.f, 200.f)
-                    backgroundColor:[conversationStyle bubbleColorOutgoingFailed]
+                    backgroundColor:[conversationStyle bubbleColorWithIsIncoming:NO]
                           textColor:[UIColor whiteColor]
                          imageLabel:@"W"
                        messageState:TSOutgoingMessageStateSending
                          hasCaption:YES],
         [self fakeOutgoingPngAction:thread
-                        actionLabel:@"Fake Outgoing 'Outgoing Unsent' Png"
+                        actionLabel:@"Fake Outgoing 'Outgoing' Png"
                           imageSize:CGSizeMake(200.f, 200.f)
-                    backgroundColor:[conversationStyle bubbleColorOutgoingFailed]
-                          textColor:[UIColor whiteColor]
-                         imageLabel:@"W"
-                       messageState:TSOutgoingMessageStateSent
-                         hasCaption:YES],
-
-        [self fakeOutgoingPngAction:thread
-                        actionLabel:@"Fake Outgoing 'Outgoing Sending' Png"
-                          imageSize:CGSizeMake(200.f, 200.f)
-                    backgroundColor:[conversationStyle bubbleColorOutgoingSending]
-                          textColor:[UIColor whiteColor]
-                         imageLabel:@"W"
-                       messageState:TSOutgoingMessageStateFailed
-                         hasCaption:YES],
-        [self fakeOutgoingPngAction:thread
-                        actionLabel:@"Fake Outgoing 'Outgoing Sending' Png"
-                          imageSize:CGSizeMake(200.f, 200.f)
-                    backgroundColor:[conversationStyle bubbleColorOutgoingSending]
-                          textColor:[UIColor whiteColor]
-                         imageLabel:@"W"
-                       messageState:TSOutgoingMessageStateSending
-                         hasCaption:YES],
-        [self fakeOutgoingPngAction:thread
-                        actionLabel:@"Fake Outgoing 'Outgoing Sending' Png"
-                          imageSize:CGSizeMake(200.f, 200.f)
-                    backgroundColor:[conversationStyle bubbleColorOutgoingSending]
-                          textColor:[UIColor whiteColor]
-                         imageLabel:@"W"
-                       messageState:TSOutgoingMessageStateSent
-                         hasCaption:YES],
-
-        [self fakeOutgoingPngAction:thread
-                        actionLabel:@"Fake Outgoing 'Outgoing Sent' Png"
-                          imageSize:CGSizeMake(200.f, 200.f)
-                    backgroundColor:[conversationStyle bubbleColorOutgoingSent]
-                          textColor:[UIColor whiteColor]
-                         imageLabel:@"W"
-                       messageState:TSOutgoingMessageStateFailed
-                         hasCaption:YES],
-        [self fakeOutgoingPngAction:thread
-                        actionLabel:@"Fake Outgoing 'Outgoing Sent' Png"
-                          imageSize:CGSizeMake(200.f, 200.f)
-                    backgroundColor:[conversationStyle bubbleColorOutgoingSent]
-                          textColor:[UIColor whiteColor]
-                         imageLabel:@"W"
-                       messageState:TSOutgoingMessageStateSending
-                         hasCaption:YES],
-        [self fakeOutgoingPngAction:thread
-                        actionLabel:@"Fake Outgoing 'Outgoing Sent' Png"
-                          imageSize:CGSizeMake(200.f, 200.f)
-                    backgroundColor:[conversationStyle bubbleColorOutgoingSent]
+                    backgroundColor:[conversationStyle bubbleColorWithIsIncoming:NO]
                           textColor:[UIColor whiteColor]
                          imageLabel:@"W"
                        messageState:TSOutgoingMessageStateSent
@@ -1578,7 +1528,15 @@ NS_ASSUME_NONNULL_BEGIN
         [self fakeIncomingPngAction:thread
                         actionLabel:@"Fake Incoming 'Incoming' Png"
                           imageSize:CGSizeMake(200.f, 200.f)
-                    backgroundColor:[conversationStyle primaryColor]
+                    backgroundColor:[conversationStyle conversationColors].defaultColor
+                          textColor:[UIColor whiteColor]
+                         imageLabel:@"W"
+             isAttachmentDownloaded:YES
+                         hasCaption:YES],
+        [self fakeIncomingPngAction:thread
+                        actionLabel:@"Fake Incoming 'Incoming' Png"
+                          imageSize:CGSizeMake(200.f, 200.f)
+                    backgroundColor:[conversationStyle conversationColors].shadeColor
                           textColor:[UIColor whiteColor]
                          imageLabel:@"W"
              isAttachmentDownloaded:YES
@@ -1586,7 +1544,15 @@ NS_ASSUME_NONNULL_BEGIN
         [self fakeIncomingPngAction:thread
                         actionLabel:@"Fake Incoming 'Incoming' Png"
                           imageSize:CGSizeMake(200.f, 200.f)
-                    backgroundColor:[conversationStyle primaryColor]
+                    backgroundColor:[conversationStyle conversationColors].defaultColor
+                          textColor:[UIColor whiteColor]
+                         imageLabel:@"W"
+             isAttachmentDownloaded:NO
+                         hasCaption:YES],
+        [self fakeIncomingPngAction:thread
+                        actionLabel:@"Fake Incoming 'Incoming' Png"
+                          imageSize:CGSizeMake(200.f, 200.f)
+                    backgroundColor:[conversationStyle conversationColors].shadeColor
                           textColor:[UIColor whiteColor]
                          imageLabel:@"W"
              isAttachmentDownloaded:NO
diff --git a/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m b/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m
index 4e03947a7..ca66b3bf7 100644
--- a/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m
+++ b/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m
@@ -288,11 +288,8 @@ const CGFloat kIconViewLength = 24;
     [mainSection addItem:[OWSTableItem
                              itemWithCustomCellBlock:^{
                                  NSString *colorName = self.thread.conversationColorName;
-                                 UIColor *currentColor = [UIColor
-                                     ows_conversationColorForColorName:colorName
-                                                                  mode:(Theme.isDarkThemeEnabled
-                                                                               ? ConversationColorMode_Shade
-                                                                               : ConversationColorMode_Default)];
+                                 UIColor *currentColor =
+                                     [UIColor ows_conversationColorsForColorName:colorName].themeColor;
                                  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/ViewControllers/CountryCodeViewController.m b/SignalMessaging/ViewControllers/CountryCodeViewController.m
index e5a7b7f56..a0690a1e9 100644
--- a/SignalMessaging/ViewControllers/CountryCodeViewController.m
+++ b/SignalMessaging/ViewControllers/CountryCodeViewController.m
@@ -5,6 +5,7 @@
 #import "CountryCodeViewController.h"
 #import "OWSSearchBar.h"
 #import "PhoneNumberUtil.h"
+#import "Theme.h"
 #import "UIColor+OWS.h"
 #import "UIFont+OWS.h"
 #import "UIView+OWS.h"
diff --git a/SignalMessaging/ViewControllers/OWSTableViewController.m b/SignalMessaging/ViewControllers/OWSTableViewController.m
index 52d1840bd..f4aeb07fb 100644
--- a/SignalMessaging/ViewControllers/OWSTableViewController.m
+++ b/SignalMessaging/ViewControllers/OWSTableViewController.m
@@ -4,6 +4,7 @@
 
 #import "OWSTableViewController.h"
 #import "OWSNavigationController.h"
+#import "Theme.h"
 #import "UIColor+OWS.h"
 #import "UIFont+OWS.h"
 #import "UIView+OWS.h"
diff --git a/SignalMessaging/categories/Theme.h b/SignalMessaging/categories/Theme.h
index 14b210134..e1482cf35 100644
--- a/SignalMessaging/categories/Theme.h
+++ b/SignalMessaging/categories/Theme.h
@@ -2,6 +2,7 @@
 //  Copyright (c) 2018 Open Whisper Systems. All rights reserved.
 //
 
+#import "UIColor+OWS.h"
 #import <UIKit/UIKit.h>
 
 NS_ASSUME_NONNULL_BEGIN
@@ -56,4 +57,12 @@ extern NSString *const ThemeDidChangeNotification;
 
 @end
 
+#pragma mark -
+
+@interface OWSConversationColors (Theme)
+
+@property (nonatomic, readonly) UIColor *themeColor;
+
+@end
+
 NS_ASSUME_NONNULL_END
diff --git a/SignalMessaging/categories/Theme.m b/SignalMessaging/categories/Theme.m
index 453495cf6..1a5b29fd6 100644
--- a/SignalMessaging/categories/Theme.m
+++ b/SignalMessaging/categories/Theme.m
@@ -162,4 +162,15 @@ NSString *const ThemeKeyThemeEnabled = @"ThemeKeyThemeEnabled";
 
 @end
 
+#pragma mark -
+
+@implementation OWSConversationColors (Theme)
+
+- (UIColor *)themeColor
+{
+    return Theme.isDarkThemeEnabled ? self.shadeColor : self.defaultColor;
+}
+
+@end
+
 NS_ASSUME_NONNULL_END
diff --git a/SignalMessaging/categories/UIColor+OWS.h b/SignalMessaging/categories/UIColor+OWS.h
index 78ec2fcaf..f5e6910f9 100644
--- a/SignalMessaging/categories/UIColor+OWS.h
+++ b/SignalMessaging/categories/UIColor+OWS.h
@@ -2,16 +2,23 @@
 //  Copyright (c) 2018 Open Whisper Systems. All rights reserved.
 //
 
-#import "Theme.h"
 #import <UIKit/UIKit.h>
 
 NS_ASSUME_NONNULL_BEGIN
 
-typedef NS_ENUM(NSUInteger, ConversationColorMode) {
-    ConversationColorMode_Default,
-    ConversationColorMode_Shade,
-    ConversationColorMode_Tint,
-};
+@interface OWSConversationColors : NSObject
+
+@property (nonatomic, readonly) UIColor *defaultColor;
+@property (nonatomic, readonly) UIColor *shadeColor;
+@property (nonatomic, readonly) UIColor *tintColor;
+
++ (OWSConversationColors *)conversationColorsWithDefaultColor:(UIColor *)defaultColor
+                                                   shadeColor:(UIColor *)shadeColor
+                                                    tintColor:(UIColor *)tintColor;
+
+@end
+
+#pragma mark -
 
 @interface UIColor (OWS)
 
@@ -102,15 +109,18 @@ typedef NS_ENUM(NSUInteger, ConversationColorMode) {
 
 #pragma mark - Conversation Colors
 
-+ (nullable UIColor *)ows_conversationColorForColorName:(NSString *)colorName
-                                                   mode:(ConversationColorMode)mode
-    NS_SWIFT_NAME(ows_conversationColor(colorName:mode:));
++ (nullable OWSConversationColors *)ows_conversationColorsForColorName:(NSString *)colorName
+    NS_SWIFT_NAME(ows_conversationColors(colorName:));
 
-@property (class, readonly, nonatomic) NSArray<NSString *> *ows_conversationColorNames;
+// If the conversation color name is valid, return its colors.
+// Otherwise return the "default" conversation colors.
++ (OWSConversationColors *)ows_conversationColorsOrDefaultForColorName:(NSString *)conversationColorName
+    NS_SWIFT_NAME(ows_conversationColorsOrDefault(colorName:));
 
-+ (nullable UIColor *)ows_conversationTintColorForColorName:(NSString *)colorName;
+@property (class, readonly, nonatomic) NSArray<NSString *> *ows_conversationColorNames;
 
 + (NSString *)ows_defaultConversationColorName;
++ (OWSConversationColors *)ows_defaultConversationColors;
 
 // TODO: Remove
 @property (class, readonly, nonatomic) UIColor *ows_darkSkyBlueColor;
diff --git a/SignalMessaging/categories/UIColor+OWS.m b/SignalMessaging/categories/UIColor+OWS.m
index 9ff0a247a..9082aab79 100644
--- a/SignalMessaging/categories/UIColor+OWS.m
+++ b/SignalMessaging/categories/UIColor+OWS.m
@@ -2,12 +2,41 @@
 //  Copyright (c) 2018 Open Whisper Systems. All rights reserved.
 //
 
-#import "UIColor+OWS.h"
 #import "OWSMath.h"
+#import "Theme.h"
+#import "UIColor+OWS.h"
 #import <SignalServiceKit/Cryptography.h>
 
 NS_ASSUME_NONNULL_BEGIN
 
+
+@interface OWSConversationColors ()
+
+@property (nonatomic) UIColor *defaultColor;
+@property (nonatomic) UIColor *shadeColor;
+@property (nonatomic) UIColor *tintColor;
+
+@end
+
+#pragma mark -
+
+@implementation OWSConversationColors
+
++ (OWSConversationColors *)conversationColorsWithDefaultColor:(UIColor *)defaultColor
+                                                   shadeColor:(UIColor *)shadeColor
+                                                    tintColor:(UIColor *)tintColor
+{
+    OWSConversationColors *instance = [OWSConversationColors new];
+    instance.defaultColor = defaultColor;
+    instance.shadeColor = shadeColor;
+    instance.tintColor = tintColor;
+    return instance;
+}
+
+@end
+
+#pragma mark -
+
 @implementation UIColor (OWS)
 
 #pragma mark -
@@ -459,30 +488,42 @@ NS_ASSUME_NONNULL_BEGIN
     return self.ows_conversationColorMap.allKeys;
 }
 
-+ (nullable UIColor *)ows_conversationColorForColorName:(NSString *)colorName mode:(ConversationColorMode)mode
++ (nullable OWSConversationColors *)ows_conversationColorsForColorName:(NSString *)conversationColorName
 {
-    OWSAssertDebug(colorName.length > 0);
-
-    switch (mode) {
-        case ConversationColorMode_Default:
-            return self.ows_conversationColorMap[colorName];
-        case ConversationColorMode_Shade:
-            return self.ows_conversationColorMapShade[colorName];
-        case ConversationColorMode_Tint:
-            return self.ows_conversationColorMapTint[colorName];
+    UIColor *_Nullable defaultColor = self.ows_conversationColorMap[conversationColorName];
+    UIColor *_Nullable shadeColor = self.ows_conversationColorMapShade[conversationColorName];
+    UIColor *_Nullable tintColor = self.ows_conversationColorMapTint[conversationColorName];
+    if (!defaultColor || !shadeColor || !tintColor) {
+        return nil;
     }
+    OWSAssertDebug(defaultColor);
+    OWSAssertDebug(shadeColor);
+    OWSAssertDebug(tintColor);
+    return [OWSConversationColors conversationColorsWithDefaultColor:defaultColor
+                                                          shadeColor:shadeColor
+                                                           tintColor:tintColor];
 }
 
-+ (nullable UIColor *)ows_conversationTintColorForColorName:(NSString *)colorName
++ (OWSConversationColors *)ows_conversationColorsOrDefaultForColorName:(NSString *)conversationColorName
 {
-    OWSAssertDebug(colorName.length > 0);
-
-    return self.ows_conversationColorMapTint[colorName];
+    OWSConversationColors *_Nullable conversationColors =
+        [self ows_conversationColorsForColorName:conversationColorName];
+    if (conversationColors) {
+        return conversationColors;
+    }
+    return [self ows_defaultConversationColors];
 }
 
 + (NSString *)ows_defaultConversationColorName
 {
-    return @"teal";
+    NSString *conversationColorName = @"teal";
+    OWSAssert([self.ows_conversationColorNames containsObject:conversationColorName]);
+    return conversationColorName;
+}
+
++ (OWSConversationColors *)ows_defaultConversationColors
+{
+    return [self ows_conversationColorsForColorName:self.ows_defaultConversationColorName];
 }
 
 // TODO: Remove
diff --git a/SignalMessaging/categories/UIViewController+OWS.m b/SignalMessaging/categories/UIViewController+OWS.m
index 79d7bffea..3e8279ddb 100644
--- a/SignalMessaging/categories/UIViewController+OWS.m
+++ b/SignalMessaging/categories/UIViewController+OWS.m
@@ -2,9 +2,10 @@
 //  Copyright (c) 2018 Open Whisper Systems. All rights reserved.
 //
 
-#import "UIViewController+OWS.h"
+#import "Theme.h"
 #import "UIColor+OWS.h"
 #import "UIView+OWS.h"
+#import "UIViewController+OWS.h"
 #import <SignalServiceKit/AppContext.h>
 #import <SignalServiceKit/iOSVersions.h>
 
diff --git a/SignalMessaging/utils/ConversationStyle.swift b/SignalMessaging/utils/ConversationStyle.swift
index 964f6d92c..da42d199e 100644
--- a/SignalMessaging/utils/ConversationStyle.swift
+++ b/SignalMessaging/utils/ConversationStyle.swift
@@ -64,7 +64,7 @@ public class ConversationStyle: NSObject {
     public required init(thread: TSThread) {
 
         self.thread = thread
-        self.primaryColor = ConversationStyle.primaryColor(thread: thread)
+        self.conversationColors = ConversationStyle.conversationColors(thread: thread)
 
         super.init()
 
@@ -126,22 +126,18 @@ public class ConversationStyle: NSObject {
 
         lastTextLineAxis = CGFloat(round(baseFontOffset + messageTextFont.capHeight * 0.5))
 
-        self.primaryColor = ConversationStyle.primaryColor(thread: thread)
+        self.conversationColors = ConversationStyle.conversationColors(thread: thread)
     }
 
     // MARK: Colors
 
-    private class func primaryColor(thread: TSThread) -> UIColor {
-        guard let colorName = thread.conversationColorName else {
-            return self.defaultBubbleColorIncoming
-        }
+    @objc
+    public var conversationColors: OWSConversationColors
 
-        guard let color = UIColor.ows_conversationColor(colorName: colorName,
-                                                        mode: Theme.isDarkThemeEnabled ? .shade : .default) else {
-            return self.defaultBubbleColorIncoming
-        }
+    private class func conversationColors(thread: TSThread) -> OWSConversationColors {
+        let colorName = thread.conversationColorName
 
-        return color
+        return UIColor.ows_conversationColorsOrDefault(colorName: colorName)
     }
 
     @objc
@@ -149,37 +145,15 @@ public class ConversationStyle: NSObject {
         return Theme.isDarkThemeEnabled ? UIColor.ows_gray75 : UIColor.ows_messageBubbleLightGray
     }
 
-    @objc
-    public let bubbleColorOutgoingFailed = UIColor.ows_darkSkyBlue
-
-    @objc
-    public let bubbleColorOutgoingSending = UIColor.ows_darkSkyBlue
-
-    @objc
-    public let bubbleColorOutgoingSent = UIColor.ows_darkSkyBlue
-
     @objc
     public let dateBreakTextColor = UIColor.ows_gray60
 
-    @objc
-    public var primaryColor: UIColor
-
     @objc
     public func bubbleColor(message: TSMessage) -> UIColor {
         if message is TSIncomingMessage {
-            return ConversationStyle.defaultBubbleColorIncoming
-        } else if let outgoingMessage = message as? TSOutgoingMessage {
-            switch outgoingMessage.messageState {
-            case .failed:
-                return bubbleColorOutgoingFailed
-            case .sending:
-                return bubbleColorOutgoingSending
-            default:
-                return bubbleColorOutgoingSent
-            }
+            return bubbleColor(isIncoming: true)
         } else {
-            owsFailDebug("Unexpected message type: \(message)")
-            return bubbleColorOutgoingSent
+            return bubbleColor(isIncoming: false)
         }
     }
 
@@ -188,7 +162,7 @@ public class ConversationStyle: NSObject {
         if isIncoming {
             return ConversationStyle.defaultBubbleColorIncoming
         } else {
-            return self.bubbleColorOutgoingSent
+            return conversationColors.themeColor
         }
     }
 
@@ -229,10 +203,9 @@ public class ConversationStyle: NSObject {
     @objc
     public func quotedReplyBubbleColor(isIncoming: Bool) -> UIColor {
         if Theme.isDarkThemeEnabled {
-            let alpha: CGFloat = (isIncoming ? 0.6 :0.5)
-            return UIColor.white.blend(with: bubbleColorOutgoingSent, alpha: alpha)
+            return conversationColors.shadeColor
         } else if isIncoming {
-            return bubbleColorOutgoingSent.withAlphaComponent(0.25)
+            return conversationColors.tintColor
         } else {
             return ConversationStyle.defaultBubbleColorIncoming.withAlphaComponent(0.75)
         }
@@ -241,9 +214,9 @@ public class ConversationStyle: NSObject {
     @objc
     public func quotedReplyStripeColor(isIncoming: Bool) -> UIColor {
         if isIncoming {
-            return bubbleColorOutgoingSent
+            return conversationColors.themeColor
         } else {
-            return UIColor.white
+            return Theme.backgroundColor
         }
     }
 
diff --git a/SignalMessaging/utils/OWSAvatarBuilder.m b/SignalMessaging/utils/OWSAvatarBuilder.m
index 2e597948a..ecfaba7ca 100644
--- a/SignalMessaging/utils/OWSAvatarBuilder.m
+++ b/SignalMessaging/utils/OWSAvatarBuilder.m
@@ -7,6 +7,7 @@
 #import "OWSGroupAvatarBuilder.h"
 #import "TSContactThread.h"
 #import "TSGroupThread.h"
+#import "Theme.h"
 #import "UIColor+OWS.h"
 #import "UIFont+OWS.h"
 #import "UIView+OWS.h"
diff --git a/SignalMessaging/utils/OWSContactAvatarBuilder.m b/SignalMessaging/utils/OWSContactAvatarBuilder.m
index c6037d343..2f559ecd4 100644
--- a/SignalMessaging/utils/OWSContactAvatarBuilder.m
+++ b/SignalMessaging/utils/OWSContactAvatarBuilder.m
@@ -124,10 +124,7 @@ NS_ASSUME_NONNULL_BEGIN
         [initials appendString:@"#"];
     }
 
-    UIColor *color =
-        [UIColor ows_conversationColorForColorName:self.colorName
-                                              mode:(Theme.isDarkThemeEnabled ? ConversationColorMode_Shade
-                                                                             : ConversationColorMode_Default)];
+    UIColor *color = [UIColor ows_conversationColorsOrDefaultForColorName:self.colorName].themeColor;
     OWSAssertDebug(color);
 
     UIImage *_Nullable image =
diff --git a/SignalMessaging/utils/UIUtil.m b/SignalMessaging/utils/UIUtil.m
index c925d9625..464cd83d3 100644
--- a/SignalMessaging/utils/UIUtil.m
+++ b/SignalMessaging/utils/UIUtil.m
@@ -3,6 +3,7 @@
 //
 
 #import "UIUtil.h"
+#import "Theme.h"
 #import "UIColor+OWS.h"
 #import <SignalServiceKit/AppContext.h>
 
diff --git a/SignalServiceKit/src/Contacts/TSThread.h b/SignalServiceKit/src/Contacts/TSThread.h
index 0ebcb0723..207d5c9c8 100644
--- a/SignalServiceKit/src/Contacts/TSThread.h
+++ b/SignalServiceKit/src/Contacts/TSThread.h
@@ -33,7 +33,7 @@ NS_ASSUME_NONNULL_BEGIN
  */
 - (NSString *)name;
 
-@property (readonly, nullable) NSString *conversationColorName;
+@property (nonatomic, readonly) NSString *conversationColorName;
 
 - (void)updateConversationColorName:(NSString *)colorName transaction:(YapDatabaseReadWriteTransaction *)transaction;
 + (NSString *)stableConversationColorNameForString:(NSString *)colorSeed;
diff --git a/SignalServiceKit/src/Contacts/TSThread.m b/SignalServiceKit/src/Contacts/TSThread.m
index 56daa1f89..24b1651e8 100644
--- a/SignalServiceKit/src/Contacts/TSThread.m
+++ b/SignalServiceKit/src/Contacts/TSThread.m
@@ -23,7 +23,7 @@ NS_ASSUME_NONNULL_BEGIN
 
 @property (nonatomic) NSDate *creationDate;
 @property (nonatomic, copy, nullable) NSDate *archivalDate;
-@property (nonatomic, nullable) NSString *conversationColorName;
+@property (nonatomic) NSString *conversationColorName;
 @property (nonatomic, nullable) NSDate *lastMessageDate;
 @property (nonatomic, copy, nullable) NSString *messageDraft;
 @property (atomic, nullable) NSDate *mutedUntilDate;