diff --git a/NextcloudTalk.xcodeproj/project.pbxproj b/NextcloudTalk.xcodeproj/project.pbxproj index 90f19a61..79490fd0 100644 --- a/NextcloudTalk.xcodeproj/project.pbxproj +++ b/NextcloudTalk.xcodeproj/project.pbxproj @@ -246,6 +246,12 @@ 2CC007CA20E125C20096D91F /* NewRoomTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2CC007C820E125C20096D91F /* NewRoomTableViewController.m */; }; 2CC007CB20E125C20096D91F /* NewRoomTableViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2CC007C920E125C20096D91F /* NewRoomTableViewController.xib */; }; 2CC007CE20E50B0A0096D91F /* MessageBodyTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2CC007CD20E50B0A0096D91F /* MessageBodyTextView.m */; }; + 2CC32E8D27F4540E00BB8C39 /* ReactionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC32E8C27F4540E00BB8C39 /* ReactionsView.swift */; }; + 2CC32E9227F45AE000BB8C39 /* ReactionsViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC32E9027F45AE000BB8C39 /* ReactionsViewCell.swift */; }; + 2CC32E9327F45AE000BB8C39 /* ReactionsViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2CC32E9127F45AE000BB8C39 /* ReactionsViewCell.xib */; }; + 2CC32E9827F5D9BD00BB8C39 /* NCChatReaction.m in Sources */ = {isa = PBXBuildFile; fileRef = 2CC32E9727F5D9BD00BB8C39 /* NCChatReaction.m */; }; + 2CC32E9927F5DADA00BB8C39 /* NCChatReaction.m in Sources */ = {isa = PBXBuildFile; fileRef = 2CC32E9727F5D9BD00BB8C39 /* NCChatReaction.m */; }; + 2CC32E9A27F5DADB00BB8C39 /* NCChatReaction.m in Sources */ = {isa = PBXBuildFile; fileRef = 2CC32E9727F5D9BD00BB8C39 /* NCChatReaction.m */; }; 2CC5F0982716FF1900DE1775 /* NCCommunication in Frameworks */ = {isa = PBXBuildFile; productRef = 2CC5F0972716FF1900DE1775 /* NCCommunication */; }; 2CC5F09A2717028B00DE1775 /* NCCommunication in Frameworks */ = {isa = PBXBuildFile; productRef = 2CC5F0992717028B00DE1775 /* NCCommunication */; }; 2CC5F09C2717198500DE1775 /* NCCommunication in Frameworks */ = {isa = PBXBuildFile; productRef = 2CC5F09B2717198500DE1775 /* NCCommunication */; }; @@ -714,6 +720,11 @@ 2CC007C920E125C20096D91F /* NewRoomTableViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NewRoomTableViewController.xib; sourceTree = ""; }; 2CC007CC20E50B0A0096D91F /* MessageBodyTextView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MessageBodyTextView.h; sourceTree = ""; }; 2CC007CD20E50B0A0096D91F /* MessageBodyTextView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MessageBodyTextView.m; sourceTree = ""; }; + 2CC32E8C27F4540E00BB8C39 /* ReactionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsView.swift; sourceTree = ""; }; + 2CC32E9027F45AE000BB8C39 /* ReactionsViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsViewCell.swift; sourceTree = ""; }; + 2CC32E9127F45AE000BB8C39 /* ReactionsViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ReactionsViewCell.xib; sourceTree = ""; }; + 2CC32E9627F5D9BD00BB8C39 /* NCChatReaction.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NCChatReaction.h; sourceTree = ""; }; + 2CC32E9727F5D9BD00BB8C39 /* NCChatReaction.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NCChatReaction.m; sourceTree = ""; }; 2CC7158820B837140045C789 /* PlaceholderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PlaceholderView.xib; sourceTree = ""; }; 2CC7158A20B8394A0045C789 /* PlaceholderView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PlaceholderView.h; sourceTree = ""; }; 2CC7158B20B8394A0045C789 /* PlaceholderView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PlaceholderView.m; sourceTree = ""; }; @@ -1177,6 +1188,9 @@ 2CA52AC92670D02800619610 /* VoiceMessageRecordingView.h */, 2CA52ACA2670D02800619610 /* VoiceMessageRecordingView.m */, 2CA52ACC2670D07900619610 /* VoiceMessageRecordingView.xib */, + 2CC32E8C27F4540E00BB8C39 /* ReactionsView.swift */, + 2CC32E9027F45AE000BB8C39 /* ReactionsViewCell.swift */, + 2CC32E9127F45AE000BB8C39 /* ReactionsViewCell.xib */, ); name = "Chat views"; sourceTree = ""; @@ -1413,6 +1427,8 @@ 2C6DEAB3243CCCCA00AE8437 /* Chat views */, 2CA1553F208E350300CE8EF0 /* NCChatMessage.h */, 2CA15540208E350300CE8EF0 /* NCChatMessage.m */, + 2CC32E9627F5D9BD00BB8C39 /* NCChatReaction.h */, + 2CC32E9727F5D9BD00BB8C39 /* NCChatReaction.m */, 2C42ADB220B58E6300296DEA /* NCChatController.h */, 2C42ADB320B58E6300296DEA /* NCChatController.m */, 1FEDE3C5257D439500853F79 /* NCChatFileController.h */, @@ -1610,6 +1626,7 @@ buildActionMask = 2147483647; files = ( 2CC007BE20D8F24B0096D91F /* RoomCreation2TableViewController.xib in Resources */, + 2CC32E9327F45AE000BB8C39 /* ReactionsViewCell.xib in Resources */, 2C330372255E6EBC00BDB4E4 /* InfoPlist.strings in Resources */, 2C78EFA11F828C41008AFA74 /* CallViewController.xib in Resources */, 2C3780C5210F4A26003F9AE8 /* HeaderWithButton.xib in Resources */, @@ -1838,6 +1855,7 @@ 2C78EF9C1F826B22008AFA74 /* NCCallController.m in Sources */, 2C4D7D761F30F7B600FF4A0D /* ARDUtilities.m in Sources */, 2CB6ACE92641954700D3D641 /* MapViewController.m in Sources */, + 2CC32E9227F45AE000BB8C39 /* ReactionsViewCell.swift in Sources */, 2CBF82AE1FC888FC00636459 /* NCPushNotification.m in Sources */, 2CC7159420C54D080045C789 /* ChatTableViewCell.m in Sources */, 2CA1CCAA1F02D1A4002FE6A2 /* NCAPIController.m in Sources */, @@ -1867,6 +1885,7 @@ 2C4446F0265D454200DF1DBC /* NotificationCenterNotifications.m in Sources */, 1F3D3B22255F109E00230DAE /* BarButtonItemWithActivity.m in Sources */, 2C0574821EDD9E8E00D9E7F2 /* main.m in Sources */, + 2CC32E9827F5D9BD00BB8C39 /* NCChatReaction.m in Sources */, 2C40281522832EED0000DDFC /* NCDatabaseManager.m in Sources */, 2CC007B820D8139D0096D91F /* RoomCreationTableViewController.m in Sources */, DA7558132790D65700A48A1B /* AccountTableViewCell.swift in Sources */, @@ -1924,6 +1943,7 @@ 2C1ABDCE257E939600AEDFB6 /* NCContact.m in Sources */, 2C7A12422017872600864818 /* AddParticipantsTableViewController.m in Sources */, 2C43BA7621309A1000B3068A /* NCMessageParameter.m in Sources */, + 2CC32E8D27F4540E00BB8C39 /* ReactionsView.swift in Sources */, 2C4DE9F221F732B40096940D /* NCAudioController.m in Sources */, 2C8A2BC9221F094F00DE6D2C /* DirectoryTableViewController.m in Sources */, 2C42ADB420B58E6300296DEA /* NCChatController.m in Sources */, @@ -1986,6 +2006,7 @@ 2C62AFB924C1A4E6007E460A /* ShareViewController.m in Sources */, 2C4446DF2658158000DF1DBC /* NCChatBlock.m in Sources */, 2C62B00924C1BDBD007E460A /* NCAPISessionManager.m in Sources */, + 2CC32E9A27F5DADB00BB8C39 /* NCChatReaction.m in Sources */, 2C62AFBB24C1B7B1007E460A /* NCDatabaseManager.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2024,6 +2045,7 @@ 2C444704265D641300DF1DBC /* NCUserDefaults.m in Sources */, 2CC001B724A37A9A00A20167 /* NCUser.m in Sources */, 2CC0016124A25B5500A20167 /* NCAPIController.m in Sources */, + 2CC32E9927F5DADA00BB8C39 /* NCChatReaction.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/NextcloudTalk/ChatMessageTableViewCell.m b/NextcloudTalk/ChatMessageTableViewCell.m index 8e777488..2f0f70ab 100644 --- a/NextcloudTalk/ChatMessageTableViewCell.m +++ b/NextcloudTalk/ChatMessageTableViewCell.m @@ -27,14 +27,20 @@ #import "UIImageView+AFNetworking.h" #import "UIImageView+Letters.h" +#import "NextcloudTalk-Swift.h" + #import "NCAPIController.h" #import "NCAppBranding.h" +#import "NCChatMessage.h" #import "NCDatabaseManager.h" #import "NCUtils.h" #import "QuotedMessageView.h" @interface ChatMessageTableViewCell () @property (nonatomic, strong) UIView *quoteContainerView; +@property (nonatomic, strong) ReactionsView *reactionsView; +@property (nonatomic, strong) NSArray *vConstraint1; +@property (nonatomic, strong) NSArray *vConstraint2; @end @implementation ChatMessageTableViewCell @@ -83,6 +89,8 @@ [self.quoteContainerView addGestureRecognizer:quoteTap]; } + [self.contentView addSubview:self.reactionsView]; + NSDictionary *views = @{@"avatarView": self.avatarView, @"userStatusImageView": self.userStatusImageView, @"statusView": self.statusView, @@ -90,7 +98,8 @@ @"dateLabel": self.dateLabel, @"bodyTextView": self.bodyTextView, @"quoteContainerView": self.quoteContainerView, - @"quotedMessageView": self.quotedMessageView + @"quotedMessageView": self.quotedMessageView, + @"reactionsView": self.reactionsView }; NSDictionary *metrics = @{@"avatarSize": @(kChatCellAvatarHeight), @@ -104,9 +113,12 @@ if ([self.reuseIdentifier isEqualToString:ChatMessageCellIdentifier]) { [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-right-[avatarView(avatarSize)]-right-[titleLabel]-[dateLabel(dateLabelWidth)]-right-|" options:0 metrics:metrics views:views]]; [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-right-[avatarView(avatarSize)]-right-[bodyTextView(>=0)]-right-|" options:0 metrics:metrics views:views]]; + [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-right-[avatarView(avatarSize)]-right-[reactionsView(>=0)]-right-|" options:0 metrics:metrics views:views]]; [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-padding-[statusView(statusSize)]-padding-[bodyTextView(>=0)]-right-|" options:0 metrics:metrics views:views]]; - [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-right-[titleLabel(avatarSize)]-left-[bodyTextView(>=0@999)]-left-|" options:0 metrics:metrics views:views]]; - [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-right-[dateLabel(avatarSize)]-left-[bodyTextView(>=0@999)]-left-|" options:0 metrics:metrics views:views]]; + _vConstraint1 = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-right-[titleLabel(avatarSize)]-left-[bodyTextView(>=0@999)]-0-[reactionsView(0)]-left-|" options:0 metrics:metrics views:views]; + [self.contentView addConstraints:_vConstraint1]; + _vConstraint2 = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-right-[dateLabel(avatarSize)]-left-[bodyTextView(>=0@999)]-0-[reactionsView(0)]-left-|" options:0 metrics:metrics views:views]; + [self.contentView addConstraints:_vConstraint2]; [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-right-[titleLabel(avatarSize)]-left-[statusView(statusSize)]-(>=0)-|" options:0 metrics:metrics views:views]]; } else if ([self.reuseIdentifier isEqualToString:ReplyMessageCellIdentifier]) { [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-right-[avatarView(avatarSize)]-right-[titleLabel]-[dateLabel(dateLabelWidth)]-right-|" options:0 metrics:metrics views:views]]; @@ -151,6 +163,10 @@ self.quotedMessageView.actorLabel.text = @""; self.quotedMessageView.messageLabel.text = @""; + self.reactionsView.reactions = @[]; + _vConstraint1[5].constant = 0; + _vConstraint2[5].constant = 0; + [self.avatarView cancelImageDownloadTask]; self.avatarView.image = nil; self.avatarView.contentMode = UIViewContentModeScaleToFill; @@ -219,6 +235,17 @@ return _dateLabel; } +- (ReactionsView *)reactionsView +{ + if (!_reactionsView) { + UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; + flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; + _reactionsView = [[ReactionsView alloc] initWithFrame:CGRectMake(0, 0, 50, 50) collectionViewLayout:flowLayout]; + _reactionsView.translatesAutoresizingMaskIntoConstraints = NO; + } + return _reactionsView; +} + - (UIView *)quoteContainerView { if (!_quoteContainerView) { @@ -270,10 +297,12 @@ [self setBotAvatar]; } } else { - [self.avatarView setImageWithURLRequest:[[NCAPIController sharedInstance] createAvatarRequestForUser:message.actorId - andSize:96 - usingAccount:activeAccount] - placeholderImage:nil success:nil failure:nil]; + [self.avatarView + setImageWithURLRequest:[[NCAPIController sharedInstance] + createAvatarRequestForUser:message.actorId + andSize:96 + usingAccount:activeAccount] + placeholderImage:nil success:nil failure:nil]; } // This check is just a workaround to fix the issue with the deleted parents returned by the API. @@ -305,6 +334,13 @@ self.bodyTextView.textColor = [UIColor tertiaryLabelColor]; } } + + const NSArray *reactions = message.reactionsArray; + [self.reactionsView updateReactionsWithReactions:reactions]; + if (reactions.count > 0) { + _vConstraint1[5].constant = 40; + _vConstraint2[5].constant = 40; + } } - (void)setGuestAvatar:(NSString *)displayName diff --git a/NextcloudTalk/GroupedChatMessageTableViewCell.h b/NextcloudTalk/GroupedChatMessageTableViewCell.h index 57411432..cb5c71de 100644 --- a/NextcloudTalk/GroupedChatMessageTableViewCell.h +++ b/NextcloudTalk/GroupedChatMessageTableViewCell.h @@ -21,6 +21,9 @@ */ #import + +#import "NextcloudTalk-Swift.h" + #import "ChatTableViewCell.h" #import "NCChatMessage.h" #import "MessageBodyTextView.h" @@ -32,6 +35,8 @@ static NSString *GroupedChatMessageCellIdentifier = @"GroupedChatMessageCellIden @property (nonatomic, strong) MessageBodyTextView *bodyTextView; @property (nonatomic, strong) UIView *statusView; +@property (nonatomic, strong) ReactionsView *reactionsView; +@property (nonatomic, strong) NSArray *vConstraint; + (CGFloat)defaultFontSize; - (void)setupForMessage:(NCChatMessage *)message withLastCommonReadMessage:(NSInteger)lastCommonRead; diff --git a/NextcloudTalk/GroupedChatMessageTableViewCell.m b/NextcloudTalk/GroupedChatMessageTableViewCell.m index 25e1df73..8a1a049f 100644 --- a/NextcloudTalk/GroupedChatMessageTableViewCell.m +++ b/NextcloudTalk/GroupedChatMessageTableViewCell.m @@ -47,9 +47,11 @@ _statusView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, kChatCellStatusViewHeight, kChatCellStatusViewHeight)]; _statusView.translatesAutoresizingMaskIntoConstraints = NO; [self.contentView addSubview:_statusView]; + [self.contentView addSubview:self.reactionsView]; NSDictionary *views = @{@"bodyTextView": self.bodyTextView, - @"statusView": self.statusView + @"statusView": self.statusView, + @"reactionsView": self.reactionsView }; NSDictionary *metrics = @{@"avatar": @50, @@ -61,7 +63,9 @@ [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-avatar-[bodyTextView(>=0)]-right-|" options:0 metrics:metrics views:views]]; [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-padding-[statusView(statusSize)]-padding-[bodyTextView(>=0)]-right-|" options:0 metrics:metrics views:views]]; - [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-left-[bodyTextView(>=0@999)]-left-|" options:0 metrics:metrics views:views]]; + [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-padding-[statusView(statusSize)]-padding-[reactionsView(>=0)]-right-|" options:0 metrics:metrics views:views]]; + _vConstraint = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-left-[bodyTextView(>=0@999)]-0-[reactionsView(0)]-left-|" options:0 metrics:metrics views:views]; + [self.contentView addConstraints:_vConstraint]; [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-left-[statusView(statusSize)]-(>=0)-|" options:0 metrics:metrics views:views]]; } @@ -75,6 +79,9 @@ self.bodyTextView.text = @""; + self.reactionsView.reactions = @[]; + _vConstraint[3].constant = 0; + self.statusView.hidden = NO; [self.statusView.subviews makeObjectsPerformSelector: @selector(removeFromSuperview)]; } @@ -111,6 +118,11 @@ self.bodyTextView.textColor = [UIColor tertiaryLabelColor]; } } + const NSArray *reactions = message.reactionsArray; + [self.reactionsView updateReactionsWithReactions:reactions]; + if (reactions.count > 0) { + _vConstraint[3].constant = 40; + } } - (void)setDeliveryState:(ChatMessageDeliveryState)state @@ -153,6 +165,17 @@ return _bodyTextView; } +- (ReactionsView *)reactionsView +{ + if (!_reactionsView) { + UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; + flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; + _reactionsView = [[ReactionsView alloc] initWithFrame:CGRectMake(0, 0, 50, 50) collectionViewLayout:flowLayout]; + _reactionsView.translatesAutoresizingMaskIntoConstraints = NO; + } + return _reactionsView; +} + + (CGFloat)defaultFontSize { CGFloat pointSize = 16.0; diff --git a/NextcloudTalk/NCChatController.m b/NextcloudTalk/NCChatController.m index 7cfc5ad7..b693ecdd 100644 --- a/NextcloudTalk/NCChatController.m +++ b/NextcloudTalk/NCChatController.m @@ -376,7 +376,7 @@ NSString * const NCChatControllerDidReceiveCallEndedMessageNotification userInfo:userInfo]; } // Notify if "deleted messages" have been received - if ([message.systemMessage isEqualToString:@"message_deleted"] || [message.systemMessage isEqualToString:@"reaction"]) { + if ([message.systemMessage isEqualToString:@"message_deleted"] || [message.systemMessage isEqualToString:@"reaction"] || [message.systemMessage isEqualToString:@"reaction_revoked"]) { [userInfo setObject:message forKey:@"updateMessage"]; [[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveUpdateMessageNotification object:self @@ -624,8 +624,10 @@ NSString * const NCChatControllerDidReceiveCallEndedMessageNotification object:self userInfo:userInfo]; } - // Notify if "deleted messages" have been received - if ([message.systemMessage isEqualToString:@"message_deleted"] || [message.systemMessage isEqualToString:@"reaction"]) { + // Notify if an "update messages" have been received + if ([message.systemMessage isEqualToString:@"message_deleted"] || + [message.systemMessage isEqualToString:@"reaction"] || + [message.systemMessage isEqualToString:@"reaction_revoked"]) { [userInfo setObject:message forKey:@"updateMessage"]; [[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveUpdateMessageNotification object:self diff --git a/NextcloudTalk/NCChatMessage.h b/NextcloudTalk/NCChatMessage.h index 72c04e49..0437dd56 100644 --- a/NextcloudTalk/NCChatMessage.h +++ b/NextcloudTalk/NCChatMessage.h @@ -23,6 +23,7 @@ #import #import #import +#import "NCChatReaction.h" #import "NCDatabaseManager.h" #import "NCMessageParameter.h" #import "NCMessageFileParameter.h" @@ -37,11 +38,6 @@ extern NSString * const kMessageTypeSystem; extern NSString * const kMessageTypeCommand; extern NSString * const kMessageTypeVoiceMessage; -@interface NCChatReaction : RLMObject -@property (nonatomic, strong) NSString *reaction; -@property (nonatomic, assign) NSInteger count; -@end - RLM_ARRAY_TYPE(NCChatReaction) @interface NCChatMessage : RLMObject @@ -81,5 +77,6 @@ RLM_ARRAY_TYPE(NCChatReaction) - (NSMutableAttributedString *)parsedMessage; - (NSMutableAttributedString *)systemMessageFormat; - (NCChatMessage *)parent; +- (NSArray *)reactionsArray; @end diff --git a/NextcloudTalk/NCChatMessage.m b/NextcloudTalk/NCChatMessage.m index f02de45f..fc258f21 100644 --- a/NextcloudTalk/NCChatMessage.m +++ b/NextcloudTalk/NCChatMessage.m @@ -41,9 +41,6 @@ NSString * const kMessageTypeVoiceMessage = @"voice-message"; @end -@implementation NCChatReaction -@end - @implementation NCChatMessage + (instancetype)messageWithDictionary:(NSDictionary *)messageDict @@ -93,9 +90,7 @@ NSString * const kMessageTypeVoiceMessage = @"voice-message"; NSDictionary *reactionsDict = reactions; NSMutableArray *reactionsArray = [NSMutableArray new]; for (NSString *reactionKey in reactionsDict.allKeys) { - NCChatReaction *reaction = [[NCChatReaction alloc] init]; - reaction.reaction = reactionKey; - reaction.count = [[reactionsDict objectForKey:reactionKey] integerValue]; + NCChatReaction *reaction = [NCChatReaction initWithReaction:reactionKey andCount:[[reactionsDict objectForKey:reactionKey] integerValue]]; [reactionsArray addObject:reaction]; } message.reactions = (RLMArray *)reactionsArray; @@ -374,4 +369,13 @@ NSString * const kMessageTypeVoiceMessage = @"voice-message"; return nil; } +- (NSArray *)reactionsArray +{ + NSMutableArray *reactions = [NSMutableArray new]; + for (NCChatReaction *reaction in _reactions) { + [reactions addObject:reaction]; + } + return reactions; +} + @end diff --git a/NextcloudTalk/NCChatReaction.h b/NextcloudTalk/NCChatReaction.h new file mode 100644 index 00000000..30883760 --- /dev/null +++ b/NextcloudTalk/NCChatReaction.h @@ -0,0 +1,34 @@ +/** + * @copyright Copyright (c) 2022 Ivan Sein + * + * @author Ivan Sein + * + * @license GNU GPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#import + + +@interface NCChatReaction : RLMObject + +@property (nonatomic, strong) NSString *reaction; +@property (nonatomic, assign) NSInteger count; + ++ (instancetype)initWithReaction:(NSString *)reaction andCount:(NSInteger)count; + +@end + diff --git a/NextcloudTalk/NCChatReaction.m b/NextcloudTalk/NCChatReaction.m new file mode 100644 index 00000000..ba935232 --- /dev/null +++ b/NextcloudTalk/NCChatReaction.m @@ -0,0 +1,35 @@ +/** + * @copyright Copyright (c) 2022 Ivan Sein + * + * @author Ivan Sein + * + * @license GNU GPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#import "NCChatReaction.h" + +@implementation NCChatReaction + ++ (instancetype)initWithReaction:(NSString *)reaction andCount:(NSInteger)count +{ + NCChatReaction *reactionObject = [[NCChatReaction alloc] init]; + reactionObject.reaction = reaction; + reactionObject.count = count; + return reactionObject; +} + +@end diff --git a/NextcloudTalk/NCChatViewController.m b/NextcloudTalk/NCChatViewController.m index f2351b80..247e766b 100644 --- a/NextcloudTalk/NCChatViewController.m +++ b/NextcloudTalk/NCChatViewController.m @@ -3041,7 +3041,9 @@ NSString * const NCChatViewControllerTalkToUserNotification = @"NCChatViewContro return separatorCell; } if (message.isSystemMessage) { - if ([message.systemMessage isEqualToString:@"message_deleted"] || [message.systemMessage isEqualToString:@"reaction"]) { + if ([message.systemMessage isEqualToString:@"message_deleted"] || + [message.systemMessage isEqualToString:@"reaction"] || + [message.systemMessage isEqualToString:@"reaction_revoked"]) { return (SystemMessageTableViewCell *)[self.tableView dequeueReusableCellWithIdentifier:InvisibleSystemMessageCellIdentifier]; } SystemMessageTableViewCell *systemCell = (SystemMessageTableViewCell *)[self.tableView dequeueReusableCellWithIdentifier:SystemMessageCellIdentifier]; @@ -3129,8 +3131,11 @@ NSString * const NCChatViewControllerTalkToUserNotification = @"NCChatViewContro return kMessageSeparatorCellHeight; } - // Message deleted (the ones that notify about a deleted message, they should not be displayed) - if (message.message.length == 0 || [message.systemMessage isEqualToString:@"message_deleted"] || [message.systemMessage isEqualToString:@"reaction"]) { + // Update messages (the ones that notify about an update in one message, they should not be displayed) + if (message.message.length == 0 || + [message.systemMessage isEqualToString:@"message_deleted"] || + [message.systemMessage isEqualToString:@"reaction"] || + [message.systemMessage isEqualToString:@"reaction_revoked"]) { return 0.0; } @@ -3146,6 +3151,10 @@ NSString * const NCChatViewControllerTalkToUserNotification = @"NCChatViewContro height = kChatMessageCellMinimumHeight; } + if (message.reactionsArray.count > 0) { + height += 40; // reactionsView(40) + } + if (message.parent) { height += 55; // left(5) + quoteView(50) return height; @@ -3157,6 +3166,10 @@ NSString * const NCChatViewControllerTalkToUserNotification = @"NCChatViewContro if (height < kGroupedChatMessageCellMinimumHeight) { height = kGroupedChatMessageCellMinimumHeight; } + + if (message.reactionsArray.count > 0) { + height += 40; // reactionsView(40) + } } // Voice message should be before message.file check since it contains a file diff --git a/NextcloudTalk/ReactionsView.swift b/NextcloudTalk/ReactionsView.swift new file mode 100644 index 00000000..8da55ed2 --- /dev/null +++ b/NextcloudTalk/ReactionsView.swift @@ -0,0 +1,81 @@ +/** + * @copyright Copyright (c) 2022 Ivan Sein + * + * @author Ivan Sein + * + * @license GNU GPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +import UIKit + +@objcMembers class ReactionsView: UICollectionView, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { + + var reactions: [NCChatReaction] = [] + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + self.dataSource = self + self.delegate = self + } + + required override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) { + super.init(frame: frame, collectionViewLayout: layout) + self.dataSource = self + self.delegate = self + self.register(UINib(nibName: "ReactionsViewCell", bundle: .main), forCellWithReuseIdentifier: "ReactionCellIdentifier") + self.backgroundColor = .clear + self.showsHorizontalScrollIndicator = false + } + + func updateReactions(reactions: [NCChatReaction]) { + self.reactions = reactions + self.reloadData() + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return reactions.count + } + + func collectionView(_ collectionView: UICollectionView, numberOfSections section: Int) -> Int { + return 1 + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + return 2 + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + return CGSize(width: 50, height: 30) + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ReactionCellIdentifier", for: indexPath) as? ReactionsViewCell + if indexPath.row < reactions.count { + cell?.setReaction(reaction: reactions[indexPath.row]) + } + return cell ?? UICollectionViewCell() + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + // TODO: Set reaction + } + + func neededHeight() -> CGFloat { + return self.collectionViewLayout.collectionViewContentSize.height + } + +} diff --git a/NextcloudTalk/ReactionsViewCell.swift b/NextcloudTalk/ReactionsViewCell.swift new file mode 100644 index 00000000..aac637ac --- /dev/null +++ b/NextcloudTalk/ReactionsViewCell.swift @@ -0,0 +1,47 @@ +/** + * @copyright Copyright (c) 2022 Ivan Sein + * + * @author Ivan Sein + * + * @license GNU GPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +import UIKit + +@objcMembers class ReactionsViewCell: UICollectionViewCell { + + @IBOutlet weak var label: UILabel! + + override func awakeFromNib() { + super.awakeFromNib() + label.layer.borderWidth = 1.0 + label.layer.borderColor = NCAppBranding.elementColor().cgColor + label.layer.cornerRadius = 15.0 + label.clipsToBounds = true + label.backgroundColor = NCAppBranding.backgroundColor() + } + + override func prepareForReuse() { + super.prepareForReuse() + label.text = "" + } + + func setReaction(reaction: NCChatReaction) { + label.text = reaction.reaction + " " + String(reaction.count) + } + +} diff --git a/NextcloudTalk/ReactionsViewCell.xib b/NextcloudTalk/ReactionsViewCell.xib new file mode 100644 index 00000000..55b75560 --- /dev/null +++ b/NextcloudTalk/ReactionsViewCell.xib @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +