diff --git a/Vector.xcodeproj/project.pbxproj b/Vector.xcodeproj/project.pbxproj index 0d72993c0..cc2a02558 100644 --- a/Vector.xcodeproj/project.pbxproj +++ b/Vector.xcodeproj/project.pbxproj @@ -34,6 +34,8 @@ 71EBE66E1C04C4D300E7D953 /* RoomActivitiesView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 71EBE66C1C04C4D300E7D953 /* RoomActivitiesView.xib */; }; 71F7F5151C218D8900E7ED8F /* RecentCellData.m in Sources */ = {isa = PBXBuildFile; fileRef = 71F7F5141C218D8900E7ED8F /* RecentCellData.m */; }; 71F7F5181C22CC7500E7ED8F /* MXRoom+Vector.m in Sources */ = {isa = PBXBuildFile; fileRef = 71F7F5171C22CC7500E7ED8F /* MXRoom+Vector.m */; }; + 71F7F51D1C23079F00E7ED8F /* VectorContactTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 71F7F51B1C23079F00E7ED8F /* VectorContactTableViewCell.m */; }; + 71F7F51E1C23079F00E7ED8F /* VectorContactTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 71F7F51C1C23079F00E7ED8F /* VectorContactTableViewCell.xib */; }; F001D7621B8207C000A162C3 /* RoomInputToolbarView.m in Sources */ = {isa = PBXBuildFile; fileRef = F001D75C1B8207C000A162C3 /* RoomInputToolbarView.m */; }; F001D7631B8207C000A162C3 /* RoomInputToolbarView.xib in Resources */ = {isa = PBXBuildFile; fileRef = F001D75D1B8207C000A162C3 /* RoomInputToolbarView.xib */; }; F001D76C1B821E4F00A162C3 /* MediaPickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F001D76B1B821E4F00A162C3 /* MediaPickerViewController.m */; }; @@ -210,6 +212,9 @@ 71F7F5141C218D8900E7ED8F /* RecentCellData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RecentCellData.m; sourceTree = ""; }; 71F7F5161C22CC7500E7ED8F /* MXRoom+Vector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MXRoom+Vector.h"; sourceTree = ""; }; 71F7F5171C22CC7500E7ED8F /* MXRoom+Vector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MXRoom+Vector.m"; sourceTree = ""; }; + 71F7F51A1C23079F00E7ED8F /* VectorContactTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VectorContactTableViewCell.h; path = Contact/VectorContactTableViewCell.h; sourceTree = ""; }; + 71F7F51B1C23079F00E7ED8F /* VectorContactTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VectorContactTableViewCell.m; path = Contact/VectorContactTableViewCell.m; sourceTree = ""; }; + 71F7F51C1C23079F00E7ED8F /* VectorContactTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = VectorContactTableViewCell.xib; path = Contact/VectorContactTableViewCell.xib; sourceTree = ""; }; 9B179239B79688A61A3F465F /* libPods-Vector.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Vector.a"; sourceTree = BUILT_PRODUCTS_DIR; }; F001D75B1B8207C000A162C3 /* RoomInputToolbarView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RoomInputToolbarView.h; sourceTree = ""; }; F001D75C1B8207C000A162C3 /* RoomInputToolbarView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RoomInputToolbarView.m; sourceTree = ""; }; @@ -444,6 +449,16 @@ name = RoomActivitiesView; sourceTree = ""; }; + 71F7F5191C23079100E7ED8F /* Contact */ = { + isa = PBXGroup; + children = ( + 71F7F51A1C23079F00E7ED8F /* VectorContactTableViewCell.h */, + 71F7F51B1C23079F00E7ED8F /* VectorContactTableViewCell.m */, + 71F7F51C1C23079F00E7ED8F /* VectorContactTableViewCell.xib */, + ); + name = Contact; + sourceTree = ""; + }; E1451F540F8BC02A7FB7AA31 /* Pods */ = { isa = PBXGroup; children = ( @@ -456,6 +471,7 @@ F001D7521B8207C000A162C3 /* Views */ = { isa = PBXGroup; children = ( + 71F7F5191C23079100E7ED8F /* Contact */, 71046D581C0C631100DCA984 /* RoomTitle */, 71EBE6691C04C4BE00E7D953 /* RoomActivitiesView */, 7179283B1C0384DE00407D96 /* TableViewCell */, @@ -958,6 +974,7 @@ F02528D71C11B6FC00E1FE1B /* camera_capture@3x.png in Resources */, F02528EF1C11B6FC00E1FE1B /* placeholder.png in Resources */, F094A9B41B78D8F000B1FBBF /* Main.storyboard in Resources */, + 71F7F51E1C23079F00E7ED8F /* VectorContactTableViewCell.xib in Resources */, F094A9B91B78D8F000B1FBBF /* LaunchScreen.xib in Resources */, F0C34B621C15C28300C36F09 /* RoomOutgoingAttachmentBubbleCell.xib in Resources */, F094A9B61B78D8F000B1FBBF /* Images.xcassets in Resources */, @@ -1053,6 +1070,7 @@ 71F7F5151C218D8900E7ED8F /* RecentCellData.m in Sources */, F084DAAE1BB57BD100B4C530 /* AuthInputsEmailIdentityBasedView.m in Sources */, 71352D591C10569F001D50B0 /* AvatarGenerator.m in Sources */, + 71F7F51D1C23079F00E7ED8F /* VectorContactTableViewCell.m in Sources */, 717928471C03852C00407D96 /* TableViewCellWithLabelAndLargeTextView.m in Sources */, F0D2D9871C197DCB007B8C96 /* RoomIncomingTextMsgBubbleCell.m in Sources */, F0C34B6F1C15CA2E00C36F09 /* RoomOutgoingAttachmentWithPaginationTitleBubbleCell.m in Sources */, diff --git a/Vector/AppDelegate.m b/Vector/AppDelegate.m index e6a4e31eb..9c9c5aa57 100644 --- a/Vector/AppDelegate.m +++ b/Vector/AppDelegate.m @@ -639,6 +639,9 @@ { // Store this new session [self addMatrixSession:mxSession]; + + // each room member must have his own contact. + [MXKContactManager sharedManager].memberContactCreation = MXKMemberContactCreationAll; } else if (mxSession.state == MXSessionStateStoreDataReady) { diff --git a/Vector/Assets/en.lproj/Vector.strings b/Vector/Assets/en.lproj/Vector.strings index aa7fec278..f103b0a42 100644 --- a/Vector/Assets/en.lproj/Vector.strings +++ b/Vector/Assets/en.lproj/Vector.strings @@ -78,6 +78,14 @@ "room_participants_admin_name" = "%@ (admin)"; "room_participants_invite_another_user" = "Invite another user"; +"room_participants_active" = "Active"; +"room_participants_invite" = "Invite"; +"room_participants_leave" = "Left"; +"room_participants_ban" = "Banned"; +"room_participants_active_less_1_hour" = "Active less 1 hour ago"; +"room_participants_active_less_x_hours" = "Active less %@ hours ago"; +"room_participants_active_less_x_days" = "Active less %@ days ago"; + // Chat "room_one_user_is_typing" = "%@ is typing..."; "room_two_users_are_typing" = "%@ & %@ are typing..."; diff --git a/Vector/Utils/AvatarGenerator.m b/Vector/Utils/AvatarGenerator.m index fdde410c3..a8f3fd52b 100644 --- a/Vector/Utils/AvatarGenerator.m +++ b/Vector/Utils/AvatarGenerator.m @@ -104,12 +104,19 @@ static UILabel* backgroundLabel = nil; Returns the UIImage for the text and a selected color. It checks first if it is not yet cached before generating one. */ -+ (UIImage*)avatarForText:(NSString*)text andColorIndex:(NSUInteger)colorIndex ++ (UIImage*)avatarForText:(NSString*)aText andColorIndex:(NSUInteger)colorIndex { + if ([aText hasPrefix:@"@"] || [aText hasPrefix:@"#"]) + { + aText = [aText substringFromIndex:1]; + } + + NSString* firstChar = [[aText substringToIndex:1] uppercaseString]; + // the images are cached to avoid create them several times // the key is // it should be smaller than using the text as a key - NSString* key = [NSString stringWithFormat:@"%@%tu", text, colorIndex]; + NSString* key = [NSString stringWithFormat:@"%@%tu", firstChar, colorIndex]; if (!imageByKeyDict) { @@ -120,7 +127,7 @@ static UILabel* backgroundLabel = nil; if (!image) { - image = [AvatarGenerator imageFromText:text withBackgroundColor:[colorsList objectAtIndex:colorIndex]]; + image = [AvatarGenerator imageFromText:firstChar withBackgroundColor:[colorsList objectAtIndex:colorIndex]]; [imageByKeyDict setObject:image forKey:key]; } @@ -134,14 +141,7 @@ static UILabel* backgroundLabel = nil; */ + (UIImage*)generateAvatarForText:(NSString*)text { - NSUInteger index = [AvatarGenerator colorIndexForText:text]; - - if (text.length > 0) - { - text = [[text substringToIndex:1] uppercaseString]; - } - - return [AvatarGenerator avatarForText:text andColorIndex:index]; + return [AvatarGenerator avatarForText:text andColorIndex:[AvatarGenerator colorIndexForText:text]]; } /** @@ -156,18 +156,6 @@ static UILabel* backgroundLabel = nil; NSUInteger index = [AvatarGenerator colorIndexForText:userId]; NSString* text = displayname ? displayname : userId; - // if the displayname is the userID - // skip the @ - if (!displayname && ([text hasPrefix:@"@"] || [text hasPrefix:@"#"])) - { - text = [text substringFromIndex:1]; - } - - if (text.length > 0) - { - text = [[text substringToIndex:1] uppercaseString]; - } - return [AvatarGenerator avatarForText:text andColorIndex:index]; } @@ -181,22 +169,7 @@ static UILabel* backgroundLabel = nil; NSString* displayName = room.vectorDisplayname; NSString* roomId = room.state.roomId; - // the selected color is based on the roomId - NSUInteger index = [AvatarGenerator colorIndexForText:roomId]; - NSString* text = displayName; - - // ignore the first # - if ([text hasPrefix:@"#"] || [text hasPrefix:@"@"]) - { - text = [text substringFromIndex:1]; - } - - if (text.length > 0) - { - text = [[text substringToIndex:1] uppercaseString]; - } - - return [AvatarGenerator avatarForText:text andColorIndex:index]; + return [AvatarGenerator avatarForText:displayName andColorIndex:[AvatarGenerator colorIndexForText:roomId]]; } @end diff --git a/Vector/ViewController/RoomParticipantsViewController.m b/Vector/ViewController/RoomParticipantsViewController.m index bfddec332..a73828f94 100644 --- a/Vector/ViewController/RoomParticipantsViewController.m +++ b/Vector/ViewController/RoomParticipantsViewController.m @@ -24,12 +24,16 @@ #import "Contact.h" +#import "VectorContactTableViewCell.h" + @interface RoomParticipantsViewController () { // Add participants section MXKTableViewCellWithSearchBar *addParticipantsSearchBarCell; NSString *addParticipantsSearchText; + UIView* searchBarSeparator; + // Search result section NSMutableArray *filteredParticipants; @@ -82,7 +86,6 @@ addParticipantsSearchBarCell = [[MXKTableViewCellWithSearchBar alloc] init]; addParticipantsSearchBarCell.mxkSearchBar.searchBarStyle = UISearchBarStyleMinimal; - // addParticipantsSearchBarCell.mxkSearchBar.barTintColor = [UIColor whiteColor]; // set barTint in case of UISearchBarStyleDefault (= UISearchBarStyleProminent) addParticipantsSearchBarCell.mxkSearchBar.returnKeyType = UIReturnKeyDone; addParticipantsSearchBarCell.mxkSearchBar.delegate = self; addParticipantsSearchBarCell.mxkSearchBar.placeholder = NSLocalizedStringFromTable(@"room_participants_invite_another_user", @"Vector", nil); @@ -494,28 +497,6 @@ return count; } -- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section -{ - if (section == participantsSection) - { - NSInteger count = mutableParticipants.count; - if (userMatrixId) - { - count++; - } - - if (count > 1) - { - return [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_participants_multi_participants", @"Vector", nil), count]; - } - else - { - return NSLocalizedStringFromTable(@"room_participants_one_participant", @"Vector", nil); - } - } - return nil; -} - - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = nil; @@ -524,7 +505,16 @@ { if (indexPath.row == 0) { + cell = addParticipantsSearchBarCell; + + CGRect frame = addParticipantsSearchBarCell.mxkSearchBar.frame; + frame.size.height = addParticipantsSearchBarCell.frame.size.height; + + + + + if (_isAddParticipantSearchBarEditing) { [addParticipantsSearchBarCell.mxkSearchBar becomeFirstResponder]; @@ -535,39 +525,33 @@ { if (indexPath.row < filteredParticipants.count) { - MXKContactTableCell* filteredParticipantCell = [tableView dequeueReusableCellWithIdentifier:[MXKContactTableCell defaultReuseIdentifier]]; + VectorContactTableViewCell* filteredParticipantCell = [tableView dequeueReusableCellWithIdentifier:[VectorContactTableViewCell defaultReuseIdentifier]]; if (!filteredParticipantCell) { - filteredParticipantCell = [[MXKContactTableCell alloc] init]; - filteredParticipantCell.thumbnailDisplayBoxType = MXKContactTableCellThumbnailDisplayBoxTypeCircle; - filteredParticipantCell.hideMatrixPresence = YES; + filteredParticipantCell = [[VectorContactTableViewCell alloc] init]; } + filteredParticipantCell.room = self.mxRoom; + [filteredParticipantCell render:filteredParticipants[indexPath.row]]; - // Show 'add' icon. - filteredParticipantCell.contactAccessoryViewType = MXKContactTableCellAccessoryCustom; - filteredParticipantCell.contactAccessoryViewHeightConstraint.constant = 30; - filteredParticipantCell.contactAccessoryViewWidthConstraint.constant = 30; - filteredParticipantCell.contactAccessoryImageView.image = [UIImage imageNamed:@"add"]; - filteredParticipantCell.contactAccessoryImageView.hidden = NO; - filteredParticipantCell.contactAccessoryView.hidden = NO; - - filteredParticipantCell.selectionStyle = UITableViewCellSelectionStyleNone; - cell = filteredParticipantCell; + + filteredParticipantCell.bottomLineSeparator.hidden = ((indexPath.row+1) != filteredParticipants.count); } } else if (indexPath.section == participantsSection) { - MXKContactTableCell *participantCell = [tableView dequeueReusableCellWithIdentifier:[MXKContactTableCell defaultReuseIdentifier]]; + VectorContactTableViewCell *participantCell = [tableView dequeueReusableCellWithIdentifier:[VectorContactTableViewCell defaultReuseIdentifier]]; if (!participantCell) { - participantCell = [[MXKContactTableCell alloc] init]; - participantCell.thumbnailDisplayBoxType = MXKContactTableCellThumbnailDisplayBoxTypeCircle; - participantCell.hideMatrixPresence = YES; + participantCell = [[VectorContactTableViewCell alloc] init]; } + participantCell.room = self.mxRoom; + participantCell.bottomLineSeparator.hidden = ((indexPath.row+1) != mutableParticipants.count); + + if (userMatrixId && indexPath.row == 0) { Contact *contact = [mxkContactsById objectForKey:userMatrixId]; @@ -579,31 +563,6 @@ } [participantCell render:contact]; - - if (self.mxRoom) - { - // Show 'Leave' buton. - participantCell.contactAccessoryViewType = MXKContactTableCellAccessoryCustom; - UIButton *actionButton = participantCell.contactAccessoryButton; - actionButton.hidden = NO; - [actionButton setTitle:NSLocalizedStringFromTable(@"leave", @"Vector", nil) forState:UIControlStateNormal]; - [actionButton setTitleColor:VECTOR_GREEN_COLOR forState:UIControlStateNormal]; - [actionButton addTarget:self action:@selector(onButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; - actionButton.tag = 0; - [actionButton sizeToFit]; - - participantCell.contactAccessoryViewHeightConstraint.constant = actionButton.frame.size.height; - participantCell.contactAccessoryViewWidthConstraint.constant = actionButtonWidth; - [participantCell needsUpdateConstraints]; - participantCell.contactAccessoryView.hidden = NO; - } - else - { - // Hide accessory view. - participantCell.contactAccessoryViewType = MXKContactTableCellAccessoryCustom; - } - - participantCell.selectionStyle = UITableViewCellSelectionStyleNone; } else { @@ -646,36 +605,10 @@ { [participantCell render:contact]; } - - if (self.mxRoom) - { - // Show 'remove' button. - participantCell.contactAccessoryViewType = MXKContactTableCellAccessoryCustom; - UIButton *actionButton = participantCell.contactAccessoryButton; - actionButton.hidden = NO; - [actionButton setTitle:NSLocalizedStringFromTable(@"remove", @"Vector", nil) forState:UIControlStateNormal]; - [actionButton setTitleColor:VECTOR_GREEN_COLOR forState:UIControlStateNormal]; - [actionButton addTarget:self action:@selector(onButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; - actionButton.tag = indexPath.row; - [actionButton sizeToFit]; - - participantCell.contactAccessoryViewHeightConstraint.constant = actionButton.frame.size.height; - participantCell.contactAccessoryViewWidthConstraint.constant = actionButtonWidth; - participantCell.contactAccessoryView.hidden = NO; - } - else - { - // Show 'remove' icon. - participantCell.contactAccessoryViewType = MXKContactTableCellAccessoryCustom; - participantCell.contactAccessoryViewHeightConstraint.constant = 30; - participantCell.contactAccessoryViewWidthConstraint.constant = 30; - participantCell.contactAccessoryImageView.image = [UIImage imageNamed:@"remove"]; - participantCell.contactAccessoryImageView.hidden = NO; - participantCell.contactAccessoryView.hidden = NO; - } - - participantCell.selectionStyle = UITableViewCellSelectionStyleNone; } + + + } cell = participantCell; @@ -692,7 +625,7 @@ { return 10; } - return 44; + return 74; } @@ -909,20 +842,68 @@ // text color UITextField *searchBarTextField = [searchBar valueForKey:@"_searchField"]; - searchBarTextField.textColor = VECTOR_GREEN_COLOR; + searchBarTextField.textColor = VECTOR_TEXT_GRAY_COLOR; // Magnifying glass icon. UIImageView *leftImageView = (UIImageView *)searchBarTextField.leftView; leftImageView.image = [leftImageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; leftImageView.tintColor = VECTOR_GREEN_COLOR; - - // Clear button - UIButton *clearButton = [searchBarTextField valueForKey:@"_clearButton"]; - [clearButton setImage:[clearButton.imageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateNormal]; - clearButton.tintColor = VECTOR_GREEN_COLOR; + + // remove the gray background color + UIView *effectBackgroundTop = [searchBarTextField valueForKey:@"_effectBackgroundTop"]; + UIView *effectBackgroundBottom = [searchBarTextField valueForKey:@"_effectBackgroundBottom"]; + effectBackgroundTop.hidden = YES; + effectBackgroundBottom.hidden = YES; + + // add line separator under the textfield + if (!searchBarSeparator) + { + searchBarSeparator = [[UIView alloc] init]; + searchBarSeparator.backgroundColor = VECTOR_GREEN_COLOR; + + [searchBarTextField addSubview:searchBarSeparator]; + + searchBarSeparator.translatesAutoresizingMaskIntoConstraints = NO; + + + NSLayoutConstraint* leftConstraint = [NSLayoutConstraint constraintWithItem:searchBarSeparator + attribute:NSLayoutAttributeLeading + relatedBy:NSLayoutRelationEqual + toItem:searchBarTextField + attribute:NSLayoutAttributeLeading + multiplier:1 + constant:0]; + + NSLayoutConstraint* widthConstraint = [NSLayoutConstraint constraintWithItem:searchBarSeparator + attribute:NSLayoutAttributeWidth + relatedBy:NSLayoutRelationEqual + toItem:searchBarTextField + attribute:NSLayoutAttributeWidth + multiplier:1 + constant:0]; + + NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:searchBarSeparator + attribute:NSLayoutAttributeHeight + relatedBy:NSLayoutRelationEqual + toItem:nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1.0 + constant:1]; + NSLayoutConstraint* bottomConstraint = [NSLayoutConstraint constraintWithItem:searchBarSeparator + attribute:NSLayoutAttributeBottom + relatedBy:NSLayoutRelationEqual + toItem:searchBarTextField + attribute:NSLayoutAttributeBottom + multiplier:1 + constant:0]; + + + [NSLayoutConstraint activateConstraints:@[leftConstraint, widthConstraint, heightConstraint, bottomConstraint]]; + } + // place holder - [searchBarTextField setValue:VECTOR_GREEN_COLOR forKeyPath:@"_placeholderLabel.textColor"]; + [searchBarTextField setValue:VECTOR_TEXT_GRAY_COLOR forKeyPath:@"_placeholderLabel.textColor"]; } - (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText diff --git a/Vector/Views/Contact/VectorContactTableViewCell.h b/Vector/Views/Contact/VectorContactTableViewCell.h new file mode 100644 index 000000000..da66399a5 --- /dev/null +++ b/Vector/Views/Contact/VectorContactTableViewCell.h @@ -0,0 +1,37 @@ +/* + Copyright 2015 OpenMarket Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import "MXKTableViewCell.h" +#import "MXKCellRendering.h" +#import "MXKImageView.h" + +/** + 'ContactTableCell' extends MXKTableViewCell. + */ +@interface VectorContactTableViewCell : MXKTableViewCell + +@property (strong, nonatomic) IBOutlet MXKImageView *thumbnailView; +@property (strong, nonatomic) IBOutlet UILabel *contactDisplayNameLabel; +@property (weak, nonatomic) IBOutlet UILabel *lastPresenceLabel; +@property (weak, nonatomic) IBOutlet UIView *bottomLineSeparator; +@property (weak, nonatomic) IBOutlet UIView *topLineSeparator; + +@property (nonatomic) MXRoom* room; + +@end + diff --git a/Vector/Views/Contact/VectorContactTableViewCell.m b/Vector/Views/Contact/VectorContactTableViewCell.m new file mode 100644 index 000000000..a38adb363 --- /dev/null +++ b/Vector/Views/Contact/VectorContactTableViewCell.m @@ -0,0 +1,265 @@ +/* + Copyright 2015 OpenMarket Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "VectorContactTableViewCell.h" + +#import "MXKContactManager.h" + +#import "VectorDesignValues.h" + +#import "AvatarGenerator.h" + +@interface VectorContactTableViewCell() +{ + /** + The current displayed contact. + */ + MXKContact *contact; + + /** + The observer of the presence for matrix user. + */ + id mxPresenceObserver; +} +@end + +@implementation VectorContactTableViewCell +@synthesize room; + +- (void)awakeFromNib +{ + [super awakeFromNib]; + + self.thumbnailView.layer.cornerRadius = self.thumbnailView.frame.size.width / 2; + self.thumbnailView.clipsToBounds = YES; + + // apply the vector colours + self.bottomLineSeparator.backgroundColor = VECTOR_TEXT_GRAY_COLOR; + self.topLineSeparator.backgroundColor = VECTOR_TEXT_GRAY_COLOR; + self.lastPresenceLabel.textColor = VECTOR_TEXT_GRAY_COLOR; +} + + +#pragma mark - MXKCellRendering + +// returns the first matrix id of the contact +// nil if there is none +- (NSString*)getFirstMatrixId +{ + NSString* matrixId = nil; + + if (contact.matrixIdentifiers.count > 0) + { + matrixId = contact.matrixIdentifiers.firstObject; + } + + return matrixId; +} + +- (void)render:(MXKCellData *)cellData +{ + // remove any pending observers + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + if (mxPresenceObserver) + { + [[NSNotificationCenter defaultCenter] removeObserver:mxPresenceObserver]; + mxPresenceObserver = nil; + } + + + // Sanity check: accept only object of MXKContact classes or sub-classes + NSParameterAssert([cellData isKindOfClass:[MXKContact class]]); + + contact = (MXKContact*)cellData; + + // sanity check + // should never happen + if (!contact) + { + self.thumbnailView.image = nil; + self.contactDisplayNameLabel.text = nil; + self.lastPresenceLabel.text = nil; + + return; + } + + // Be warned when the thumbnail is updated + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onThumbnailUpdate:) name:kMXKContactThumbnailUpdateNotification object:nil]; + + // Observe contact presence change + mxPresenceObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKContactManagerMatrixUserPresenceChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + + NSString* matrixId = [self getFirstMatrixId]; + + if (matrixId && [matrixId isEqualToString:notif.object]) + { + [self refreshContactPresence]; + } + }]; + + if (!contact.isMatrixContact) + { + // Refresh matrix info of the contact + [[MXKContactManager sharedManager] updateMatrixIDsForLocalContact:contact]; + } + + [self refreshContactDisplayName]; + [self refreshContactPresence]; + [self refreshContactThumbnail]; +} + ++ (CGFloat)heightForCellData:(MXKCellData*)cellData withMaximumWidth:(CGFloat)maxWidth +{ + return 74; +} + +- (void)didEndDisplay +{ + // remove any pending observers + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + if (mxPresenceObserver) { + [[NSNotificationCenter defaultCenter] removeObserver:mxPresenceObserver]; + mxPresenceObserver = nil; + } + + // Remove all gesture recognizer + while (self.thumbnailView.gestureRecognizers.count) + { + [self.thumbnailView removeGestureRecognizer:self.thumbnailView.gestureRecognizers[0]]; + } + + self.delegate = nil; + contact = nil; +} + +#pragma mark Refresh cell part + +- (void)refreshContactThumbnail +{ + UIImage* image = [contact thumbnailWithPreferedSize:self.thumbnailView.frame.size]; + + if (!image) + { + NSString* matrixId = [self getFirstMatrixId]; + + if (matrixId) + { + image = [AvatarGenerator generateRoomMemberAvatar:matrixId displayName:contact.displayName]; + } + else + { + image = [AvatarGenerator generateAvatarForText:contact.displayName]; + } + } + + self.thumbnailView.image = image; +} + +- (void)refreshContactDisplayName +{ + self.contactDisplayNameLabel.text = contact.displayName; +} + +- (void)refreshContactPresence +{ + NSString* presenceText = nil; + NSString* matrixId = [self getFirstMatrixId]; + + if (!self.lastPresenceLabel.hidden) + { + NSString* presence = nil; + + MXRoomMember* member = [self.room.state memberWithUserId:matrixId]; + + // the oneself user is always active + if ([matrixId isEqualToString:self.room.mxSession.myUser.userId]) + { + presenceText = NSLocalizedStringFromTable(@"room_participants_active", @"Vector", nil); + } + else if (!member || (member.membership != MXMembershipJoin)) + { + if (member.membership == MXMembershipInvite) + { + presence = NSLocalizedStringFromTable(@"room_participants_invite", @"Vector", nil); + } + else if (member.membership == MXMembershipLeave) + { + presence = NSLocalizedStringFromTable(@"room_participants_leave", @"Vector", nil); + } + else if (member.membership == MXMembershipBan) + { + presence = NSLocalizedStringFromTable(@"room_participants_ban", @"Vector", nil); + } + } + else + { + MXUser *user = [room.mxSession userWithUserId:matrixId]; + + if (user) + { + if (user.presence == MXPresenceOnline) + { + presence = NSLocalizedStringFromTable(@"room_participants_active", @"Vector", nil); + } + else + { + NSUInteger lastActiveMs = user.lastActiveAgo; + + if (-1 != lastActiveMs) + { + NSUInteger lastActivehour = lastActiveMs / 1000 / 60 / 60; + NSUInteger lastActiveDays = lastActivehour / 24; + + if (lastActivehour < 1) + { + presenceText = NSLocalizedStringFromTable(@"room_participants_active_less_1_hour", @"Vector", nil); + } + else if (lastActivehour < 24) + { + presenceText = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_participants_active_less_x_hours", @"Vector", nil), lastActivehour]; + } + else + { + presenceText = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_participants_active_less_x_days", @"Vector", nil), lastActiveDays]; + } + } + } + } + } + } + + self.lastPresenceLabel.text = presenceText; +} + +#pragma mark - events + +- (void)onThumbnailUpdate:(NSNotification *)notif +{ + // sanity check + if ([notif.object isKindOfClass:[NSString class]]) + { + NSString* contactID = notif.object; + + if ([contactID isEqualToString:contact.contactID]) + { + [self refreshContactThumbnail]; + } + } +} + +@end \ No newline at end of file diff --git a/Vector/Views/Contact/VectorContactTableViewCell.xib b/Vector/Views/Contact/VectorContactTableViewCell.xib new file mode 100644 index 000000000..82961ab99 --- /dev/null +++ b/Vector/Views/Contact/VectorContactTableViewCell.xib @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +