diff --git a/NextcloudTalk/Calls/CallViewController.swift b/NextcloudTalk/Calls/CallViewController.swift index ee41a9d4b..a4f2bcd5a 100644 --- a/NextcloudTalk/Calls/CallViewController.swift +++ b/NextcloudTalk/Calls/CallViewController.swift @@ -267,10 +267,6 @@ class CallViewController: UIViewController, self.titleView.titleTextColor = .white self.titleView.update(for: room) - // The titleView uses the themeColor as a background for the userStatusImage - // As we always have a black background, we need to change that - self.titleView.userStatusBackgroundColor = .black - self.titleView.delegate = self self.collectionView.delegate = self self.applyInitialSnapshot() diff --git a/NextcloudTalk/Chat/Chat views/NCChatTitleView.h b/NextcloudTalk/Chat/Chat views/NCChatTitleView.h index 1eb9fd066..8a71770df 100644 --- a/NextcloudTalk/Chat/Chat views/NCChatTitleView.h +++ b/NextcloudTalk/Chat/Chat views/NCChatTitleView.h @@ -7,7 +7,7 @@ #import "NCRoom.h" -@class AvatarImageView; +@class AvatarView; @class NCChatTitleView; @class NCThread; @@ -21,11 +21,9 @@ @property (nonatomic, weak) id delegate; @property (weak, nonatomic) IBOutlet UITextView *titleTextView; -@property (weak, nonatomic) IBOutlet AvatarImageView *avatarimage; -@property (weak, nonatomic) IBOutlet UIImageView *userStatusImage; +@property (weak, nonatomic) IBOutlet AvatarView *avatarView; @property (assign, nonatomic) BOOL showSubtitle; @property (strong, nonatomic) UIColor *titleTextColor; -@property (strong, nonatomic) UIColor *userStatusBackgroundColor; @property (strong, nonatomic) UILongPressGestureRecognizer *longPressGestureRecognizer; - (void)updateForRoom:(NCRoom *)room; diff --git a/NextcloudTalk/Chat/Chat views/NCChatTitleView.m b/NextcloudTalk/Chat/Chat views/NCChatTitleView.m index dfa7b93e3..84fe2fba8 100644 --- a/NextcloudTalk/Chat/Chat views/NCChatTitleView.m +++ b/NextcloudTalk/Chat/Chat views/NCChatTitleView.m @@ -51,10 +51,6 @@ - (void)commonInit [self addSubview:self.contentView]; self.contentView.frame = self.bounds; - self.avatarimage.layer.cornerRadius = self.avatarimage.bounds.size.width / 2; - self.avatarimage.clipsToBounds = YES; - self.avatarimage.backgroundColor = [UIColor systemGray3Color]; - self.titleTextView.textContainer.lineFragmentPadding = 0; self.titleTextView.textContainerInset = UIEdgeInsetsZero; @@ -64,10 +60,8 @@ - (void)commonInit self.showSubtitle = YES; if (@available(iOS 26.0, *)) { - self.userStatusBackgroundColor = [UIColor clearColor]; self.titleTextColor = [UIColor labelColor]; } else { - self.userStatusBackgroundColor = [NCAppBranding themeColor]; self.titleTextColor = [NCAppBranding themeTextColor]; } @@ -83,20 +77,18 @@ - (void)commonInit - (void)layoutSubviews { [super layoutSubviews]; - self.avatarimage.layer.cornerRadius = self.avatarimage.bounds.size.width / 2; - self.userStatusImage.layer.cornerRadius = self.userStatusImage.bounds.size.width / 2; } - (void)updateForRoom:(NCRoom *)room { // Set room image - [self.avatarimage setAvatarFor:room]; + [self.avatarView setAvatarFor:room]; NSString *subtitle = nil; if ([[NCDatabaseManager sharedInstance] serverHasTalkCapability:kCapabilitySingleConvStatus]) { // User status - [self setStatusImageForUserStatus:room.status]; + [self.avatarView setStatusFor:room allowCustomStatusIcon:NO]; // User status message if (!room.statusMessage || [room.statusMessage isEqualToString:@""]) { @@ -127,35 +119,13 @@ - (void)updateForRoom:(NCRoom *)room - (void)updateForThread:(NCThread *)thread { // Set thread image - TalkAccount *account = [[NCDatabaseManager sharedInstance] talkAccountForAccountId:thread.accountId]; - [self.avatarimage setThreadAvatarForThread:thread]; + [self.avatarView setThreadAvatarForThread:thread]; // Set thread title and number of replies NSString *repliesString = [NSString localizedStringWithFormat:NSLocalizedString(@"%ld replies", @"Replies in a thread"), (long)thread.numReplies]; [self setTitle:thread.title withSubtitle:repliesString]; } -- (void)setStatusImageForUserStatus:(NSString *)userStatus -{ - UIImage *statusImage = nil; - if ([userStatus isEqualToString:@"online"]) { - statusImage = [NCUtils renderAspectImageWithImage:[NCUserStatus getOnlineSFIcon] ofSize:CGSizeMake(10, 10) centerImage:NO]; - } else if ([userStatus isEqualToString:@"away"]) { - statusImage = [NCUtils renderAspectImageWithImage:[NCUserStatus getAwaySFIcon] ofSize:CGSizeMake(10, 10) centerImage:NO]; - } else if ([userStatus isEqualToString:@"busy"]) { - statusImage = [NCUtils renderAspectImageWithImage:[NCUserStatus getBusySFIcon] ofSize:CGSizeMake(10, 10) centerImage:NO]; - } else if ([userStatus isEqualToString:@"dnd"]) { - statusImage = [NCUtils renderAspectImageWithImage:[NCUserStatus getDoNotDisturbSFIcon] ofSize:CGSizeMake(10, 10) centerImage:NO]; - } - - if (statusImage) { - [_userStatusImage setImage:statusImage]; - _userStatusImage.contentMode = UIViewContentModeCenter; - _userStatusImage.clipsToBounds = YES; - _userStatusImage.backgroundColor = _userStatusBackgroundColor; - } -} - - (void)setTitle:(NSString *)title withSubtitle:(NSString *)subtitle { if (!title) { @@ -195,18 +165,15 @@ -(void)handlGestureRecognizer:(UILongPressGestureRecognizer *)gestureRecognizer if (gestureRecognizer.state == UIGestureRecognizerStateBegan) { // Simulate a pressed stated. Don't use self.alpha here as it will interfere with NavigationController transitions self.titleTextView.alpha = 0.7; - self.avatarimage.alpha = 0.7; - self.userStatusImage.alpha = 0.7; + self.avatarView.alpha = 0.7; } else if (gestureRecognizer.state == UIGestureRecognizerStateEnded) { // Call delegate & reset the pressed state -> use dispatch after to give the UI time to show the actual pressed state dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ self.titleTextView.alpha = 1.0; - self.avatarimage.alpha = 1.0; - self.userStatusImage.alpha = 1.0; - + self.avatarView.alpha = 1.0; + [self.delegate chatTitleViewTapped:self]; }); - } } diff --git a/NextcloudTalk/Chat/Chat views/NCChatTitleView.xib b/NextcloudTalk/Chat/Chat views/NCChatTitleView.xib index a05d83ee9..7735f13b0 100644 --- a/NextcloudTalk/Chat/Chat views/NCChatTitleView.xib +++ b/NextcloudTalk/Chat/Chat views/NCChatTitleView.xib @@ -1,19 +1,18 @@ - + - + - + - @@ -21,23 +20,8 @@ - - - - - - - - - - - - - - - - + @@ -56,21 +40,23 @@ + + + + + + + + - - - - - - - - - + + + - + diff --git a/NextcloudTalk/Rooms/RoomsTableViewController.m b/NextcloudTalk/Rooms/RoomsTableViewController.m index 6d233a5ed..aaae5ad03 100644 --- a/NextcloudTalk/Rooms/RoomsTableViewController.m +++ b/NextcloudTalk/Rooms/RoomsTableViewController.m @@ -1809,7 +1809,7 @@ - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)rc RoomTableViewCell *cell = (RoomTableViewCell *)rcell; NCRoom *room = [_rooms objectAtIndex:indexPath.row]; - [cell.avatarView setStatusFor:room]; + [cell.avatarView setStatusFor:room allowCustomStatusIcon:YES]; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath diff --git a/NextcloudTalk/User Interface/AvatarButton.swift b/NextcloudTalk/User Interface/AvatarButton.swift index b6ab5ea84..3a60f4bf6 100644 --- a/NextcloudTalk/User Interface/AvatarButton.swift +++ b/NextcloudTalk/User Interface/AvatarButton.swift @@ -78,4 +78,12 @@ import SDWebImage self.setImage(image, for: .normal) } } + + // MARK: Thread avatars + + public func setThreadAvatar(forThread thread: NCThread) { + if let image = AvatarManager.shared.getThreadAvatar(for: thread, with: self.traitCollection.userInterfaceStyle) { + self.setImage(image, for: .normal) + } + } } diff --git a/NextcloudTalk/User Interface/AvatarProtocol.swift b/NextcloudTalk/User Interface/AvatarProtocol.swift index bfd72cdad..a6fe31b82 100644 --- a/NextcloudTalk/User Interface/AvatarProtocol.swift +++ b/NextcloudTalk/User Interface/AvatarProtocol.swift @@ -15,4 +15,7 @@ protocol AvatarProtocol { func setActorAvatar(forMessage message: NCChatMessage, withAccount account: TalkAccount) func setActorAvatar(forId actorId: String?, withType actorType: String?, withDisplayName actorDisplayName: String?, withRoomToken roomToken: String?, using account: TalkAccount) + // MARK: - Thread avatars + func setThreadAvatar(forThread thread: NCThread) + } diff --git a/NextcloudTalk/User Interface/AvatarView.swift b/NextcloudTalk/User Interface/AvatarView.swift index 9b31756e7..2df437b5f 100644 --- a/NextcloudTalk/User Interface/AvatarView.swift +++ b/NextcloudTalk/User Interface/AvatarView.swift @@ -8,7 +8,7 @@ import SDWebImage @objcMembers class AvatarView: UIView, AvatarProtocol { - private let userStatusSizePercentage = 0.36 + private let userStatusSizePercentage = 1.0/3 private let userStatusImageViewMargin = 2.0 public let avatarImageView = AvatarImageView(frame: .zero) @@ -16,6 +16,9 @@ import SDWebImage private let userStatusImageView = UIImageView() private let userStatusLabel = UILabel() + private var room: NCRoom? + private var allowCustomStatusIcon: Bool? + // MARK: - Init override init(frame: CGRect) { @@ -38,6 +41,7 @@ import SDWebImage favoriteImageView.contentMode = .scaleAspectFill userStatusImageView.contentMode = .center userStatusLabel.textAlignment = .center + userStatusLabel.adjustsFontSizeToFitWidth = true userStatusImageView.isHidden = true userStatusLabel.isHidden = true @@ -78,6 +82,12 @@ import SDWebImage userStatusImageView.layer.cornerRadius = userStatusImageView.frame.height / 2 userStatusImageView.clipsToBounds = true + + if let room = self.room, let statusIconAllowed = self.allowCustomStatusIcon { + setStatus(for: room, allowCustomStatusIcon: statusIconAllowed) + } + + setUserStatusImageViewCutoutLayer() } public func prepareForReuse() { @@ -90,8 +100,13 @@ import SDWebImage userStatusImageView.image = nil userStatusImageView.backgroundColor = .clear + userStatusImageView.isHidden = true userStatusLabel.text = nil + userStatusLabel.isHidden = true + + room = nil + allowCustomStatusIcon = nil } func cancelCurrentRequest() { @@ -122,11 +137,17 @@ import SDWebImage self.avatarImageView.setActorAvatar(forId: actorId, withType: actorType, withDisplayName: actorDisplayName, withRoomToken: roomToken, using: account) } + // MARK: - Thread avatars + + public func setThreadAvatar(forThread thread: NCThread) { + self.avatarImageView.setThreadAvatar(forThread: thread) + } + // MARK: - User status - public func setStatus(for room: NCRoom) { + public func setStatus(for room: NCRoom, allowCustomStatusIcon statusIconAllowed: Bool) { if room.type == .oneToOne, let roomStatus = room.status { - if roomStatus != "dnd", let roomStatusIcon = room.statusIcon { + if roomStatus != "dnd", statusIconAllowed, let roomStatusIcon = room.statusIcon { setUserStatusIcon(roomStatusIcon) } else { setUserStatus(roomStatus) @@ -137,20 +158,29 @@ import SDWebImage let size = CGSize(width: diameter, height: diameter) if let configuredImage = NCUtils.renderAspectImage(image: statusImage, ofSize: size, centerImage: true)?.withTintColor(.label, renderingMode: .alwaysOriginal) { setUserStatusImage(configuredImage) - setUserStatusImageViewCutoutLayer() } } } else if room.isFederated { if let statusImage = statusImageWith(name: "globe", color: .label, padding: 3) { setUserStatusImage(statusImage) - setUserStatusImageViewCutoutLayer() } } + + self.room = room + self.allowCustomStatusIcon = statusIconAllowed + + setUserStatusImageViewCutoutLayer() } private func setUserStatusImageViewCutoutLayer() { + // Only create cutout when we show the image view (no cutout for emojis) + if userStatusImageView.isHidden { + avatarImageView.layer.mask = nil + return + } + // Create a cutout path from the userStatusImageView - let statusWidth = userStatusImageView.bounds.width + let statusWidth = self.statusImageSize(padding: 0) let cutoutRect = CGRect(x: avatarImageView.bounds.maxX - statusWidth + userStatusImageViewMargin, y: avatarImageView.bounds.maxY - statusWidth + userStatusImageViewMargin, width: statusWidth, height: statusWidth) let cutoutPath = UIBezierPath(roundedRect: cutoutRect, cornerRadius: (statusWidth) / 2) @@ -174,22 +204,18 @@ import SDWebImage if userStatus == "online" { if let statusImage = statusImageWith(name: "checkmark.circle.fill", color: .systemGreen, padding: 2) { setUserStatusImage(statusImage) - setUserStatusImageViewCutoutLayer() } } else if userStatus == "away" { if let statusImage = statusImageWith(name: "clock.fill", color: .systemYellow, padding: 2) { setUserStatusImage(statusImage) - setUserStatusImageViewCutoutLayer() } } else if userStatus == "busy" { if let statusImage = statusImageWith(name: "circle.fill", color: .systemRed, padding: 2) { setUserStatusImage(statusImage) - setUserStatusImageViewCutoutLayer() } } else if userStatus == "dnd" { if let statusImage = statusImageWith(name: "minus.circle.fill", color: .systemRed, padding: 2) { setUserStatusImage(statusImage) - setUserStatusImageViewCutoutLayer() } } } @@ -234,6 +260,6 @@ import SDWebImage } private func statusImageSize(padding: CGFloat) -> CGFloat { - return self.frame.size.height * userStatusSizePercentage - padding * 2 + return (self.frame.size.height * userStatusSizePercentage - padding * 2).rounded() } }