diff --git a/WordPress/Classes/Extensions/NSAttributedString+Helpers.swift b/WordPress/Classes/Extensions/NSAttributedString+Helpers.swift new file mode 100644 index 000000000000..8a0f982ca63d --- /dev/null +++ b/WordPress/Classes/Extensions/NSAttributedString+Helpers.swift @@ -0,0 +1,44 @@ +import Foundation + + +extension NSAttributedString +{ + /** + Note: + This method will embed a collection of assets, in the specified NSRange's. Since NSRange is an ObjC struct, + you'll need to wrap it up into a NSValue instance! + */ + public func stringByEmbeddingImageAttachments(embeds: [NSValue: UIImage]?) -> NSAttributedString { + // Allow nil embeds: behave as a simple NO-OP + if embeds == nil { + return self + } + + // Proceed embedding! + let unwrappedEmbeds = embeds! + let theString = self.mutableCopy() as NSMutableAttributedString + var rangeDelta = 0 + + for (value, image) in unwrappedEmbeds { + let imageAttachment = NSTextAttachment() + imageAttachment.bounds = CGRect(origin: CGPointZero, size: image.size) + imageAttachment.image = image + + // Each embed is expected to add 1 char to the string. Compensate for that + let attachmentString = NSAttributedString(attachment: imageAttachment) + var correctedRange = value.rangeValue + correctedRange.location += rangeDelta + + // Bounds Safety + let lastPosition = correctedRange.location + correctedRange.length + if lastPosition <= theString.length { + theString.replaceCharactersInRange(correctedRange, withAttributedString: attachmentString) + } + + rangeDelta += attachmentString.length + + } + + return theString + } +} diff --git a/WordPress/Classes/Extensions/NSParagraphStyle+Helpers.swift b/WordPress/Classes/Extensions/NSParagraphStyle+Helpers.swift index f9c2fd419cb1..efccda6515d4 100644 --- a/WordPress/Classes/Extensions/NSParagraphStyle+Helpers.swift +++ b/WordPress/Classes/Extensions/NSParagraphStyle+Helpers.swift @@ -3,11 +3,15 @@ import Foundation extension NSMutableParagraphStyle { - convenience init(minLineHeight: CGFloat, maxLineHeight: CGFloat, lineBreakMode: NSLineBreakMode, alignment: NSTextAlignment) { + convenience init(minLineHeight: CGFloat, lineBreakMode: NSLineBreakMode, alignment: NSTextAlignment) { self.init() self.minimumLineHeight = minLineHeight - self.maximumLineHeight = maxLineHeight self.lineBreakMode = lineBreakMode self.alignment = alignment } + + convenience init(minLineHeight: CGFloat, maxLineHeight: CGFloat, lineBreakMode: NSLineBreakMode, alignment: NSTextAlignment) { + self.init(minLineHeight: minLineHeight, lineBreakMode: lineBreakMode, alignment: alignment) + self.maximumLineHeight = maxLineHeight + } } diff --git a/WordPress/Classes/Extensions/Notifications/NotificationBlock+Interface.swift b/WordPress/Classes/Extensions/Notifications/NotificationBlock+Interface.swift index b77cc7e00026..6302939436b0 100644 --- a/WordPress/Classes/Extensions/Notifications/NotificationBlock+Interface.swift +++ b/WordPress/Classes/Extensions/Notifications/NotificationBlock+Interface.swift @@ -4,30 +4,7 @@ import Foundation extension NotificationBlock { public func subjectAttributedText() -> NSAttributedString { - if text == nil { - return NSAttributedString() - } - - let theString = NSMutableAttributedString(string: text, attributes: WPStyleGuide.Notifications.subjectRegularStyle) - - theString.applyAttributesToQuotes(WPStyleGuide.Notifications.subjectItalicsStyle) - - for notificationRange in ranges as [NotificationRange] { - - // Make sure this range is not of bounds! - let range = notificationRange.range - if range.location + range.length > theString.length { - continue - } - - if notificationRange.isUser { - theString.addAttributes(WPStyleGuide.Notifications.subjectBoldStyle, range: range) - } else if notificationRange.isPost || notificationRange.isComment { - theString.addAttributes(WPStyleGuide.Notifications.subjectItalicsStyle, range: range) - } - } - - return theString; + return textWithRangeStyles(isSubject: true) } public func snippetAttributedText() -> NSAttributedString { @@ -35,40 +12,77 @@ extension NotificationBlock return NSAttributedString() } - return NSMutableAttributedString(string: text, attributes: WPStyleGuide.Notifications.snippetRegularStyle) + return NSAttributedString(string: text, attributes: Styles.snippetRegularStyle) } - public func regularAttributedText() -> NSAttributedString { + public func richAttributedText() -> NSAttributedString { + // Operations such as editing a comment cause a lag between the REST and Simperium update. + // TextOverride is a transient property meant to store, temporarily, the edited text + if textOverride != nil { + return NSAttributedString(string: textOverride, attributes: Styles.blockRegularStyle) + } + + return textWithRangeStyles(isSubject: false) + } + + public func buildRangesToImagesMap(mediaMap: [NSURL: UIImage]?) -> [NSValue: UIImage]? { + // If we've got a text override: Ranges may not match, and the new text may not even contain ranges! + if mediaMap == nil || textOverride != nil { + return nil + } + + var ranges = [NSValue: UIImage]() + + for theMedia in media as [NotificationMedia] { + if let image = mediaMap![theMedia.mediaURL] { + let rangeValue = NSValue(range: theMedia.range) + ranges[rangeValue] = image + } + } + + return ranges + } + + + // MARK: - Private Helpers + private func textWithRangeStyles(#isSubject: Bool) -> NSAttributedString { if text == nil { return NSAttributedString() } - let theString = NSMutableAttributedString(string: text, attributes: WPStyleGuide.Notifications.blockRegularStyle) + // Setup the styles + let regularStyle = isSubject ? Styles.subjectRegularStyle : (isBadge ? Styles.blockBadgeStyle : Styles.blockRegularStyle) + let quotesStyle = isSubject ? Styles.subjectItalicsStyle : Styles.blockBoldStyle + let userStyle = isSubject ? Styles.subjectBoldStyle : Styles.blockBoldStyle + let postStyle = isSubject ? Styles.subjectItalicsStyle : Styles.blockItalicsStyle + let commentStyle = postStyle + let blockStyle = Styles.blockQuotedStyle - theString.applyAttributesToQuotes(WPStyleGuide.Notifications.blockBoldStyle) + // Format the String + let theString = NSMutableAttributedString(string: text, attributes: regularStyle) + theString.applyAttributesToQuotes(quotesStyle) for range in ranges as [NotificationRange] { - if range.isPost { - theString.addAttributes(WPStyleGuide.Notifications.blockItalicsStyle, range: range.range) + if range.isUser { + theString.addAttributes(userStyle, range: range.range) + } else if range.isPost { + theString.addAttributes(postStyle, range: range.range) + } else if range.isComment { + theString.addAttributes(commentStyle, range: range.range) } else if range.isBlockquote { - theString.addAttributes(WPStyleGuide.Notifications.blockQuotedStyle, range: range.range) + theString.addAttributes(blockStyle, range: range.range) } - if range.url != nil { + // Don't Highlight Links in the subject + if isSubject == false && range.url != nil { theString.addAttribute(NSLinkAttributeName, value: range.url, range: range.range) - theString.addAttribute(NSForegroundColorAttributeName, value: WPStyleGuide.Notifications.blockLinkColor, range: range.range) + theString.addAttribute(NSForegroundColorAttributeName, value: Styles.blockLinkColor, range: range.range) } } return theString } - - public func regularAttributedTextOverride() -> NSAttributedString? { - // Operations such as editing a comment cause a lag between the REST and Simperium update. - // TextOverride is a transient property meant to store, temporarily, the edited text - if textOverride != nil { - return NSAttributedString(string: textOverride, attributes: WPStyleGuide.Notifications.blockRegularStyle) - } - return nil - } + + private typealias Styles = WPStyleGuide.Notifications } + diff --git a/WordPress/Classes/Models/Notifications/Notification.h b/WordPress/Classes/Models/Notifications/Notification.h index 47bc7b9a9ca8..52f0f0cd7d27 100644 --- a/WordPress/Classes/Models/Notifications/Notification.h +++ b/WordPress/Classes/Models/Notifications/Notification.h @@ -19,10 +19,12 @@ extern NSString * NoteActionReplyKey; extern NSString * NoteActionApproveKey; extern NSString * NoteActionEditKey; +extern NSString * NoteMediaTypeImage; + typedef NS_ENUM(NSInteger, NoteBlockType) { NoteBlockTypeText, - NoteBlockTypeImage, // BlockTypesImage: Includes Badges and Images + NoteBlockTypeImage, // BlockTypesImage: Includes Badges and Images NoteBlockTypeUser, NoteBlockTypeComment }; @@ -32,9 +34,9 @@ typedef NS_ENUM(NSInteger, NoteBlockGroupType) NoteBlockGroupTypeText = NoteBlockTypeText, NoteBlockGroupTypeImage = NoteBlockTypeImage, NoteBlockGroupTypeUser = NoteBlockTypeUser, - NoteBlockGroupTypeComment = NoteBlockTypeComment, // Contains a User + Comment Block - NoteBlockGroupTypeSubject = 20, // Contains a User + Text Block - NoteBlockGroupTypeHeader = 30 // Contains a User + Text Block + NoteBlockGroupTypeComment = NoteBlockTypeComment, // Contains a User + Comment Block + NoteBlockGroupTypeSubject = 20, // Contains a User + Text Block + NoteBlockGroupTypeHeader = 30 // Contains a User + Text Block }; @@ -94,6 +96,7 @@ typedef NS_ENUM(NSInteger, NoteBlockGroupType) @property (nonatomic, assign, readonly) NoteBlockGroupType type; - (NotificationBlock *)blockOfType:(NoteBlockType)type; +- (NSSet *)imageUrlsForBlocksOfTypes:(NSSet *)types; @end @@ -111,17 +114,18 @@ typedef NS_ENUM(NSInteger, NoteBlockGroupType) @property (nonatomic, strong, readonly) NSDictionary *actions; // Derived Properties -@property (nonatomic, assign, readonly) NoteBlockType type; +@property (nonatomic, assign, readonly) NoteBlockType type; +@property (nonatomic, assign, readonly) BOOL isBadge; @property (nonatomic, strong, readonly) NSNumber *metaSiteID; @property (nonatomic, strong, readonly) NSNumber *metaCommentID; @property (nonatomic, strong, readonly) NSString *metaLinksHome; @property (nonatomic, strong, readonly) NSString *metaTitlesHome; -@property (nonatomic, strong, readonly) NSString *metaTitleOrUrl; // Overrides @property (nonatomic, strong, readwrite) NSString *textOverride; - (NotificationRange *)notificationRangeWithUrl:(NSURL *)url; +- (NSArray *)imageUrls; - (void)setActionOverrideValue:(NSNumber *)obj forKey:(NSString *)key; - (void)removeActionOverrideForKey:(NSString *)key; diff --git a/WordPress/Classes/Models/Notifications/Notification.m b/WordPress/Classes/Models/Notifications/Notification.m index ca70f515bbad..10d8052f6ef5 100644 --- a/WordPress/Classes/Models/Notifications/Notification.m +++ b/WordPress/Classes/Models/Notifications/Notification.m @@ -201,7 +201,8 @@ + (NSArray *)mediaFromArray:(NSArray *)rawMedia @interface NotificationBlock () @property (nonatomic, strong, readwrite) NSMutableDictionary *actionsOverride; -@property (nonatomic, assign, readwrite) NoteBlockType type; +@property (nonatomic, assign, readwrite) NoteBlockType type; +@property (nonatomic, assign, readwrite) BOOL isBadge; @end @@ -245,15 +246,6 @@ - (NSString *)metaTitlesHome return [[self.meta dictionaryForKey:NoteTitlesKey] stringForKey:NoteHomeKey]; } -- (NSString *)metaTitleOrUrl -{ - if ([self metaTitlesHome]) { - return [self metaTitlesHome]; - } - - return self.metaLinksHome.hostname; -} - - (NotificationRange *)notificationRangeWithUrl:(NSURL *)url { for (NotificationRange *range in self.ranges) { @@ -265,6 +257,19 @@ - (NotificationRange *)notificationRangeWithUrl:(NSURL *)url return nil; } +- (NSArray *)imageUrls +{ + NSMutableArray *urls = [NSMutableArray array]; + + for (NotificationMedia *media in self.media) { + if (media.isImage && media.mediaURL != nil) { + [urls addObject:media.mediaURL]; + } + } + + return urls; +} + - (void)setActionOverrideValue:(NSNumber *)value forKey:(NSString *)key { if (!_actionsOverride) { @@ -301,6 +306,7 @@ + (NSArray *)blocksFromArray:(NSArray *)rawBlocks notification:(Notification *)n } NSMutableArray *parsed = [NSMutableArray array]; + BOOL isBadge = false; for (NSDictionary *rawDict in rawBlocks) { if (![rawDict isKindOfClass:[NSDictionary class]]) { @@ -330,11 +336,24 @@ + (NSArray *)blocksFromArray:(NSArray *)rawBlocks notification:(Notification *)n } else { block.type = NoteBlockTypeText; } - + + // Figure out if this is a badge + for (NotificationMedia *media in block.media) { + if (media.isBadge) { + isBadge = true; + } + } [parsed addObject:block]; } + // Note: Seriously. Duck typing should be abolished. + if (isBadge) { + for (NotificationBlock *block in parsed) { + block.isBadge = true; + } + } + return parsed; } @@ -346,8 +365,8 @@ + (NSArray *)blocksFromArray:(NSArray *)rawBlocks notification:(Notification *)n #pragma mark ==================================================================================== @interface NotificationBlockGroup () -@property (nonatomic, strong) NSArray *blocks; -@property (nonatomic, assign) NoteBlockGroupType type; +@property (nonatomic, strong) NSArray *blocks; +@property (nonatomic, assign) NoteBlockGroupType type; @end @implementation NotificationBlockGroup @@ -362,6 +381,24 @@ - (NotificationBlock *)blockOfType:(NoteBlockType)type return nil; } +- (NSSet *)imageUrlsForBlocksOfTypes:(NSSet *)types +{ + NSMutableSet *urls = [NSMutableSet set]; + + for (NotificationBlock *block in self.blocks) { + if ([types containsObject:@(block.type)] == false) { + continue; + } + + NSArray *imageUrls = [block imageUrls]; + if (imageUrls) { + [urls addObjectsFromArray:imageUrls]; + } + } + + return urls; +} + + (NotificationBlockGroup *)groupWithBlocks:(NSArray *)blocks type:(NoteBlockGroupType)type { NotificationBlockGroup *group = [self new]; @@ -549,10 +586,8 @@ - (BOOL)isBadge // for (NotificationBlockGroup *group in self.bodyBlockGroups) { for (NotificationBlock *block in group.blocks) { - for (NotificationMedia *media in block.media) { - if (media.isBadge) { - return true; - } + if (block.isBadge) { + return true; } } } diff --git a/WordPress/Classes/System/WordPress-Bridging-Header.h b/WordPress/Classes/System/WordPress-Bridging-Header.h index 6a9d22829540..f328e492a4fb 100644 --- a/WordPress/Classes/System/WordPress-Bridging-Header.h +++ b/WordPress/Classes/System/WordPress-Bridging-Header.h @@ -1,5 +1,8 @@ +#import #import +#import + #import "Notification.h" #import "DDLogSwift.h" diff --git a/WordPress/Classes/Utility/WPDynamicHeightTextView.swift b/WordPress/Classes/Utility/WPDynamicHeightTextView.swift deleted file mode 100644 index 6f17a362118a..000000000000 --- a/WordPress/Classes/Utility/WPDynamicHeightTextView.swift +++ /dev/null @@ -1,22 +0,0 @@ -import Foundation - - -public class WPDynamicHeightTextView : UITextView -{ - public var preferredMaxLayoutWidth: CGFloat = 0 { - didSet { - invalidateIntrinsicContentSize() - } - } - - public override func intrinsicContentSize() -> CGSize { - // Fix: Let's add 1pt extra size. There are few scenarios in which text gets clipped by 1 point - let bottomPadding: CGFloat = 1 - let maxWidth = (preferredMaxLayoutWidth != 0) ? preferredMaxLayoutWidth : frame.width - let maxSize = CGSize(width: maxWidth, height: CGFloat.max) - let requiredSize = sizeThatFits(maxSize) - let roundedSize = CGSize(width: ceil(requiredSize.width), height: ceil(requiredSize.height) + bottomPadding) - - return roundedSize - } -} diff --git a/WordPress/Classes/ViewRelated/Comments/CommentTableViewCell.swift b/WordPress/Classes/ViewRelated/Comments/CommentTableViewCell.swift index 6fe2cb3e36c2..c44619e4fb54 100644 --- a/WordPress/Classes/ViewRelated/Comments/CommentTableViewCell.swift +++ b/WordPress/Classes/ViewRelated/Comments/CommentTableViewCell.swift @@ -17,7 +17,8 @@ import Foundation return } - super.attributedCommentText = NSMutableAttributedString(string: commentText!, attributes: WPStyleGuide.Notifications.blockRegularStyle) + let style = WPStyleGuide.Notifications.blockRegularStyle + super.attributedCommentText = NSMutableAttributedString(string: commentText!, attributes: style) } } } diff --git a/WordPress/Classes/ViewRelated/Comments/CommentTableViewCell.xib b/WordPress/Classes/ViewRelated/Comments/CommentTableViewCell.xib index 0e9829739b48..4aa9a6639e64 100644 --- a/WordPress/Classes/ViewRelated/Comments/CommentTableViewCell.xib +++ b/WordPress/Classes/ViewRelated/Comments/CommentTableViewCell.xib @@ -1,9 +1,8 @@ - + - @@ -41,7 +40,7 @@ - - + + + + @@ -162,18 +156,19 @@ + - - + + @@ -181,11 +176,11 @@ - + @@ -208,11 +203,11 @@ - + diff --git a/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationDetailsViewController.m b/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationDetailsViewController.m index 8817f1610db9..3db95aba7ade 100644 --- a/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationDetailsViewController.m +++ b/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationDetailsViewController.m @@ -68,6 +68,9 @@ @interface NotificationDetailsViewController () - + - @@ -120,7 +119,7 @@ - + @@ -138,7 +137,7 @@ - + @@ -146,10 +145,10 @@ - + - + @@ -171,8 +170,9 @@ - - - + + + + - + - - + + - + + @@ -313,14 +311,14 @@ - - - + + + @@ -332,11 +330,11 @@ - + diff --git a/WordPress/Classes/ViewRelated/Notifications/Tools/ReplyBezierView.swift b/WordPress/Classes/ViewRelated/Notifications/ReplyTextView/ReplyBezierView.swift similarity index 100% rename from WordPress/Classes/ViewRelated/Notifications/Tools/ReplyBezierView.swift rename to WordPress/Classes/ViewRelated/Notifications/ReplyTextView/ReplyBezierView.swift diff --git a/WordPress/Classes/ViewRelated/Notifications/Tools/ReplyTextView.swift b/WordPress/Classes/ViewRelated/Notifications/ReplyTextView/ReplyTextView.swift similarity index 100% rename from WordPress/Classes/ViewRelated/Notifications/Tools/ReplyTextView.swift rename to WordPress/Classes/ViewRelated/Notifications/ReplyTextView/ReplyTextView.swift diff --git a/WordPress/Classes/ViewRelated/Notifications/Tools/ReplyTextView.xib b/WordPress/Classes/ViewRelated/Notifications/ReplyTextView/ReplyTextView.xib similarity index 100% rename from WordPress/Classes/ViewRelated/Notifications/Tools/ReplyTextView.xib rename to WordPress/Classes/ViewRelated/Notifications/ReplyTextView/ReplyTextView.xib diff --git a/WordPress/Classes/ViewRelated/Notifications/RichTextView/NSAttributedString+RichTextView.swift b/WordPress/Classes/ViewRelated/Notifications/RichTextView/NSAttributedString+RichTextView.swift new file mode 100644 index 000000000000..da4d19bbcaab --- /dev/null +++ b/WordPress/Classes/ViewRelated/Notifications/RichTextView/NSAttributedString+RichTextView.swift @@ -0,0 +1,25 @@ +import Foundation +import UIKit + + +// NOTE: +// This is not cool. The reason why we need this constant, is because DTCoreText overrides NSAttachmentAttributeName. +// Even using Swift's namespaces ("UIKit.NSAttachmentAttributeName") doesn't return the right value. +// Please, nuke DTCoreText, and remove this constant. +// +public let UIKitAttachmentAttributeName = "NSAttachment" + +extension NSAttributedString +{ + public func enumerateAttachments(block: (attachment: NSTextAttachment, range: NSRange) -> ()) { + let range = NSMakeRange(0, length) + + enumerateAttribute(UIKitAttachmentAttributeName, inRange: range, options: .LongestEffectiveRangeNotRequired) { + (value: AnyObject!, range: NSRange, stop: UnsafeMutablePointer) -> Void in + + if let attachment = value as? NSTextAttachment { + block(attachment: attachment, range: range) + } + } + } +} diff --git a/WordPress/Classes/ViewRelated/Notifications/RichTextView/RichTextView.swift b/WordPress/Classes/ViewRelated/Notifications/RichTextView/RichTextView.swift new file mode 100644 index 000000000000..81a469ef5ab7 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Notifications/RichTextView/RichTextView.swift @@ -0,0 +1,247 @@ +import Foundation + + +@objc public protocol RichTextViewDataSource +{ + optional func textView(textView: UITextView, viewForTextAttachment attachment: NSTextAttachment) -> UIView? +} + +@objc public protocol RichTextViewDelegate : UITextViewDelegate +{ + optional func textView(textView: UITextView, didPressLink link: NSURL) +} + + +@objc public class RichTextView : UIView, UITextViewDelegate +{ + public var dataSource: RichTextViewDataSource? + public var delegate: RichTextViewDelegate? + + + // MARK: - Initializers + public override init() { + super.init() + setupSubviews() + } + + public override init(frame: CGRect) { + super.init(frame: frame) + setupSubviews() + } + + public required init(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setupSubviews() + } + + + // MARK: - Properties + public var contentInset: UIEdgeInsets = UIEdgeInsetsZero { + didSet { + textView?.contentInset = contentInset + } + } + + public var textContainerInset: UIEdgeInsets = UIEdgeInsetsZero { + didSet { + textView?.textContainerInset = textContainerInset + } + } + + public var attributedText: NSAttributedString! { + didSet { + assert(textView != nil) + textView.attributedText = attributedText ?? NSAttributedString() + renderAttachments() + } + } + + public var editable: Bool = false { + didSet { + textView?.editable = editable + } + } + + public var selectable: Bool = true { + didSet { + textView?.selectable = selectable + } + } + + public var dataDetectorTypes: UIDataDetectorTypes = .None { + didSet { + textView?.dataDetectorTypes = dataDetectorTypes + } + } + + public override var backgroundColor: UIColor? { + didSet { + textView?.backgroundColor = backgroundColor + } + } + + public var linkTextAttributes: [NSObject : AnyObject]! { + didSet { + textView?.linkTextAttributes = linkTextAttributes + } + } + + + // MARK: - TextKit Getters + public var layoutManager: NSLayoutManager { + get { + return textView.layoutManager + } + } + + public var textStorage: NSTextStorage { + get { + return textView.textStorage + } + } + + public var textContainer: NSTextContainer { + get { + return textView.textContainer + } + } + + + // MARK: - Autolayout Helpers + public var preferredMaxLayoutWidth: CGFloat = 0 { + didSet { + invalidateIntrinsicContentSize() + } + } + + public override func intrinsicContentSize() -> CGSize { + // Fix: Let's add 1pt extra size. There are few scenarios in which text gets clipped by 1 point + let bottomPadding = CGFloat(1) + let maxWidth = (preferredMaxLayoutWidth != 0) ? preferredMaxLayoutWidth : frame.width + let maxSize = CGSize(width: maxWidth, height: CGFloat.max) + let requiredSize = textView!.sizeThatFits(maxSize) + let roundedSize = CGSize(width: ceil(requiredSize.width), height: ceil(requiredSize.height) + bottomPadding) + + return roundedSize + } + + + // MARK: - Private Methods + private func setupSubviews() { + gesturesRecognizer = UITapGestureRecognizer() + gesturesRecognizer.addTarget(self, action: "handleTextViewTap:") + + textView = UITextView(frame: bounds) + textView.backgroundColor = backgroundColor + textView.contentInset = UIEdgeInsetsZero + textView.textContainerInset = UIEdgeInsetsZero + textView.textContainer.lineFragmentPadding = 0 + textView.layoutManager.allowsNonContiguousLayout = false + textView.editable = editable + textView.dataDetectorTypes = dataDetectorTypes + textView.delegate = self + textView.gestureRecognizers = [gesturesRecognizer] + addSubview(textView) + + // Setup Layout + textView.setTranslatesAutoresizingMaskIntoConstraints(false) + pinSubviewToAllEdges(textView) + } + + private func renderAttachments() { + // Nuke old attachments + attachmentViews.map { $0.removeFromSuperview() } + attachmentViews.removeAll(keepCapacity: false) + + // Proceed only if needed + if attributedText == nil { + return + } + + // Load new attachments + attributedText.enumerateAttachments { + (attachment: NSTextAttachment, range: NSRange) -> () in + + let attachmentView = self.dataSource?.textView?(self.textView, viewForTextAttachment: attachment) + if attachmentView == nil { + return + } + + let unwrappedView = attachmentView! + unwrappedView.frame.origin = self.textView.frameForTextInRange(range).integerRect.origin + self.textView.addSubview(unwrappedView) + self.attachmentViews.append(unwrappedView) + } + } + + + // MARK: - UITapGestureRecognizer Helpers + public func handleTextViewTap(recognizer: UITapGestureRecognizer) { + + // NOTE: Why do we need this? + // Because this mechanism allows us to disable DataDetectors, and yet, detect taps on links. + // + let textStorage = textView.textStorage + let layoutManager = textView.layoutManager + let textContainer = textView.textContainer + + let locationInTextView = recognizer.locationInView(textView) + let characterIndex = layoutManager.characterIndexForPoint(locationInTextView, + inTextContainer: textContainer, + fractionOfDistanceBetweenInsertionPoints: nil) + + if characterIndex >= textStorage.length { + return + } + + // Load the NSURL instance, if any + let rawURL = textStorage.attribute(NSLinkAttributeName, atIndex: characterIndex, effectiveRange: nil) as? NSURL + if let unwrappedURL = rawURL { + delegate?.textView?(textView, didPressLink: unwrappedURL) + } + } + + + // MARK: - UITextViewDelegate Wrapped Methods + public func textViewShouldBeginEditing(textView: UITextView) -> Bool { + return delegate?.textViewShouldBeginEditing?(textView) ?? true + } + + public func textViewShouldEndEditing(textView: UITextView) -> Bool { + return delegate?.textViewShouldEndEditing?(textView) ?? true + } + + public func textViewDidBeginEditing(textView: UITextView) { + delegate?.textViewDidBeginEditing?(textView) + } + + public func textViewDidEndEditing(textView: UITextView) { + delegate?.textViewDidEndEditing?(textView) + } + + public func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool { + return delegate?.textView?(textView, shouldChangeTextInRange: range, replacementText: text) ?? true + } + + public func textViewDidChange(textView: UITextView) { + delegate?.textViewDidChange?(textView) + } + + public func textViewDidChangeSelection(textView: UITextView) { + delegate?.textViewDidChangeSelection?(textView) + } + + public func textView(textView: UITextView, shouldInteractWithURL URL: NSURL, inRange characterRange: NSRange) -> Bool { + return delegate?.textView?(textView, shouldInteractWithURL: URL, inRange: characterRange) ?? true + } + + public func textView(textView: UITextView, shouldInteractWithTextAttachment textAttachment: NSTextAttachment, inRange characterRange: NSRange) -> Bool { + return delegate?.textView?(textView, shouldInteractWithTextAttachment: textAttachment, inRange: characterRange) ?? true + } + + + // MARK: - Private Properites + private var textView: UITextView! + private var gesturesRecognizer: UITapGestureRecognizer! + private var attachmentViews: [UIView] = [] +} diff --git a/WordPress/Classes/ViewRelated/Notifications/RichTextView/UITextView+RichTextView.swift b/WordPress/Classes/ViewRelated/Notifications/RichTextView/UITextView+RichTextView.swift new file mode 100644 index 000000000000..e961cb2389d5 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Notifications/RichTextView/UITextView+RichTextView.swift @@ -0,0 +1,14 @@ +import Foundation + + +extension UITextView +{ + func frameForTextInRange(range: NSRange) -> CGRect { + let firstPosition = positionFromPosition(beginningOfDocument, offset: range.location) + let lastPosition = positionFromPosition(beginningOfDocument, offset: range.location + range.length) + let textRange = textRangeFromPosition(firstPosition, toPosition: lastPosition) + let textFrame = firstRectForRange(textRange) + + return textFrame + } +} diff --git a/WordPress/Classes/ViewRelated/Notifications/Style/WPStyleGuide+Notifications.swift b/WordPress/Classes/ViewRelated/Notifications/Style/WPStyleGuide+Notifications.swift index 8a6feacf4430..b7c2fb62e502 100644 --- a/WordPress/Classes/ViewRelated/Notifications/Style/WPStyleGuide+Notifications.swift +++ b/WordPress/Classes/ViewRelated/Notifications/Style/WPStyleGuide+Notifications.swift @@ -5,7 +5,7 @@ extension WPStyleGuide { public struct Notifications { - // Styles Used by NotificationsViewController + // MARK: - Styles Used by NotificationsViewController // // NoteTableViewCell @@ -19,29 +19,23 @@ extension WPStyleGuide public static let noteBackgroundUnreadColor = UIColor(red: 0xF1/255.0, green: 0xF6/255.0, blue: 0xF9/255.0, alpha: 0xFF/255.0) // Subject Text - public static let subjectColor = WPStyleGuide.littleEddieGrey() - public static let subjectRegularFont = WPFontManager.openSansRegularFontOfSize(subjectFontSize) - public static let subjectBoldFont = WPFontManager.openSansBoldFontOfSize(subjectFontSize) - public static let subjectItalicsFont = WPFontManager.openSansItalicFontOfSize(subjectFontSize) - public static let subjectRegularStyle = [ NSParagraphStyleAttributeName: subjectParagraph, NSFontAttributeName: subjectRegularFont, NSForegroundColorAttributeName: subjectColor ] - + public static let subjectBoldStyle = [ NSParagraphStyleAttributeName: subjectParagraph, NSFontAttributeName: subjectBoldFont ] public static let subjectItalicsStyle = [ NSParagraphStyleAttributeName: subjectParagraph, NSFontAttributeName: subjectItalicsFont ] - + // Subject Snippet - public static let snippetColor = WPStyleGuide.allTAllShadeGrey() + private static let snippetColor = WPStyleGuide.allTAllShadeGrey() public static let snippetRegularStyle = [ NSParagraphStyleAttributeName: snippetParagraph, NSFontAttributeName: subjectRegularFont, NSForegroundColorAttributeName: snippetColor ] - - // Styles used by NotificationDetailsViewController + // MARK: - Styles used by NotificationDetailsViewController // // Header @@ -76,11 +70,15 @@ extension WPStyleGuide public static let blockItalicsStyle = [ NSParagraphStyleAttributeName: blockParagraph, NSFontAttributeName: blockItalicsFont, NSForegroundColorAttributeName: blockTextColor] - + public static let blockQuotedStyle = [ NSParagraphStyleAttributeName: blockParagraph, NSFontAttributeName: blockItalicsFont, NSForegroundColorAttributeName: blockQuotedColor] - + + public static let blockBadgeStyle = [ NSParagraphStyleAttributeName: badgeParagraph, + NSFontAttributeName: blockRegularFont, + NSForegroundColorAttributeName: blockTextColor] + // Badges public static let badgeBackgroundColor = UIColor.clearColor() @@ -88,19 +86,12 @@ extension WPStyleGuide public static let blockActionDisabledColor = UIColor(red: 0x7F/255.0, green: 0x9E/255.0, blue: 0xB4/255.0, alpha: 0xFF/255.0) public static let blockActionEnabledColor = UIColor(red: 0xEA/255.0, green: 0x6D/255.0, blue: 0x1B/255.0, alpha: 0xFF/255.0) - // Helper Methods - public static func blockParagraphStyleWithIndentation(indentation: CGFloat) -> NSParagraphStyle { - let paragraph = blockParagraph.mutableCopy() as NSMutableParagraphStyle - paragraph.firstLineHeadIndent = indentation - return paragraph - } - - public static func blockParagraphStyleWithAlignment(alignment: NSTextAlignment) -> NSParagraphStyle { - let paragraph = blockParagraph.mutableCopy() as NSMutableParagraphStyle - paragraph.alignment = alignment - return paragraph + // RichText Helpers + public static func blockBackgroundColorForRichText(isBadge: Bool) -> UIColor { + return isBadge ? badgeBackgroundColor : blockBackgroundColor } - + + // Comment Helpers public static func blockTextColorForComment(isApproved approved: Bool) -> UIColor { return approved ? blockTextColor : blockUnapprovedTextColor } @@ -117,12 +108,17 @@ extension WPStyleGuide return approved ? blockLinkColor : blockUnapprovedTextColor } - // Private + + // MARK: - Private Propreties + // + + // Constants private static let subjectFontSize = UIDevice.isPad() ? CGFloat(16) : CGFloat(14) private static let subjectLineSize = UIDevice.isPad() ? CGFloat(24) : CGFloat(18) private static let blockFontSize = UIDevice.isPad() ? CGFloat(16) : CGFloat(14) private static let blockLineSize = UIDevice.isPad() ? CGFloat(24) : CGFloat(20) + // ParagraphStyle's private static let subjectParagraph = NSMutableParagraphStyle( minLineHeight: subjectLineSize, maxLineHeight: subjectLineSize, lineBreakMode: .ByWordWrapping, alignment: .Left ) @@ -130,8 +126,19 @@ extension WPStyleGuide minLineHeight: subjectLineSize, maxLineHeight: subjectLineSize, lineBreakMode: .ByTruncatingTail, alignment: .Left ) private static let blockParagraph = NSMutableParagraphStyle( - minLineHeight: blockLineSize, maxLineHeight: blockLineSize, lineBreakMode: .ByWordWrapping, alignment: .Left + minLineHeight: blockLineSize, lineBreakMode: .ByWordWrapping, alignment: .Left + ) + private static let badgeParagraph = NSMutableParagraphStyle( + minLineHeight: blockLineSize, maxLineHeight: blockLineSize, lineBreakMode: .ByWordWrapping, alignment: .Center ) + + // Colors + private static let subjectColor = WPStyleGuide.littleEddieGrey() + + // Fonts + private static let subjectRegularFont = WPFontManager.openSansRegularFontOfSize(subjectFontSize) + private static let subjectBoldFont = WPFontManager.openSansBoldFontOfSize(subjectFontSize) + private static let subjectItalicsFont = WPFontManager.openSansItalicFontOfSize(subjectFontSize) } // MARK: - ObjectiveC Helpers: Nuke me once NotificationDetailsViewController is Swifted! diff --git a/WordPress/Classes/ViewRelated/Notifications/Tools/NotificationMediaDownloader.swift b/WordPress/Classes/ViewRelated/Notifications/Tools/NotificationMediaDownloader.swift new file mode 100644 index 000000000000..876c3c5ba4eb --- /dev/null +++ b/WordPress/Classes/ViewRelated/Notifications/Tools/NotificationMediaDownloader.swift @@ -0,0 +1,140 @@ +import Foundation + + +@objc public class NotificationMediaDownloader : NSObject +{ + deinit { + downloadQueue.cancelAllOperations() + } + + public init(maximumImageWidth: CGFloat) { + downloadQueue = NSOperationQueue() + resizeQueue = dispatch_queue_create("org.wordpress.notifications.media-downloader", DISPATCH_QUEUE_CONCURRENT) + mediaMap = [NSURL: UIImage]() + retryMap = [NSURL: Int]() + maxImageWidth = maximumImageWidth + responseSerializer = AFImageResponseSerializer() as AFImageResponseSerializer + super.init() + } + + // MARK: - Public Helpers + public typealias SuccessBlock = (()->()) + + public func downloadMediaWithUrls(urls: NSSet, completion: SuccessBlock) { + let allUrls = urls.allObjects as? [NSURL] + if allUrls == nil { + return + } + + let missingUrls = allUrls!.filter { self.shouldDownloadImageWithURL($0) } + if missingUrls.count == 0 { + return + } + + for url in missingUrls { + downloadImageWithURL(url) { (NSError error, UIImage downloadedImage) -> () in + if error != nil || downloadedImage == nil { + return + } + + self.resizeImageIfNeeded(downloadedImage!, maxImageWidth: self.maxImageWidth) { + (UIImage resizedImage) -> () in + self.mediaMap[url] = resizedImage + completion() + } + } + } + } + + public func imagesForUrls(urls: [NSURL]) -> [NSURL: UIImage] { + var filtered = [NSURL: UIImage]() + + for (url, image) in mediaMap { + if contains(urls, url) { + filtered[url] = image + } + } + + return filtered + } + + + // MARK: - Networking Wrappers + private func downloadImageWithURL(url: NSURL, callback: ((NSError?, UIImage?) -> ())) { + let request = NSMutableURLRequest(URL: url) + request.HTTPShouldHandleCookies = false + request.addValue("image/*", forHTTPHeaderField: "Accept") + + let operation = AFHTTPRequestOperation(request: request) + operation.responseSerializer = responseSerializer + operation.setCompletionBlockWithSuccess({ + [weak self] + (AFHTTPRequestOperation operation, AnyObject responseObject) -> Void in + + if let unwrappedImage = responseObject as? UIImage { + callback(nil, unwrappedImage) + } + + }, failure: { + (AFHTTPRequestOperation operation, NSError error) -> Void in + callback(error, nil) + }) + + downloadQueue.addOperation(operation) + retryMap[url] = retryCountForURL(url) + 1 + } + + private func retryCountForURL(url: NSURL) -> Int { + return retryMap[url] ?? 0 + } + + private func shouldDownloadImageWithURL(url: NSURL!) -> Bool { + // Download only if it's not cached, and if it's not being downloaded right now! + if url == nil || mediaMap[url] != nil { + return false + } + + if retryCountForURL(url) > maximumRetryCount { + return false + } + + for operation in downloadQueue.operations as [AFHTTPRequestOperation] { + if operation.request.URL.isEqual(url) { + return false + } + } + + return true + } + + // MARK: - Async Image Resizing Helpers + private func resizeImageIfNeeded(image: UIImage, maxImageWidth: CGFloat, callback: ((UIImage) -> ())) { + if image.size.width <= maxImageWidth { + callback(image) + return + } + + // Calculate the target size + Process in BG! + var targetSize = image.size + targetSize.height = round(maxImageWidth * targetSize.height / targetSize.width) + targetSize.width = maxImageWidth + + dispatch_async(resizeQueue, { () -> Void in + let resizedImage = image.imageCroppedToFitSize(targetSize, ignoreAlpha: false) + dispatch_async(dispatch_get_main_queue(), { () -> Void in + callback(resizedImage) + }) + }) + } + + // MARK: - Constants + private let maximumRetryCount: Int = 3 + + // MARK: - Private Properties + private let responseSerializer: AFHTTPResponseSerializer + private let downloadQueue: NSOperationQueue + private let resizeQueue: dispatch_queue_t + private var mediaMap: [NSURL: UIImage] + private var retryMap: [NSURL: Int] + private let maxImageWidth: CGFloat +} diff --git a/WordPress/Classes/ViewRelated/Notifications/Views/NoteBlockCommentTableViewCell.swift b/WordPress/Classes/ViewRelated/Notifications/Views/NoteBlockCommentTableViewCell.swift index 76aba8d77fc1..3594379b70fa 100644 --- a/WordPress/Classes/ViewRelated/Notifications/Views/NoteBlockCommentTableViewCell.swift +++ b/WordPress/Classes/ViewRelated/Notifications/Views/NoteBlockCommentTableViewCell.swift @@ -32,7 +32,7 @@ import Foundation } public var site: String? { didSet { - btnSite.setTitle(site ?? String(), forState: .Normal) + siteLabel.text = site ?? String() } } public var isReplyEnabled: Bool = false { @@ -99,8 +99,12 @@ import Foundation // Setup Labels nameLabel.font = WPStyleGuide.Notifications.blockBoldFont timestampLabel.font = WPStyleGuide.Notifications.blockRegularFont - btnSite.titleLabel!.font = WPStyleGuide.Notifications.blockRegularFont + siteLabel.font = WPStyleGuide.Notifications.blockRegularFont + // Setup Recognizers + siteLabel.gestureRecognizers = [ UITapGestureRecognizer(target: self, action: "siteWasPressed:") ] + siteLabel.userInteractionEnabled = true + // Background approvalStatusView.backgroundColor = WPStyleGuide.Notifications.blockUnapprovedBgColor approvalSidebarView.backgroundColor = WPStyleGuide.Notifications.blockUnapprovedSideColor @@ -229,7 +233,7 @@ import Foundation separatorView.backgroundColor = WPStyleGuide.Notifications.blockSeparatorColorForComment(isApproved: isCommentApproved) nameLabel.textColor = WPStyleGuide.Notifications.blockTextColorForComment(isApproved: isCommentApproved) timestampLabel.textColor = WPStyleGuide.Notifications.blockTimestampColorForComment(isApproved: isCommentApproved) - btnSite.setTitleColor(WPStyleGuide.Notifications.blockTimestampColorForComment(isApproved: isCommentApproved), forState: .Normal) + siteLabel.textColor = WPStyleGuide.Notifications.blockTimestampColorForComment(isApproved: isCommentApproved) super.linkColor = WPStyleGuide.Notifications.blockLinkColorForComment(isApproved: isCommentApproved) super.attributedText = isCommentApproved ? attributedCommentText : attributedCommentUnapprovedText } @@ -267,8 +271,8 @@ import Foundation @IBOutlet private weak var gravatarImageView : UIImageView! @IBOutlet private weak var nameLabel : UILabel! @IBOutlet private weak var timestampLabel : UILabel! + @IBOutlet private weak var siteLabel : UILabel! @IBOutlet private weak var separatorView : UIView! - @IBOutlet private weak var btnSite : UIButton! @IBOutlet private weak var btnReply : UIButton! @IBOutlet private weak var btnLike : UIButton! @IBOutlet private weak var btnApprove : UIButton! diff --git a/WordPress/Classes/ViewRelated/Notifications/Views/NoteBlockImageTableViewCell.swift b/WordPress/Classes/ViewRelated/Notifications/Views/NoteBlockImageTableViewCell.swift index 6359841c8497..a698111f9dbe 100644 --- a/WordPress/Classes/ViewRelated/Notifications/Views/NoteBlockImageTableViewCell.swift +++ b/WordPress/Classes/ViewRelated/Notifications/Views/NoteBlockImageTableViewCell.swift @@ -35,9 +35,6 @@ import Foundation selectionStyle = .None } - // MARK: - Private Methods - - // MARK: - Private private var imageURL: NSURL? diff --git a/WordPress/Classes/ViewRelated/Notifications/Views/NoteBlockTextTableViewCell.swift b/WordPress/Classes/ViewRelated/Notifications/Views/NoteBlockTextTableViewCell.swift index 724c3b777ed0..c090d6433d4a 100644 --- a/WordPress/Classes/ViewRelated/Notifications/Views/NoteBlockTextTableViewCell.swift +++ b/WordPress/Classes/ViewRelated/Notifications/Views/NoteBlockTextTableViewCell.swift @@ -1,27 +1,23 @@ import Foundation -@objc public class NoteBlockTextTableViewCell : NoteBlockTableViewCell +@objc public class NoteBlockTextTableViewCell : NoteBlockTableViewCell, RichTextViewDataSource, RichTextViewDelegate { // MARK: - Public Properties public var onUrlClick: ((NSURL) -> Void)? public var attributedText: NSAttributedString? { didSet { - textView.attributedText = attributedText ?? NSAttributedString() + textView.attributedText = attributedText setNeedsLayout() } } + public var isBadge: Bool = false { didSet { - if isBadge { - backgroundColor = WPStyleGuide.Notifications.badgeBackgroundColor - alignment = .Center - } else { - backgroundColor = WPStyleGuide.Notifications.blockBackgroundColor - alignment = .Left - } + backgroundColor = WPStyleGuide.Notifications.blockBackgroundColorForRichText(isBadge) } } + public var linkColor: UIColor? { didSet { if let unwrappedLinkColor = linkColor { @@ -29,6 +25,7 @@ import Foundation } } } + public var dataDetectors: UIDataDetectorTypes? { didSet { if let unwrappedDataDetectors = dataDetectors { @@ -36,94 +33,53 @@ import Foundation } } } + public var labelPadding: UIEdgeInsets { return privateLabelPadding } - public var isTextViewSelectable: Bool = false { + + public var isTextViewSelectable: Bool = true { didSet { textView.selectable = isTextViewSelectable } } - // TODO: - // Once NotificationDetailsViewController has been migrated to Swift, please, nuke this property, and make sure this class is fed - // with an string already aligned. - // This is temporary workaround since WPStyleGuide+Notifications is swift only. - // - private var alignment : NSTextAlignment = .Left { - didSet { - if attributedText == nil { - return - } - - let unwrappedMutableString = attributedText!.mutableCopy() as NSMutableAttributedString - let range = NSRange(location: 0, length: unwrappedMutableString.length) - let paragraph = WPStyleGuide.Notifications.blockParagraphStyleWithAlignment(.Center) - unwrappedMutableString.addAttribute(NSParagraphStyleAttributeName, value: paragraph, range: range) - - attributedText = unwrappedMutableString - } - } - // MARK: - View Methods public override func awakeFromNib() { super.awakeFromNib() backgroundColor = WPStyleGuide.Notifications.blockBackgroundColor selectionStyle = .None - textView.contentInset = privateLabelInsets + + assert(textView != nil) + textView.contentInset = UIEdgeInsetsZero textView.textContainerInset = UIEdgeInsetsZero textView.backgroundColor = UIColor.clearColor() textView.editable = false - textView.selectable = false + textView.selectable = true textView.dataDetectorTypes = .None + textView.dataSource = self + textView.delegate = self - // Setup a Gestures Recognizer: This way we'll handle links! - gesturesRecognizer = UITapGestureRecognizer() - gesturesRecognizer.addTarget(self, action: "handleTap:") - textView.gestureRecognizers = [gesturesRecognizer] + textView.setTranslatesAutoresizingMaskIntoConstraints(false) } public override func layoutSubviews() { // Calculate the TextView's width, before hitting layoutSubviews! textView.preferredMaxLayoutWidth = min(bounds.width, maxWidth) - labelPadding.left - labelPadding.right - super.layoutSubviews() } - - - // MARK: - UITapGestureRecognizer Helpers - public func handleTap(recognizer: UITapGestureRecognizer) { - - // Detect the location tapped - let textStorage = textView.textStorage - let layoutManager = textView.layoutManager - let textContainer = textView.textContainer - - let locationInTextView = recognizer.locationInView(textView) - let characterIndex = layoutManager.characterIndexForPoint(locationInTextView, inTextContainer: textContainer, fractionOfDistanceBetweenInsertionPoints: nil) - if characterIndex >= textStorage.length { - return - } - - // Load the NSURL instance, if any - let rawURL: AnyObject? = textView.textStorage.attribute(NSLinkAttributeName, atIndex: characterIndex, effectiveRange: nil) - if let unwrappedURL = rawURL as? NSURL { - onUrlClick?(unwrappedURL) - } + // MARK: - RichTextView Data Source + public func textView(textView: UITextView, didPressLink link: NSURL) { + onUrlClick?(link) } - // MARK: - Constants private let maxWidth = WPTableViewFixedWidth private let privateLabelPadding = UIEdgeInsets(top: 0, left: 12, bottom: 0, right: 12) - private let privateLabelInsets = UIEdgeInsets(top: 1, left: -5, bottom: 0, right: 0) - - // MARK: - Private - private var gesturesRecognizer: UITapGestureRecognizer! // MARK: - IBOutlets - @IBOutlet private weak var textView: WPDynamicHeightTextView! + @IBOutlet private weak var textView: RichTextView! } diff --git a/WordPress/Resources/InlineComposeView.xib b/WordPress/Resources/InlineComposeView.xib deleted file mode 100644 index 1c70ed23ecb5..000000000000 --- a/WordPress/Resources/InlineComposeView.xib +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index b6b5327695c4..d24951c78416 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -259,9 +259,7 @@ ACBAB6860E1247F700F38795 /* PostPreviewViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = ACBAB6850E1247F700F38795 /* PostPreviewViewController.m */; }; ACC156CC0E10E67600D6E1A0 /* WPPostViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = ACC156CB0E10E67600D6E1A0 /* WPPostViewController.m */; }; ADF544C2195A0F620092213D /* CustomHighlightButton.m in Sources */ = {isa = PBXBuildFile; fileRef = ADF544C1195A0F620092213D /* CustomHighlightButton.m */; }; - B5134AF519B2C4F200FADE8C /* ReplyBezierView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5134AF419B2C4F200FADE8C /* ReplyBezierView.swift */; }; B51D9A7E19634D4400CA857B /* Noticons-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = B55853F419630AF900FAF6C3 /* Noticons-Regular.otf */; }; - B52B4F7A19C0E49B00526D6F /* WPDynamicHeightTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52B4F7919C0E49B00526D6F /* WPDynamicHeightTextView.swift */; }; B52C4C7D199D4CD3009FD823 /* NoteBlockUserTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52C4C7C199D4CD3009FD823 /* NoteBlockUserTableViewCell.swift */; }; B52C4C7F199D74AE009FD823 /* NoteTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52C4C7E199D74AE009FD823 /* NoteTableViewCell.swift */; }; B532D4E9199D4357006E4DF6 /* NoteBlockCommentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B532D4E5199D4357006E4DF6 /* NoteBlockCommentTableViewCell.swift */; }; @@ -271,6 +269,11 @@ B532D4EE199D4418006E4DF6 /* NoteBlockImageTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B532D4ED199D4418006E4DF6 /* NoteBlockImageTableViewCell.swift */; }; B53FDF6D19B8C336000723B6 /* UIScreen+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53FDF6C19B8C336000723B6 /* UIScreen+Helpers.swift */; }; B548458219A258890077E7A5 /* UIActionSheet+Helpers.m in Sources */ = {isa = PBXBuildFile; fileRef = B548458119A258890077E7A5 /* UIActionSheet+Helpers.m */; }; + B54866CA1A0D7042004AC79D /* NSAttributedString+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54866C91A0D7042004AC79D /* NSAttributedString+Helpers.swift */; }; + B54E1DF01A0A7BAA00807537 /* ReplyBezierView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54E1DED1A0A7BAA00807537 /* ReplyBezierView.swift */; }; + B54E1DF11A0A7BAA00807537 /* ReplyTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54E1DEE1A0A7BAA00807537 /* ReplyTextView.swift */; }; + B54E1DF21A0A7BAA00807537 /* ReplyTextView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B54E1DEF1A0A7BAA00807537 /* ReplyTextView.xib */; }; + B54E1DF41A0A7BBF00807537 /* NotificationMediaDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54E1DF31A0A7BBF00807537 /* NotificationMediaDownloader.swift */; }; B5509A9319CA38B3006D2E49 /* EditReplyViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B5509A9219CA38B3006D2E49 /* EditReplyViewController.m */; }; B5509A9519CA3B9F006D2E49 /* EditReplyViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = B5509A9419CA3B9F006D2E49 /* EditReplyViewController.xib */; }; B55853F31962337500FAF6C3 /* NSScanner+Helpers.m in Sources */ = {isa = PBXBuildFile; fileRef = B55853F21962337500FAF6C3 /* NSScanner+Helpers.m */; }; @@ -298,9 +301,10 @@ B5CC05F61962150600975CAC /* Constants.m in Sources */ = {isa = PBXBuildFile; fileRef = B5CC05F51962150600975CAC /* Constants.m */; }; B5CC05F91962186D00975CAC /* Meta.m in Sources */ = {isa = PBXBuildFile; fileRef = B5CC05F81962186D00975CAC /* Meta.m */; }; B5CC05FC196218E100975CAC /* XMLParserCollecter.m in Sources */ = {isa = PBXBuildFile; fileRef = B5CC05FB196218E100975CAC /* XMLParserCollecter.m */; }; + B5D7F2DC1A04180A006D3047 /* NSAttributedString+RichTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D7F2D91A04180A006D3047 /* NSAttributedString+RichTextView.swift */; }; + B5D7F2DD1A04180A006D3047 /* RichTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D7F2DA1A04180A006D3047 /* RichTextView.swift */; }; + B5D7F2DE1A04180A006D3047 /* UITextView+RichTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D7F2DB1A04180A006D3047 /* UITextView+RichTextView.swift */; }; B5E167F419C08D18009535AA /* NSCalendar+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E167F319C08D18009535AA /* NSCalendar+Helpers.swift */; }; - B5E23BDC19AD0CED000D6879 /* ReplyTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E23BDA19AD0CED000D6879 /* ReplyTextView.swift */; }; - B5E23BDD19AD0CED000D6879 /* ReplyTextView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B5E23BDB19AD0CED000D6879 /* ReplyTextView.xib */; }; B5E23BDF19AD0D00000D6879 /* NoteTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B5E23BDE19AD0D00000D6879 /* NoteTableViewCell.xib */; }; B5F015CB195DFD7600F6ECF2 /* WordPressActivity.m in Sources */ = {isa = PBXBuildFile; fileRef = B5F015CA195DFD7600F6ECF2 /* WordPressActivity.m */; }; B5FD4543199D0F2800286FBB /* NotificationDetailsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B5FD4540199D0F2800286FBB /* NotificationDetailsViewController.m */; }; @@ -314,7 +318,6 @@ CC24E5EF1577D1EA00A6D5B5 /* WPFriendFinderViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CC24E5EE1577D1EA00A6D5B5 /* WPFriendFinderViewController.m */; }; CC24E5F11577DBC300A6D5B5 /* AddressBook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CC24E5F01577DBC300A6D5B5 /* AddressBook.framework */; }; CC24E5F51577E16B00A6D5B5 /* Accounts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CC24E5F41577E16B00A6D5B5 /* Accounts.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - CC70165B185A7536007B37DB /* InlineComposeView.xib in Resources */ = {isa = PBXBuildFile; fileRef = CC70165A185A7536007B37DB /* InlineComposeView.xib */; }; CCEF153114C9EA050001176D /* WPWebAppViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CCEF153014C9EA050001176D /* WPWebAppViewController.m */; }; CEBD3EAB0FF1BA3B00C1396E /* Blog.m in Sources */ = {isa = PBXBuildFile; fileRef = CEBD3EAA0FF1BA3B00C1396E /* Blog.m */; }; E100C6BB1741473000AE48D8 /* WordPress-11-12.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = E100C6BA1741472F00AE48D8 /* WordPress-11-12.xcmappingmodel */; }; @@ -950,8 +953,6 @@ ADF544C1195A0F620092213D /* CustomHighlightButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomHighlightButton.m; sourceTree = ""; }; AEFB66560B716519236CEE67 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "../Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; B43F6A7D9B3DC5B8B4A7DDCA /* Pods-WordPressTest.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressTest.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-WordPressTest/Pods-WordPressTest.debug.xcconfig"; sourceTree = ""; }; - B5134AF419B2C4F200FADE8C /* ReplyBezierView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyBezierView.swift; sourceTree = ""; }; - B52B4F7919C0E49B00526D6F /* WPDynamicHeightTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WPDynamicHeightTextView.swift; sourceTree = ""; }; B52C4C7C199D4CD3009FD823 /* NoteBlockUserTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoteBlockUserTableViewCell.swift; sourceTree = ""; }; B52C4C7E199D74AE009FD823 /* NoteTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoteTableViewCell.swift; sourceTree = ""; }; B532D4E5199D4357006E4DF6 /* NoteBlockCommentTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoteBlockCommentTableViewCell.swift; sourceTree = ""; }; @@ -962,6 +963,11 @@ B53FDF6C19B8C336000723B6 /* UIScreen+Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIScreen+Helpers.swift"; sourceTree = ""; }; B548458019A258890077E7A5 /* UIActionSheet+Helpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIActionSheet+Helpers.h"; sourceTree = ""; }; B548458119A258890077E7A5 /* UIActionSheet+Helpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIActionSheet+Helpers.m"; sourceTree = ""; }; + B54866C91A0D7042004AC79D /* NSAttributedString+Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Helpers.swift"; sourceTree = ""; }; + B54E1DED1A0A7BAA00807537 /* ReplyBezierView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyBezierView.swift; sourceTree = ""; }; + B54E1DEE1A0A7BAA00807537 /* ReplyTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyTextView.swift; sourceTree = ""; }; + B54E1DEF1A0A7BAA00807537 /* ReplyTextView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ReplyTextView.xib; sourceTree = ""; }; + B54E1DF31A0A7BBF00807537 /* NotificationMediaDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationMediaDownloader.swift; sourceTree = ""; }; B5509A9119CA38B3006D2E49 /* EditReplyViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EditReplyViewController.h; sourceTree = ""; }; B5509A9219CA38B3006D2E49 /* EditReplyViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EditReplyViewController.m; sourceTree = ""; }; B5509A9419CA3B9F006D2E49 /* EditReplyViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = EditReplyViewController.xib; path = Resources/EditReplyViewController.xib; sourceTree = ""; }; @@ -998,9 +1004,10 @@ B5CC05F81962186D00975CAC /* Meta.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Meta.m; sourceTree = ""; }; B5CC05FA196218E100975CAC /* XMLParserCollecter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMLParserCollecter.h; sourceTree = ""; }; B5CC05FB196218E100975CAC /* XMLParserCollecter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XMLParserCollecter.m; sourceTree = ""; }; + B5D7F2D91A04180A006D3047 /* NSAttributedString+RichTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+RichTextView.swift"; sourceTree = ""; }; + B5D7F2DA1A04180A006D3047 /* RichTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RichTextView.swift; sourceTree = ""; }; + B5D7F2DB1A04180A006D3047 /* UITextView+RichTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITextView+RichTextView.swift"; sourceTree = ""; }; B5E167F319C08D18009535AA /* NSCalendar+Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSCalendar+Helpers.swift"; sourceTree = ""; }; - B5E23BDA19AD0CED000D6879 /* ReplyTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyTextView.swift; sourceTree = ""; }; - B5E23BDB19AD0CED000D6879 /* ReplyTextView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ReplyTextView.xib; sourceTree = ""; }; B5E23BDE19AD0D00000D6879 /* NoteTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NoteTableViewCell.xib; sourceTree = ""; }; B5F015C9195DFD7600F6ECF2 /* WordPressActivity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WordPressActivity.h; sourceTree = ""; }; B5F015CA195DFD7600F6ECF2 /* WordPressActivity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WordPressActivity.m; sourceTree = ""; }; @@ -1030,7 +1037,6 @@ CC24E5F01577DBC300A6D5B5 /* AddressBook.framework */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = wrapper.framework; name = AddressBook.framework; path = System/Library/Frameworks/AddressBook.framework; sourceTree = SDKROOT; }; CC24E5F21577DFF400A6D5B5 /* Twitter.framework */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = wrapper.framework; name = Twitter.framework; path = System/Library/Frameworks/Twitter.framework; sourceTree = SDKROOT; }; CC24E5F41577E16B00A6D5B5 /* Accounts.framework */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = wrapper.framework; name = Accounts.framework; path = System/Library/Frameworks/Accounts.framework; sourceTree = SDKROOT; }; - CC70165A185A7536007B37DB /* InlineComposeView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = InlineComposeView.xib; path = Resources/InlineComposeView.xib; sourceTree = ""; }; CCEF152F14C9EA050001176D /* WPWebAppViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WPWebAppViewController.h; sourceTree = ""; }; CCEF153014C9EA050001176D /* WPWebAppViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WPWebAppViewController.m; sourceTree = ""; }; CEBD3EA90FF1BA3B00C1396E /* Blog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Blog.h; sourceTree = ""; }; @@ -1997,7 +2003,6 @@ 8525398A171761D9003F6B32 /* WPComLanguages.m */, E183BD7217621D85000B0822 /* WPCookie.h */, E183BD7317621D86000B0822 /* WPCookie.m */, - B52B4F7919C0E49B00526D6F /* WPDynamicHeightTextView.swift */, E114D798153D85A800984182 /* WPError.h */, E114D799153D85A800984182 /* WPError.m */, 5DA3EE0E192508F700294E0B /* WPImageOptimizer.h */, @@ -2213,10 +2218,21 @@ path = Notifications; sourceTree = ""; }; + B54E1DEC1A0A7BAA00807537 /* ReplyTextView */ = { + isa = PBXGroup; + children = ( + B54E1DED1A0A7BAA00807537 /* ReplyBezierView.swift */, + B54E1DEE1A0A7BAA00807537 /* ReplyTextView.swift */, + B54E1DEF1A0A7BAA00807537 /* ReplyTextView.xib */, + ); + path = ReplyTextView; + sourceTree = ""; + }; B587796C19B799D800E57C5A /* Extensions */ = { isa = PBXGroup; children = ( B587798319B799EB00E57C5A /* Notifications */, + B5E167F319C08D18009535AA /* NSCalendar+Helpers.swift */, B587796F19B799D800E57C5A /* NSDate+Helpers.swift */, B587797019B799D800E57C5A /* NSIndexPath+Swift.swift */, B587797119B799D800E57C5A /* NSParagraphStyle+Helpers.swift */, @@ -2227,7 +2243,7 @@ B587797619B799D800E57C5A /* UITableViewCell+Helpers.swift */, B587797719B799D800E57C5A /* UIView+Helpers.swift */, B53FDF6C19B8C336000723B6 /* UIScreen+Helpers.swift */, - B5E167F319C08D18009535AA /* NSCalendar+Helpers.swift */, + B54866C91A0D7042004AC79D /* NSAttributedString+Helpers.swift */, ); path = Extensions; sourceTree = ""; @@ -2250,12 +2266,20 @@ path = Style; sourceTree = ""; }; + B5D7F2D81A04180A006D3047 /* RichTextView */ = { + isa = PBXGroup; + children = ( + B5D7F2DA1A04180A006D3047 /* RichTextView.swift */, + B5D7F2D91A04180A006D3047 /* NSAttributedString+RichTextView.swift */, + B5D7F2DB1A04180A006D3047 /* UITextView+RichTextView.swift */, + ); + path = RichTextView; + sourceTree = ""; + }; B5E23BD919AD0CED000D6879 /* Tools */ = { isa = PBXGroup; children = ( - B5E23BDA19AD0CED000D6879 /* ReplyTextView.swift */, - B5E23BDB19AD0CED000D6879 /* ReplyTextView.xib */, - B5134AF419B2C4F200FADE8C /* ReplyBezierView.swift */, + B54E1DF31A0A7BBF00807537 /* NotificationMediaDownloader.swift */, ); path = Tools; sourceTree = ""; @@ -2400,6 +2424,8 @@ isa = PBXGroup; children = ( B5B56D2F19AFB68800B4E29B /* Style */, + B5D7F2D81A04180A006D3047 /* RichTextView */, + B54E1DEC1A0A7BAA00807537 /* ReplyTextView */, B5E23BD919AD0CED000D6879 /* Tools */, B5FD453E199D0F2800286FBB /* Controllers */, B5FD4523199D0F1100286FBB /* Views */, @@ -2595,7 +2621,6 @@ 28AD735F0D9D9599002E5188 /* MainWindow.xib */, 30AF6CF413C2289600A29C00 /* AboutViewController.xib */, 3768BEF013041E7900E7C9A9 /* BetaFeedbackViewController.xib */, - CC70165A185A7536007B37DB /* InlineComposeView.xib */, 5DF94E311962B9D800359241 /* WPAlertView.xib */, 5DF94E321962B9D800359241 /* WPAlertViewSideBySide.xib */, ); @@ -2877,12 +2902,12 @@ 74C1C30E199170EA0077A7DC /* PostDetailViewController~ipad.xib in Resources */, 8370D10C11FA4A1B009D650F /* WPTableViewActivityCell.xib in Resources */, 8370D1BE11FA6295009D650F /* AddSiteViewController.xib in Resources */, - B5E23BDD19AD0CED000D6879 /* ReplyTextView.xib in Resources */, 8333FE0E11FF6EF200A495C1 /* EditSiteViewController.xib in Resources */, 74C1C306199170930077A7DC /* PostDetailViewController.xib in Resources */, 5DF94E331962B9D800359241 /* WPAlertView.xib in Resources */, 8362C1041201E7CE00599347 /* WebSignupViewController-iPad.xib in Resources */, 83CAD4211235F9F4003DFA20 /* MediaObjectView.xib in Resources */, + B54E1DF21A0A7BAA00807537 /* ReplyTextView.xib in Resources */, 3768BEF213041E7900E7C9A9 /* BetaFeedbackViewController.xib in Resources */, 313AE4A319E3F20400AAFABE /* CommentTableViewHeaderCell.xib in Resources */, E1D91456134A853D0089019C /* Localizable.strings in Resources */, @@ -2898,7 +2923,6 @@ 85ED988817DFA00000090D0B /* Images.xcassets in Resources */, 5DC02A3818E4C5BD009A1765 /* ThemeDetailsViewController.xib in Resources */, 313AE4A219E3F20400AAFABE /* CommentTableViewCell.xib in Resources */, - CC70165B185A7536007B37DB /* InlineComposeView.xib in Resources */, 3716E401167296D30035F8C4 /* ToastView.xib in Resources */, 5DA5BF3E18E32DCF005F11F9 /* EditMediaViewController.xib in Resources */, B5509A9519CA3B9F006D2E49 /* EditReplyViewController.xib in Resources */, @@ -3191,6 +3215,7 @@ 834CAE7C122D528A003DDF49 /* UIImage+Resize.m in Sources */, 5D9B17C519998A430047A4A2 /* ReaderBlockedTableViewCell.m in Sources */, 834CAE9F122D56B1003DDF49 /* UIImage+Alpha.m in Sources */, + B54866CA1A0D7042004AC79D /* NSAttributedString+Helpers.swift in Sources */, 834CAEA0122D56B1003DDF49 /* UIImage+RoundedCorner.m in Sources */, E18EE95119349EC300B0A40C /* ReaderTopicServiceRemote.m in Sources */, B5FD4544199D0F2800286FBB /* NotificationsViewController.m in Sources */, @@ -3202,6 +3227,7 @@ E1B4A9E112FC8B1000EB3F67 /* EGORefreshTableHeaderView.m in Sources */, 5DF94E521962BAEB00359241 /* ReaderPostRichContentView.m in Sources */, 5DA3EE12192508F700294E0B /* WPImageOptimizer.m in Sources */, + B5D7F2DE1A04180A006D3047 /* UITextView+RichTextView.swift in Sources */, E1D458691309589C00BF0235 /* Coordinate.m in Sources */, 375D090D133B94C3000CC9CD /* BlogsTableViewCell.m in Sources */, 859CFD46190E3198005FB217 /* WPMediaUploader.m in Sources */, @@ -3240,9 +3266,9 @@ 5DEB61B8156FCD5200242C35 /* WPChromelessWebViewController.m in Sources */, CC24E5EF1577D1EA00A6D5B5 /* WPFriendFinderViewController.m in Sources */, 5903AE1B19B60A98009D5354 /* WPButtonForNavigationBar.m in Sources */, - B52B4F7A19C0E49B00526D6F /* WPDynamicHeightTextView.swift in Sources */, E1AB07AD1578D34300D6AD64 /* SettingsViewController.m in Sources */, E13EB7A5157D230000885780 /* WordPressComApi.m in Sources */, + B5D7F2DC1A04180A006D3047 /* NSAttributedString+RichTextView.swift in Sources */, 5D5D0027187DA9D30027CEF6 /* CategoriesViewController.m in Sources */, 5DF7389A1965FB3C00393584 /* WPTableViewHandler.m in Sources */, E1E4CE0B1773C59B00430844 /* WPAvatarSource.m in Sources */, @@ -3259,7 +3285,6 @@ 5D44EB351986D695008B7175 /* ReaderSiteServiceRemote.m in Sources */, 5DA5BF4318E32DCF005F11F9 /* MediaSearchFilterHeaderView.m in Sources */, 5DF94E441962BAA700359241 /* WPContentView.m in Sources */, - B5E23BDC19AD0CED000D6879 /* ReplyTextView.swift in Sources */, 5DB4683B18A2E718004A89A9 /* LocationService.m in Sources */, 93740DCB17D8F86700C41B2F /* WPAlertView.m in Sources */, E1D04D7E19374CFE002FADD7 /* BlogServiceRemoteXMLRPC.m in Sources */, @@ -3319,6 +3344,7 @@ 5DAE40AD19EC70930011A0AE /* ReaderPostHeaderView.m in Sources */, 74BB6F1A19AE7B9400FB7829 /* WPLegacyEditPageViewController.m in Sources */, 85B6F7521742DAE800CE7F3A /* WPNUXBackButton.m in Sources */, + B54E1DF01A0A7BAA00807537 /* ReplyBezierView.swift in Sources */, 5DF738941965FAB900393584 /* SubscribedTopicsViewController.m in Sources */, E2E7EB46185FB140004F5E72 /* WPBlogSelectorButton.m in Sources */, E183BD7417621D87000B0822 /* WPCookie.m in Sources */, @@ -3328,6 +3354,7 @@ 5DBCD9D518F35D7500B32229 /* ReaderTopicService.m in Sources */, 5D42A3DF175E7452005CFF05 /* AbstractPost.m in Sources */, 5D42A3E0175E7452005CFF05 /* BasePost.m in Sources */, + B5D7F2DD1A04180A006D3047 /* RichTextView.swift in Sources */, E1249B4319408C910035E895 /* RemoteComment.m in Sources */, 93DEB88219E5BF7100F9546D /* TodayExtensionService.m in Sources */, 5D42A3E2175E7452005CFF05 /* ReaderPost.m in Sources */, @@ -3340,7 +3367,7 @@ 85D2275918F1EB8A001DA8DA /* WPAnalyticsTrackerMixpanel.m in Sources */, E149D64F19349E69006A843D /* AccountServiceRemoteXMLRPC.m in Sources */, E1DF5DFE19E7CFAE004E70D5 /* CategoryServiceRemoteXMLRPC.m in Sources */, - B5134AF519B2C4F200FADE8C /* ReplyBezierView.swift in Sources */, + B54E1DF11A0A7BAA00807537 /* ReplyTextView.swift in Sources */, 5DF94E461962BAA700359241 /* WPRichTextView.m in Sources */, 5D42A3FB175E75EE005CFF05 /* ReaderPostDetailViewController.m in Sources */, E18EE94E19349EBA00B0A40C /* BlogServiceRemote.m in Sources */, @@ -3363,6 +3390,7 @@ FF3DD6BE19F2B6B3003A52CB /* RemoteMedia.m in Sources */, B5FD4543199D0F2800286FBB /* NotificationDetailsViewController.m in Sources */, 74D5FFD619ACDF6700389E8F /* WPLegacyEditPostViewController.m in Sources */, + B54E1DF41A0A7BBF00807537 /* NotificationMediaDownloader.swift in Sources */, E174F6E6172A73960004F23A /* WPAccount.m in Sources */, E100C6BB1741473000AE48D8 /* WordPress-11-12.xcmappingmodel in Sources */, E1A03EE217422DCF0085D192 /* BlogToAccount.m in Sources */,