diff options
author | Vitaly Takmazov | 2023-05-14 00:56:46 +0300 |
---|---|---|
committer | Vitaly Takmazov | 2023-05-14 01:15:35 +0300 |
commit | 88ab0d847a03b92646f826f77bc3058854be1a6f (patch) | |
tree | 48b1d27e39cc5e58fed0d339ade6691c5a2b5e38 | |
parent | 7bb21424f0e764ab0d7a535be916fa93001feefa (diff) |
MessageCell conversion
-rw-r--r-- | Juick.xcodeproj/project.pbxproj | 10 | ||||
-rw-r--r-- | Juick/Supporting Files/Juick-Bridging-Header.h | 2 | ||||
-rw-r--r-- | Juick/Supporting Files/Juick-Prefix.pch | 1 | ||||
-rw-r--r-- | Juick/ViewControllers/ChatViewController.m | 2 | ||||
-rw-r--r-- | Juick/ViewControllers/DialogsViewController.m | 1 | ||||
-rw-r--r-- | Juick/ViewControllers/FeedViewController.m | 1 | ||||
-rw-r--r-- | Juick/ViewControllers/MessagesViewController.h | 6 | ||||
-rw-r--r-- | Juick/ViewControllers/MessagesViewController.m | 3 | ||||
-rw-r--r-- | Juick/ViewControllers/NewPostViewController.m | 2 | ||||
-rw-r--r-- | Juick/ViewControllers/ThreadViewController.m | 1 | ||||
-rw-r--r-- | Juick/Views/MessageCell.h | 32 | ||||
-rw-r--r-- | Juick/Views/MessageCell.m | 187 | ||||
-rw-r--r-- | Juick/Views/MessageCell.swift | 181 | ||||
-rw-r--r-- | Juick/Views/MessageCell.xib | 27 |
14 files changed, 212 insertions, 244 deletions
diff --git a/Juick.xcodeproj/project.pbxproj b/Juick.xcodeproj/project.pbxproj index 3f5232d..0f12b93 100644 --- a/Juick.xcodeproj/project.pbxproj +++ b/Juick.xcodeproj/project.pbxproj @@ -37,7 +37,6 @@ 774C98CD25126C070073C70A /* Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = 774C98CC25126C070073C70A /* Service.swift */; }; 7761133921766A3000D350CD /* ContentLoadingCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7761133721766A3000D350CD /* ContentLoadingCell.xib */; }; 7761135D21790B0300D350CD /* JuickPush.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 7761135521790B0200D350CD /* JuickPush.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 776C41BD1FD3EF180063B82E /* MessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 776C41BB1FD3EF180063B82E /* MessageCell.m */; }; 776C41BE1FD3EF180063B82E /* MessageCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 776C41BC1FD3EF180063B82E /* MessageCell.xib */; }; 776C41C11FD3FF6E0063B82E /* FeedViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 776C41C01FD3FF6E0063B82E /* FeedViewController.m */; }; 776D4EE32A0FD1A300C8BD91 /* ContentLoadingCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 776D4EE22A0FD1A300C8BD91 /* ContentLoadingCell.swift */; }; @@ -45,6 +44,7 @@ 776D4EE72A0FD96C00C8BD91 /* MessageInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 776D4EE62A0FD96C00C8BD91 /* MessageInputView.swift */; }; 776D4EE92A0FDF5D00C8BD91 /* BubbleMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 776D4EE82A0FDF5D00C8BD91 /* BubbleMessageCell.swift */; }; 776D4EED2A0FE1F100C8BD91 /* QuoteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 776D4EEC2A0FE1F100C8BD91 /* QuoteView.swift */; }; + 776D4EEF2A0FE31800C8BD91 /* MessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 776D4EEE2A0FE31800C8BD91 /* MessageCell.swift */; }; 778560602343D24E00BB37A2 /* NSData+Hex.m in Sources */ = {isa = PBXBuildFile; fileRef = 7785605F2343D24E00BB37A2 /* NSData+Hex.m */; }; 778560632344CF6F00BB37A2 /* JuickNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 778560622344CF6F00BB37A2 /* JuickNavigationController.m */; }; 77975A1D182B6E9A00410C2B /* NewPostViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 77975A1C182B6E9A00410C2B /* NewPostViewController.m */; }; @@ -151,8 +151,6 @@ 7761135521790B0200D350CD /* JuickPush.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = JuickPush.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 7761135A21790B0300D350CD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 7761136821790C2D00D350CD /* UserNotificationsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotificationsUI.framework; path = System/Library/Frameworks/UserNotificationsUI.framework; sourceTree = SDKROOT; }; - 776C41BA1FD3EF180063B82E /* MessageCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MessageCell.h; sourceTree = "<group>"; }; - 776C41BB1FD3EF180063B82E /* MessageCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MessageCell.m; sourceTree = "<group>"; }; 776C41BC1FD3EF180063B82E /* MessageCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MessageCell.xib; sourceTree = "<group>"; }; 776C41BF1FD3FF6E0063B82E /* FeedViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FeedViewController.h; sourceTree = "<group>"; }; 776C41C01FD3FF6E0063B82E /* FeedViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FeedViewController.m; sourceTree = "<group>"; }; @@ -161,6 +159,7 @@ 776D4EE62A0FD96C00C8BD91 /* MessageInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageInputView.swift; sourceTree = "<group>"; }; 776D4EE82A0FDF5D00C8BD91 /* BubbleMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BubbleMessageCell.swift; sourceTree = "<group>"; }; 776D4EEC2A0FE1F100C8BD91 /* QuoteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuoteView.swift; sourceTree = "<group>"; }; + 776D4EEE2A0FE31800C8BD91 /* MessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCell.swift; sourceTree = "<group>"; }; 7785605E2343D24E00BB37A2 /* NSData+Hex.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSData+Hex.h"; sourceTree = "<group>"; }; 7785605F2343D24E00BB37A2 /* NSData+Hex.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSData+Hex.m"; sourceTree = "<group>"; }; 778560612344CF6F00BB37A2 /* JuickNavigationController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JuickNavigationController.h; sourceTree = "<group>"; }; @@ -413,8 +412,7 @@ 773E6391204BCAD6008B8F8D /* BubbleMessageCell.xib */, 776D4EE42A0FD4B500C8BD91 /* ConversationCell.swift */, 773E6392204BCAD6008B8F8D /* ConversationCell.xib */, - 776C41BA1FD3EF180063B82E /* MessageCell.h */, - 776C41BB1FD3EF180063B82E /* MessageCell.m */, + 776D4EEE2A0FE31800C8BD91 /* MessageCell.swift */, 776C41BC1FD3EF180063B82E /* MessageCell.xib */, 776D4EEC2A0FE1F100C8BD91 /* QuoteView.swift */, 77E61A5A1FD467FC00B4E304 /* QuoteView.xib */, @@ -597,6 +595,7 @@ 773E639D204BD0F2008B8F8D /* Chat.m in Sources */, 77B8DCD62093FC03000DBB04 /* BlogViewController.m in Sources */, 773E639A204BCE6D008B8F8D /* DialogsViewController.m in Sources */, + 776D4EEF2A0FE31800C8BD91 /* MessageCell.swift in Sources */, 776D4EE92A0FDF5D00C8BD91 /* BubbleMessageCell.swift in Sources */, 772B4E6C2199811E0029706E /* Entity.m in Sources */, 77B09994189D0B9900A84F59 /* UIImage+Utils.m in Sources */, @@ -612,7 +611,6 @@ 776D4EE52A0FD4B500C8BD91 /* ConversationCell.swift in Sources */, 778560602343D24E00BB37A2 /* NSData+Hex.m in Sources */, 774C98CD25126C070073C70A /* Service.swift in Sources */, - 776C41BD1FD3EF180063B82E /* MessageCell.m in Sources */, 77317BB8181BBE8500D60005 /* main.m in Sources */, 7705301B25D4414D0058DCE6 /* User+UIView.m in Sources */, 77C3648C2241B3060017522C /* DeviceRegistration.m in Sources */, diff --git a/Juick/Supporting Files/Juick-Bridging-Header.h b/Juick/Supporting Files/Juick-Bridging-Header.h index f1dc47d..ff9797d 100644 --- a/Juick/Supporting Files/Juick-Bridging-Header.h +++ b/Juick/Supporting Files/Juick-Bridging-Header.h @@ -4,5 +4,7 @@ #import "UIView+Shimmer.h" #import "Chat.h" #import "Message.h" +#import "UIImage+Utils.h" +#import "NSDate+TimeAgo.h" #import <UserNotifications/UserNotifications.h> #import "AppDelegate.h" diff --git a/Juick/Supporting Files/Juick-Prefix.pch b/Juick/Supporting Files/Juick-Prefix.pch index 957a052..f3fb327 100644 --- a/Juick/Supporting Files/Juick-Prefix.pch +++ b/Juick/Supporting Files/Juick-Prefix.pch @@ -20,4 +20,5 @@ #import "UIImage+Utils.h" #import "UIView+Shimmer.h" #import "AppDelegate.h" + #import "Juick-Swift.h" #endif diff --git a/Juick/ViewControllers/ChatViewController.m b/Juick/ViewControllers/ChatViewController.m index 76635f2..195f178 100644 --- a/Juick/ViewControllers/ChatViewController.m +++ b/Juick/ViewControllers/ChatViewController.m @@ -8,8 +8,6 @@ #import "ChatViewController.h" -#import "Juick-Swift.h" - #define kMessageInputInitialHeight 50 @interface ChatViewController () diff --git a/Juick/ViewControllers/DialogsViewController.m b/Juick/ViewControllers/DialogsViewController.m index a1950dc..963a930 100644 --- a/Juick/ViewControllers/DialogsViewController.m +++ b/Juick/ViewControllers/DialogsViewController.m @@ -9,7 +9,6 @@ #import "Chat.h" #import "DialogsViewController.h" #import "ChatViewController.h" -#import "Juick-Swift.h" @interface DialogsViewController() diff --git a/Juick/ViewControllers/FeedViewController.m b/Juick/ViewControllers/FeedViewController.m index 3575f16..2e194a3 100644 --- a/Juick/ViewControllers/FeedViewController.m +++ b/Juick/ViewControllers/FeedViewController.m @@ -9,7 +9,6 @@ #import "FeedViewController.h" #import "ThreadViewController.h" #import "BlogViewController.h" -#import "MessageCell.h" #import "LoginViewController.h" NSString * const UserNotAuthenticatedNotificationName = @"UserNotAuthenticated"; diff --git a/Juick/ViewControllers/MessagesViewController.h b/Juick/ViewControllers/MessagesViewController.h index 6d2c7e2..55a2bde 100644 --- a/Juick/ViewControllers/MessagesViewController.h +++ b/Juick/ViewControllers/MessagesViewController.h @@ -1,5 +1,5 @@ // -// MasterViewController.h +// MessagesViewController.h // Juick // // Created by Vitaly Takmazov on 26.10.13. @@ -7,7 +7,9 @@ // #import <UIKit/UIKit.h> -#import "MessageCell.h" +#import "Message.h" + +@protocol MessageCellDelegate; extern NSString* const messageCellIdentifier; diff --git a/Juick/ViewControllers/MessagesViewController.m b/Juick/ViewControllers/MessagesViewController.m index 10b7c37..8563fca 100644 --- a/Juick/ViewControllers/MessagesViewController.m +++ b/Juick/ViewControllers/MessagesViewController.m @@ -7,7 +7,6 @@ // #import "MessagesViewController.h" -#import "MessageCell.h" #import "Message.h" @@ -156,7 +155,7 @@ NSString* const messageCellIdentifier = @"messageCell"; } else { Message *msg = [self.messages objectAtIndex:indexPath.row]; MessageCell *cell = [tableView dequeueReusableCellWithIdentifier:messageCellIdentifier forIndexPath:indexPath]; - [cell configureWithMessage:msg selectable:self.shouldAllowToSelectText]; + [cell configureWithMsg:msg selectable:self.shouldAllowToSelectText]; cell.delegate = self; return cell; } diff --git a/Juick/ViewControllers/NewPostViewController.m b/Juick/ViewControllers/NewPostViewController.m index 4ab1c0b..0fc5fd7 100644 --- a/Juick/ViewControllers/NewPostViewController.m +++ b/Juick/ViewControllers/NewPostViewController.m @@ -10,8 +10,6 @@ #import "MessagesViewController.h" #import "User+UIView.h" -#import "Juick-Swift.h" - NSString * const NewMessageNotificationName = @"NewMessage"; NSString * const ReplyPostedNotificationName = @"ReplyPosted"; diff --git a/Juick/ViewControllers/ThreadViewController.m b/Juick/ViewControllers/ThreadViewController.m index fd754cf..4baeabf 100644 --- a/Juick/ViewControllers/ThreadViewController.m +++ b/Juick/ViewControllers/ThreadViewController.m @@ -8,7 +8,6 @@ #import "ThreadViewController.h" #import "NewPostViewController.h" -#import "MessageCell.h" @implementation ThreadViewController diff --git a/Juick/Views/MessageCell.h b/Juick/Views/MessageCell.h deleted file mode 100644 index f568bdc..0000000 --- a/Juick/Views/MessageCell.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// MessageCell.h -// Juick -// -// Created by Vitaly Takmazov on 03/12/2017. -// Copyright © 2017 com.juick. All rights reserved. -// - -#import <UIKit/UIKit.h> -#import "Message.h" - -@protocol MessageCellDelegate --(void) avatarClicked:(NSString *)uname; --(void) linkClicked:(NSString *)url; - -@end - -@interface MessageCell : UITableViewCell -@property (weak, nonatomic) IBOutlet UIImageView *avatar; - -@property (weak, nonatomic) IBOutlet UILabel *title; -@property (weak, nonatomic) IBOutlet UILabel *timestamp; -@property (weak, nonatomic) IBOutlet UITextView *text; -@property (weak, nonatomic) IBOutlet UIImageView *attach; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *attachmentHeight; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *attachmentWidth; -@property (weak, nonatomic) IBOutlet UILabel *summary; - -@property (nonatomic, strong) id<MessageCellDelegate> delegate; - -- (void) configureWithMessage:(Message *)msg selectable:(BOOL)selectable; -@end diff --git a/Juick/Views/MessageCell.m b/Juick/Views/MessageCell.m deleted file mode 100644 index 9b1b593..0000000 --- a/Juick/Views/MessageCell.m +++ /dev/null @@ -1,187 +0,0 @@ -// -// MessageCell.m -// Juick -// -// Created by Vitaly Takmazov on 03/12/2017. -// Copyright © 2017 com.juick. All rights reserved. -// - -#import "MessageCell.h" -#import "Entity.h" - -#import "NSDate+TimeAgo.h" - -@interface MessageCell() -@property(nonatomic, readonly) NSMutableParagraphStyle *quoteStyle; -@property(nonatomic, readonly) UIFont *boldFont; -@property(nonatomic, readonly) UIFont *italicFont; -- (void) updateAvatarWithUrl:(NSString *)avatarUrl; -@property(nonatomic, strong) NSString *attachment; -@end - -@implementation MessageCell - -const NSString *unreadMarker = @"●"; - -- (void)awakeFromNib { - [super awakeFromNib]; - self.text.dataDetectorTypes = UIDataDetectorTypeAll; - self.text.tintColor = [UIColor colorNamed:@"Title"]; - self.title.textColor = [UIColor colorNamed:@"Title"]; - self.timestamp.textColor = [UIColor colorNamed:@"Muted"]; - self.summary.textColor = [UIColor colorNamed:@"Muted"]; - UIGestureRecognizer *avatarTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(avatarClicked:)]; - [avatarTapRecognizer setEnabled:YES]; - [self.avatar addGestureRecognizer:avatarTapRecognizer]; - [self.avatar setUserInteractionEnabled:YES]; - _quoteStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; - _quoteStyle.firstLineHeadIndent = 12.0f; - _quoteStyle.headIndent = 12.0f; - _quoteStyle.paragraphSpacing = 6.0f; - UIFontDescriptor* fontDescriptor = [UIFontDescriptor - preferredFontDescriptorWithTextStyle:UIFontTextStyleBody]; - UIFontDescriptor* boldFontDescriptor = [fontDescriptor - fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold]; - _boldFont = [UIFont fontWithDescriptor:boldFontDescriptor size: 0.0]; - UIFontDescriptor* italicFontDescriptor = [fontDescriptor - fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitItalic]; - _italicFont = [UIFont fontWithDescriptor:italicFontDescriptor size: 0.0]; -} - -- (void) updateAvatarWithUrl:(NSString *)avatarUrl { - self.avatar.image = nil; - __weak UIImageView *weakAvatar = self.avatar; - [[AppDelegate shared].api fetchImageWithURL:[NSURL URLWithString:avatarUrl] callback:^(NSData *data) { - [UIView transitionWithView:weakAvatar - duration:0.3 - options:UIViewAnimationOptionTransitionCrossDissolve - animations:^{ - weakAvatar.image = [UIImage imageWithData:data]; - } completion:nil]; - }]; -} - -- (void) configureWithMessage:(Message *)msg selectable:(BOOL)selectable { - self.selectionStyle = UITableViewCellSelectionStyleNone; - [self updateAvatarWithUrl:msg.user.avatar]; - if (!msg.user.uid) { - __weak UILabel *title = self.title; - [[AppDelegate shared].api getUserByUri:msg.user.uri callback:^(User *user) { - title.text = user.uname; - NSString *avatarUrl = user.avatar ? user.avatar : [API defaultAvatarUrl]; - [self updateAvatarWithUrl:avatarUrl]; - }]; - } - if ([msg.attach length] > 0) { - self.attachment = msg.attachment.url; - CGFloat imageHeight = [msg.attachment.medium.height floatValue] / [[UIScreen mainScreen] scale]; - CGFloat imageWidth = [msg.attachment.medium.width floatValue] / [[UIScreen mainScreen] scale]; - self.attach.image = [UIImage placeholderImageWithColor:[UIColor colorNamed:@"Muted"] size:CGSizeMake(imageWidth, imageHeight)]; - - self.attachmentHeight.constant = imageHeight; - self.attachmentWidth.constant = imageWidth; - __weak UIImageView *weakAttach = self.attach; - [[AppDelegate shared].api fetchImageWithURL:[NSURL URLWithString:msg.attachment.medium.url] callback:^(NSData *data) { - [UIView transitionWithView:weakAttach - duration:0.3 - options:UIViewAnimationOptionTransitionCrossDissolve - animations:^{ - weakAttach.image = [UIImage imageWithData:data]; - } - completion:nil]; - }]; - UITapGestureRecognizer *attachmentTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(linkClicked:)]; - [attachmentTapGestureRecognizer setEnabled:YES]; - [self.attach addGestureRecognizer:attachmentTapGestureRecognizer]; - [self.attach setUserInteractionEnabled:YES]; - } else { - self.attachmentHeight.constant = 0; - self.attach.image = nil; - } - if (msg.user.premium) { - self.title.text = [NSString stringWithFormat:@"%@ ☆", msg.user.uname]; - } else { - self.title.text = msg.user.uname; - } - - self.timestamp.text = [[[AppDelegate shared].sharedDateFormatter dateFromString:msg.timestamp] timeAgo]; - NSUInteger count = [msg.repliesCount unsignedIntegerValue]; - if (count > 0) { - if ([msg.repliesBy length] > 0) { - self.summary.text = [NSString stringWithFormat:@"%@ replies by %@", msg.repliesCount, msg.repliesBy]; - } else { - self.summary.text = [NSString stringWithFormat:@"%@ replies", msg.repliesCount]; - } - } else { - self.summary.text = nil; - } - if (msg.unread) { - self.summary.text = [NSString stringWithFormat:@"%@ %@", unreadMarker, self.summary.text]; - self.summary.textColor = [UIColor colorNamed:@"Funny"]; - } else { - self.summary.textColor = [UIColor colorNamed:@"Muted"]; - } - self.text.attributedText = nil; - if (msg.text) { - [self.text setHidden:NO]; - [self.text setSelectable:selectable]; - [self.text setUserInteractionEnabled:selectable]; - NSMutableAttributedString *txt = [[NSMutableAttributedString alloc] - initWithString:msg.text - attributes:@{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleBody], - NSForegroundColorAttributeName:[UIColor colorNamed:@"Text"] - }]; - [txt beginEditing]; - for (Entity *entity in msg.entities) { - NSUInteger start = entity.start ? [entity.start unsignedIntegerValue] : 0; - NSUInteger end = entity.end ? [entity.end unsignedIntegerValue] : 0; - NSString *text = entity.text ? entity.text : @""; - NSRange currentRange = NSMakeRange(start, end - start); - [txt addAttribute:@"displayText" value:text range:currentRange]; - if ([entity.type isEqualToString:@"a"]) { - [txt addAttribute:NSLinkAttributeName value:entity.link range:currentRange]; - } - if ([entity.type isEqualToString:@"q"]) { - [txt addAttribute:NSForegroundColorAttributeName value:[UIColor colorNamed:@"Muted"] range:currentRange]; - [txt addAttribute:NSParagraphStyleAttributeName value:_quoteStyle range:currentRange]; - } - if ([entity.type isEqualToString:@"u"]) { - [txt addAttribute:NSUnderlineStyleAttributeName value:@(NSUnderlineStyleSingle) range:currentRange]; - } - - if ([entity.type isEqualToString:@"b"]) { - [txt addAttribute:NSFontAttributeName value:_boldFont range:currentRange]; - } - if ([entity.type isEqualToString:@"i"]) { - [txt addAttribute:NSFontAttributeName value:_italicFont range:currentRange]; - } - } - [txt enumerateAttribute:@"displayText" inRange:NSMakeRange(0, [txt length]) options:0 usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { - if (value) { - [txt replaceCharactersInRange:range withString:value]; - } - }]; - if ([msg.tags count] > 0) { - NSString *tagsList = [NSString stringWithFormat:@"%@\n", [msg.tags componentsJoinedByString:@", "]]; - [txt insertAttributedString:[[NSAttributedString alloc] - initWithString:tagsList - attributes:@{ - NSFontAttributeName:_italicFont, - NSForegroundColorAttributeName:[UIColor colorNamed:@"Muted"] - }] atIndex:0]; - } - [txt endEditing]; - self.text.attributedText = txt; - } else { - [self.text setHidden:YES]; - } -} - --(void) avatarClicked:(UIGestureRecognizer *)gestureRecognizer { - [self.delegate avatarClicked:self.title.text]; -} --(void) linkClicked:(UIGestureRecognizer *)gestureRecognizer { - [self.delegate linkClicked:self.attachment]; -} - -@end diff --git a/Juick/Views/MessageCell.swift b/Juick/Views/MessageCell.swift new file mode 100644 index 0000000..ebebec2 --- /dev/null +++ b/Juick/Views/MessageCell.swift @@ -0,0 +1,181 @@ +// +// MessageCell.swift +// Juick +// +// Created by Vitaly Takmazov on 13.05.2023. +// Copyright © 2023 com.juick. All rights reserved. +// + +import UIKit + +@objc protocol MessageCellDelegate { + @objc func avatarClicked(_ uname: String) + @objc func linkClicked(_ url: String) +} + +@objc +class MessageCell: UITableViewCell { + @IBOutlet weak var avatar: UIImageView! + @IBOutlet weak var message: UITextView! + @IBOutlet weak var title: UILabel! + @IBOutlet weak var timestamp: UILabel! + @IBOutlet weak var attach: UIImageView! + @IBOutlet weak var attachmentHeight: NSLayoutConstraint! + @IBOutlet weak var attachmentWidth: NSLayoutConstraint! + @IBOutlet weak var summary: UILabel! + @objc var delegate: MessageCellDelegate? + + var _quoteStyle: NSMutableParagraphStyle? + var _boldFont: UIFont? + var _italicFont: UIFont? + var attachment: String? + + @objc + func configure(msg: Message, selectable: Bool) { + updateAvatar(url: msg.user.avatar) + if (msg.user.uid == nil) { + AppDelegate.shared().api.getUserByUri(msg.user.uri) { user in + self.title.text = user?.uname + if let url = user?.avatar ?? API.defaultAvatarUrl() { + self.updateAvatar(url: url) + } + } + } + if (msg.attach != nil) { + self.attachment = msg.attachment.url + let imageHeight = msg.attachment.medium.height as! CGFloat / UIScreen.main.scale + let imageWidth = msg.attachment.medium.width as! CGFloat / UIScreen.main.scale + self.attach.image = UIImage.placeholderImage(with: UIColor(named: "Muted"), size: CGSizeMake(imageWidth, imageHeight)) + + self.attachmentHeight.constant = imageHeight + self.attachmentWidth.constant = imageWidth + AppDelegate.shared().api.fetchImage(with: URL(string: msg.attachment.medium.url)) { data in + if let imageData = data { + UIView.transition(with: self.attach, duration: 0.3, options: .transitionCrossDissolve, animations: { + self.attach.image = UIImage(data: imageData) + }) + } + } + let attachmentTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(linkClicked(_:))) + attachmentTapGestureRecognizer.isEnabled = true + self.attach.addGestureRecognizer(attachmentTapGestureRecognizer) + self.attach.isUserInteractionEnabled = true + } else { + self.attachmentHeight.constant = 0 + self.attach.image = nil + } + if (msg.user.premium) { + self.title.text = "\(msg.user.uname!) ☆" + } else { + self.title.text = msg.user.uname! + } + + self.timestamp.text = (AppDelegate.shared().sharedDateFormatter.date(from: msg.timestamp)! as NSDate).timeAgo() + let count = msg.repliesCount?.uintValue ?? 0 + if (count > 0) { + if (msg.repliesBy != nil) { + self.summary.text = "\(msg.repliesCount!) replies by \(msg.repliesBy!)" + } else { + self.summary.text = "\(msg.repliesCount!) replies" + } + } else { + self.summary.text = nil + } + if (msg.unread) { + self.summary.text = "\(unreadMarker) \(self.summary.text!)" + self.summary.textColor = UIColor(named: "Funny") + } else { + self.summary.textColor = UIColor(named: "Muted") + } + self.message.attributedText = nil + if (msg.text != nil) { + self.message.isHidden = false + self.message.isSelectable = selectable + self.message.isUserInteractionEnabled = selectable + let txt = NSMutableAttributedString(string: msg.text, attributes: [ + NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .body), + NSAttributedString.Key.foregroundColor: UIColor(named: "Text")! + ]) + txt.beginEditing() + for entity in msg.entities { + let start = entity.start?.intValue ?? 0 + let end = entity.end?.intValue ?? 0 + let text = entity.text ?? "" + let currentRange = NSMakeRange(start, end - start) + txt.addAttribute(NSAttributedString.Key(rawValue: "displayText"), value: text, range: currentRange) + if entity.type == "a" { + txt.addAttribute(NSAttributedString.Key.link, value:entity.link!, range:currentRange) + } + if entity.type == "q" { + txt.addAttribute(NSAttributedString.Key.foregroundColor, value:UIColor(named: "Muted")!, range:currentRange) + txt.addAttribute(NSAttributedString.Key.paragraphStyle, value:_quoteStyle!, range:currentRange) + } + if entity.type == "u" { + txt.addAttribute(NSAttributedString.Key.underlineStyle, value:NSUnderlineStyle.single, range:currentRange) + } + + if entity.type == "b" { + txt.addAttribute(NSAttributedString.Key.font, value:_boldFont!, range:currentRange) + } + if entity.type == "i" { + txt.addAttribute(NSAttributedString.Key.font, value:_italicFont!, range:currentRange) + } + } + txt.enumerateAttribute(NSAttributedString.Key(rawValue: "displayText"), in: NSMakeRange(0, txt.length)) { value, range, stop in + if let text = value as? String { + txt.replaceCharacters(in: range, with:text) + } + } + + if msg.tags != nil { + let tagsList = "\(msg.tags.joined(separator: ", "))\n" + txt.insert(NSAttributedString(string: tagsList, attributes: [ + NSAttributedString.Key.font: _italicFont!, + NSAttributedString.Key.foregroundColor: UIColor(named: "Muted")! + ]), at: 0) + } + txt.endEditing() + self.message.attributedText = txt + } else { + self.message.isHidden = true + } + } + + let unreadMarker = "●" + + func updateAvatar(url: String) { + self.avatar.image = nil + AppDelegate.shared().api.fetchImage(with: URL(string: url)) { data in + if let imageData = data { + UIView.transition(with: self.avatar, duration: 0.3, options: .transitionCrossDissolve, animations: { + self.avatar.image = UIImage(data: imageData) + }) + } + } + } + + @objc func avatarClicked(_ sender: UITapGestureRecognizer) { + if let name = self.title.text { + self.delegate?.avatarClicked(name) + } + } + @objc func linkClicked(_ sender: UITapGestureRecognizer){ + self.delegate?.linkClicked(self.attachment!) + } + + override func awakeFromNib() { + let avatarTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(avatarClicked(_:))) + avatarTapRecognizer.isEnabled = true + self.avatar.addGestureRecognizer(avatarTapRecognizer) + self.avatar.isUserInteractionEnabled = true + _quoteStyle = NSParagraphStyle.default.mutableCopy() as? NSMutableParagraphStyle + _quoteStyle?.firstLineHeadIndent = 12.0 + _quoteStyle?.headIndent = 12.0 + _quoteStyle?.paragraphSpacing = 6.0 + let fontDescriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body) + guard let boldFontDescriptor = fontDescriptor.withSymbolicTraits(.traitBold) else { fatalError("Font error") } + _boldFont = UIFont(descriptor: boldFontDescriptor, size: 0.0) + guard let italicFontDescriptor = fontDescriptor.withSymbolicTraits(.traitItalic) else { fatalError("Font error") } + _italicFont = UIFont(descriptor: italicFontDescriptor, size: 0.0) + } +} diff --git a/Juick/Views/MessageCell.xib b/Juick/Views/MessageCell.xib index 5d35561..9f4bb77 100644 --- a/Juick/Views/MessageCell.xib +++ b/Juick/Views/MessageCell.xib @@ -11,7 +11,7 @@ <objects> <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/> <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/> - <tableViewCell contentMode="scaleToFill" selectionStyle="gray" indentationWidth="10" reuseIdentifier="messageCell" rowHeight="604" id="KGk-i7-Jjw" customClass="MessageCell"> + <tableViewCell contentMode="scaleToFill" selectionStyle="gray" indentationWidth="10" reuseIdentifier="messageCell" rowHeight="604" id="KGk-i7-Jjw" customClass="MessageCell" customModule="Juick" customModuleProvider="target"> <rect key="frame" x="0.0" y="0.0" width="572" height="604"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM"> @@ -31,16 +31,16 @@ <stackView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="252" axis="vertical" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="o3h-ci-LWF"> <rect key="frame" x="56" y="16" width="476" height="44.5"/> <subviews> - <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="CHd-h0-hPk"> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="ugnich" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="CHd-h0-hPk"> <rect key="frame" x="0.0" y="0.0" width="476" height="20.5"/> <fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/> - <nil key="textColor"/> + <color key="textColor" name="Title"/> <nil key="highlightedColor"/> </label> - <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="252" verticalCompressionResistancePriority="751" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="AVu-qV-q84"> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="252" verticalCompressionResistancePriority="751" text="moments ago" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="AVu-qV-q84"> <rect key="frame" x="0.0" y="28.5" width="476" height="16"/> <fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/> - <nil key="textColor"/> + <color key="textColor" name="Muted"/> <nil key="highlightedColor"/> </label> </subviews> @@ -51,6 +51,7 @@ <rect key="frame" x="20" y="104" width="532" height="192"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <string key="text">Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda.</string> + <color key="textColor" name="Text"/> <fontDescription key="fontDescription" style="UICTFontTextStyleBody"/> <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/> </textView> @@ -61,14 +62,15 @@ <constraint firstAttribute="width" priority="999" constant="256" identifier="attachWidth" id="oJg-1R-jp3"/> </constraints> </imageView> - <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="aJE-mZ-MCS"> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="12 replies" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="aJE-mZ-MCS"> <rect key="frame" x="20" y="568" width="532" height="16"/> <fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/> - <nil key="textColor"/> + <color key="textColor" name="Muted"/> <nil key="highlightedColor"/> </label> </subviews> <color key="backgroundColor" name="Background"/> + <gestureRecognizers/> <constraints> <constraint firstItem="nZn-a4-oSt" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="20" symbolic="YES" id="0zm-OK-XwQ"/> <constraint firstItem="nZn-a4-oSt" firstAttribute="top" secondItem="7FT-kJ-84d" secondAttribute="bottom" constant="8" symbolic="YES" id="4aL-eh-Hr1"/> @@ -90,8 +92,8 @@ <outlet property="attachmentHeight" destination="Cuq-dQ-VYU" id="5DR-sP-vQI"/> <outlet property="attachmentWidth" destination="oJg-1R-jp3" id="ImJ-1g-7NW"/> <outlet property="avatar" destination="CZk-Q8-JqS" id="0Jh-5F-W9D"/> + <outlet property="message" destination="nZn-a4-oSt" id="Bee-gX-BGk"/> <outlet property="summary" destination="aJE-mZ-MCS" id="lWR-A2-40d"/> - <outlet property="text" destination="nZn-a4-oSt" id="Bee-gX-BGk"/> <outlet property="timestamp" destination="AVu-qV-q84" id="Cyn-0y-V0R"/> <outlet property="title" destination="CHd-h0-hPk" id="BuC-dv-0sU"/> </connections> @@ -102,5 +104,14 @@ <namedColor name="Background"> <color red="0.99215686274509807" green="0.99215686274509807" blue="0.99607843137254903" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> </namedColor> + <namedColor name="Muted"> + <color red="0.53333333333333333" green="0.58431372549019611" blue="0.55294117647058827" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> + </namedColor> + <namedColor name="Text"> + <color red="0.13333333333333333" green="0.13333333333333333" blue="0.13333333333333333" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> + </namedColor> + <namedColor name="Title"> + <color red="0.23529411764705882" green="0.46666666666666667" blue="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> + </namedColor> </resources> </document> |