// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. // import Foundation @objc public class ConversationStyle: NSObject { private let thread: TSThread // The width of the collection view. @objc public var viewWidth: CGFloat = 0 { didSet { AssertIsOnMainThread() updateProperties() } } @objc public let contentMarginTop: CGFloat = Values.largeSpacing @objc public let contentMarginBottom: CGFloat = Values.largeSpacing @objc public var gutterLeading: CGFloat = 0 @objc public var gutterTrailing: CGFloat = 0 @objc public var headerGutterLeading: CGFloat = Values.veryLargeSpacing @objc public var headerGutterTrailing: CGFloat = Values.veryLargeSpacing // These are the gutters used by "full width" views // like "contact offer" and "info message". @objc public var fullWidthGutterLeading: CGFloat = 0 @objc public var fullWidthGutterTrailing: CGFloat = 0 @objc public var errorGutterTrailing: CGFloat = 0 @objc public var contentWidth: CGFloat { return viewWidth - (gutterLeading + gutterTrailing) } @objc public var fullWidthContentWidth: CGFloat { return viewWidth - (fullWidthGutterLeading + fullWidthGutterTrailing) } @objc public var headerViewContentWidth: CGFloat { return viewWidth - (headerGutterLeading + headerGutterTrailing) } @objc public var maxMessageWidth: CGFloat = 0 @objc public var textInsetTop: CGFloat = 0 @objc public var textInsetBottom: CGFloat = 0 @objc public var textInsetHorizontal: CGFloat = 0 // We want to align "group sender" avatars with the v-center of the // "last line" of the message body text - or where it would be for // non-text content. // // This is the distance from that v-center to the bottom of the // message bubble. @objc public var lastTextLineAxis: CGFloat = 0 @objc public required init(thread: TSThread) { self.thread = thread self.conversationColor = ConversationStyle.conversationColor(thread: thread) super.init() updateProperties() NotificationCenter.default.addObserver(self, selector: #selector(uiContentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil) } deinit { NotificationCenter.default.removeObserver(self) } @objc func uiContentSizeCategoryDidChange() { AssertIsOnMainThread() updateProperties() } // MARK: - @objc public func updateProperties() { if thread.isGroupThread() { if let thread = thread as? TSGroupThread, thread.isRSSFeed { gutterLeading = 16 gutterTrailing = 16 } else { gutterLeading = 12 + 35 + 12 // 12 + Values.smallProfilePictureSize + 12 gutterTrailing = 16 } } else { gutterLeading = 16 gutterTrailing = 16 } fullWidthGutterLeading = 16 fullWidthGutterTrailing = 16 headerGutterLeading = 16 headerGutterTrailing = 16 errorGutterTrailing = 16 if let thread = thread as? TSGroupThread, thread.isRSSFeed { maxMessageWidth = floor(contentWidth) } else { maxMessageWidth = floor(contentWidth - 32) } let messageTextFont = UIFont.systemFont(ofSize: Values.smallFontSize) let baseFontOffset: CGFloat = 16 // Don't include the distance from the "cap height" to the top of the UILabel // in the top margin. textInsetTop = max(0, round(baseFontOffset - (messageTextFont.ascender - messageTextFont.capHeight))) // Don't include the distance from the "baseline" to the bottom of the UILabel // (e.g. the descender) in the top margin. Note that UIFont.descender is a // negative value. textInsetBottom = max(0, round(baseFontOffset - abs(messageTextFont.descender))) textInsetHorizontal = 16 lastTextLineAxis = CGFloat(round(baseFontOffset + messageTextFont.capHeight * 0.5)) self.conversationColor = ConversationStyle.conversationColor(thread: thread) } // MARK: Colors @objc public var conversationColor: OWSConversationColor private class func conversationColor(thread: TSThread) -> OWSConversationColor { let colorName = thread.conversationColorName return OWSConversationColor.conversationColorOrDefault(colorName: colorName) } @objc private static var defaultBubbleColorIncoming: UIColor { return Colors.receivedMessageBackground } @objc public let bubbleColorOutgoingFailed = Colors.sentMessageBackground @objc public let bubbleColorOutgoingSending = Colors.sentMessageBackground @objc public let bubbleColorOutgoingSent = Colors.sentMessageBackground @objc public let dateBreakTextColor = UIColor.ows_gray60 @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 } } else { owsFailDebug("Unexpected message type: \(message)") return bubbleColorOutgoingSent } } @objc public func bubbleColor(isIncoming: Bool) -> UIColor { if isIncoming { return ConversationStyle.defaultBubbleColorIncoming } else { return self.bubbleColorOutgoingSent } } @objc public static var bubbleTextColorIncoming: UIColor { return Colors.text } @objc public static var bubbleTextColorOutgoing: UIColor { return Colors.text } @objc public func bubbleTextColor(message: TSMessage) -> UIColor { if message is TSIncomingMessage { return ConversationStyle.bubbleTextColorIncoming } else if message is TSOutgoingMessage { return ConversationStyle.bubbleTextColorOutgoing } else { owsFailDebug("Unexpected message type: \(message)") return ConversationStyle.bubbleTextColorOutgoing } } @objc public func bubbleTextColor(isIncoming: Bool) -> UIColor { if isIncoming { return ConversationStyle.bubbleTextColorIncoming } else { return ConversationStyle.bubbleTextColorOutgoing } } @objc public func bubbleSecondaryTextColor(isIncoming: Bool) -> UIColor { return bubbleTextColor(isIncoming: isIncoming).withAlphaComponent(Values.unimportantElementOpacity) } @objc public func quotedReplyBubbleColor(isIncoming: Bool) -> UIColor { if isIncoming { return Colors.sentMessageBackground } else { return Colors.receivedMessageBackground } } @objc public func quotedReplyStripeColor(isIncoming: Bool) -> UIColor { return isLightMode ? UIColor(hex: 0x272726) : Colors.accent } @objc public func quotingSelfHighlightColor() -> UIColor { return UIColor.init(rgbHex: 0xB5B5B5) } @objc public func quotedReplyAuthorColor() -> UIColor { return Colors.text } @objc public func quotedReplyTextColor() -> UIColor { return Colors.text } @objc public func quotedReplyAttachmentColor() -> UIColor { return Colors.text } }