From fea0e8b23eee86d8b6de06b23e9fedf6283dd066 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Sun, 27 Nov 2022 03:06:24 +0400 Subject: [PATCH] Autoremove improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 4 + .../AvatarNode/Sources/AvatarNode.swift | 389 +++++++++++++++--- .../Sources/ChatListController.swift | 8 +- .../Sources/ChatListControllerNode.swift | 20 +- .../Sources/ChatListSearchListPaneNode.swift | 48 ++- .../Sources/Node/ChatListItem.swift | 209 +++++++--- .../Sources/Node/ChatListNode.swift | 28 +- submodules/Display/Source/ImageNode.swift | 5 + .../GlobalAutoremoveHeaderItem.swift | 6 +- .../GlobalAutoremoveScreen.swift | 2 +- .../TextSizeSelectionController.swift | 7 +- .../ThemeAccentColorControllerNode.swift | 7 +- .../Themes/ThemePreviewControllerNode.swift | 7 +- .../Sources/ServiceMessageStrings.swift | 32 +- .../ChatSearchResultsContollerNode.swift | 13 +- 15 files changed, 616 insertions(+), 169 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 351165e1c7..7830e85b45 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -6249,6 +6249,10 @@ Sorry for the inconvenience."; "Conversation.AutoremoveTimerSetUserYou" = "You set messages to automatically delete after %1$@"; "Conversation.AutoremoveTimerSetUser" = "%1$@ set messages to automatically delete after %2$@"; + +"Conversation.AutoremoveTimerSetUserGlobalYou" = "You set a self-destruct timer for all chats.\nAll new messages in this chat will be automatically deleted after %1$@ they're sent."; +"Conversation.AutoremoveTimerSetUserGlobal" = "%1$@ set a self-destruct timer for all chats.\nAll new messages in this chat will be automatically deleted after %2$@ they're sent."; + "Conversation.AutoremoveTimerRemovedUserYou" = "You disabled the auto-delete timer"; "Conversation.AutoremoveTimerRemovedUser" = "%1$@ disabled the auto-delete timer"; "Conversation.AutoremoveTimerSetGroup" = "A group admin set messages to automatically delete after %1$@"; diff --git a/submodules/AvatarNode/Sources/AvatarNode.swift b/submodules/AvatarNode/Sources/AvatarNode.swift index 0f75fc7316..b84ab2dac7 100644 --- a/submodules/AvatarNode/Sources/AvatarNode.swift +++ b/submodules/AvatarNode/Sources/AvatarNode.swift @@ -9,6 +9,7 @@ import AnimationUI import AppBundle import AccountContext import Emoji +import Accelerate private let deletedIcon = UIImage(bundleImageName: "Avatar/DeletedIcon")?.precomposed() private let phoneIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/PhoneIcon"), color: .white) @@ -30,6 +31,7 @@ private class AvatarNodeParameters: NSObject { let theme: PresentationTheme? let accountPeerId: EnginePeer.Id? let peerId: EnginePeer.Id? + let colors: [UIColor] let letters: [String] let font: UIFont let icon: AvatarNodeIcon @@ -37,10 +39,11 @@ private class AvatarNodeParameters: NSObject { let hasImage: Bool let clipStyle: AvatarNodeClipStyle - init(theme: PresentationTheme?, accountPeerId: EnginePeer.Id?, peerId: EnginePeer.Id?, letters: [String], font: UIFont, icon: AvatarNodeIcon, explicitColorIndex: Int?, hasImage: Bool, clipStyle: AvatarNodeClipStyle) { + init(theme: PresentationTheme?, accountPeerId: EnginePeer.Id?, peerId: EnginePeer.Id?, colors: [UIColor], letters: [String], font: UIFont, icon: AvatarNodeIcon, explicitColorIndex: Int?, hasImage: Bool, clipStyle: AvatarNodeClipStyle) { self.theme = theme self.accountPeerId = accountPeerId self.peerId = peerId + self.colors = colors self.letters = letters self.font = font self.icon = icon @@ -52,18 +55,71 @@ private class AvatarNodeParameters: NSObject { } func withUpdatedHasImage(_ hasImage: Bool) -> AvatarNodeParameters { - return AvatarNodeParameters(theme: self.theme, accountPeerId: self.accountPeerId, peerId: self.peerId, letters: self.letters, font: self.font, icon: self.icon, explicitColorIndex: self.explicitColorIndex, hasImage: hasImage, clipStyle: self.clipStyle) + return AvatarNodeParameters(theme: self.theme, accountPeerId: self.accountPeerId, peerId: self.peerId, colors: self.colors, letters: self.letters, font: self.font, icon: self.icon, explicitColorIndex: self.explicitColorIndex, hasImage: hasImage, clipStyle: self.clipStyle) } } -private let grayscaleColors: NSArray = [ - UIColor(rgb: 0xb1b1b1).cgColor, UIColor(rgb: 0xcdcdcd).cgColor +private let grayscaleColors: [UIColor] = [ + UIColor(rgb: 0xb1b1b1), UIColor(rgb: 0xcdcdcd) ] -private let savedMessagesColors: NSArray = [ - UIColor(rgb: 0x2a9ef1).cgColor, UIColor(rgb: 0x72d5fd).cgColor +private let savedMessagesColors: [UIColor] = [ + UIColor(rgb: 0x2a9ef1), UIColor(rgb: 0x72d5fd) ] +private func calculateColors(explicitColorIndex: Int?, peerId: EnginePeer.Id?, icon: AvatarNodeIcon, theme: PresentationTheme?) -> [UIColor] { + let colorIndex: Int + if let explicitColorIndex = explicitColorIndex { + colorIndex = explicitColorIndex + } else { + if let peerId { + if peerId.namespace == .max { + colorIndex = -1 + } else { + colorIndex = abs(Int(clamping: peerId.id._internalGetInt64Value())) + } + } else { + colorIndex = -1 + } + } + + let colors: [UIColor] + if icon != .none { + if case .deletedIcon = icon { + colors = grayscaleColors + } else if case .phoneIcon = icon { + colors = grayscaleColors + } else if case .savedMessagesIcon = icon { + colors = savedMessagesColors + } else if case .repliesIcon = icon { + colors = savedMessagesColors + } else if case .editAvatarIcon = icon, let theme { + colors = [theme.list.itemAccentColor.withAlphaComponent(0.1), theme.list.itemAccentColor.withAlphaComponent(0.1)] + } else if case let .archivedChatsIcon(hiddenByDefault) = icon, let theme = theme { + let backgroundColors: (UIColor, UIColor) + if hiddenByDefault { + backgroundColors = theme.chatList.unpinnedArchiveAvatarColor.backgroundColors.colors + } else { + backgroundColors = theme.chatList.pinnedArchiveAvatarColor.backgroundColors.colors + } + colors = [backgroundColors.1, backgroundColors.0] + } else { + colors = grayscaleColors + } + } else if colorIndex == -1 { + if let theme { + let backgroundColors = theme.chatList.unpinnedArchiveAvatarColor.backgroundColors.colors + colors = [backgroundColors.1, backgroundColors.0] + } else { + colors = grayscaleColors + } + } else { + colors = AvatarNode.gradientColors[colorIndex % AvatarNode.gradientColors.count] + } + + return colors +} + public enum AvatarNodeExplicitIcon { case phone } @@ -158,21 +214,21 @@ public final class AvatarEditOverlayNode: ASDisplayNode { } public final class AvatarNode: ASDisplayNode { - public static let gradientColors: [NSArray] = [ - [UIColor(rgb: 0xff516a).cgColor, UIColor(rgb: 0xff885e).cgColor], - [UIColor(rgb: 0xffa85c).cgColor, UIColor(rgb: 0xffcd6a).cgColor], - [UIColor(rgb: 0x665fff).cgColor, UIColor(rgb: 0x82b1ff).cgColor], - [UIColor(rgb: 0x54cb68).cgColor, UIColor(rgb: 0xa0de7e).cgColor], - [UIColor(rgb: 0x4acccd).cgColor, UIColor(rgb: 0x00fcfd).cgColor], - [UIColor(rgb: 0x2a9ef1).cgColor, UIColor(rgb: 0x72d5fd).cgColor], - [UIColor(rgb: 0xd669ed).cgColor, UIColor(rgb: 0xe0a2f3).cgColor], + public static let gradientColors: [[UIColor]] = [ + [UIColor(rgb: 0xff516a), UIColor(rgb: 0xff885e)], + [UIColor(rgb: 0xffa85c), UIColor(rgb: 0xffcd6a)], + [UIColor(rgb: 0x665fff), UIColor(rgb: 0x82b1ff)], + [UIColor(rgb: 0x54cb68), UIColor(rgb: 0xa0de7e)], + [UIColor(rgb: 0x4acccd), UIColor(rgb: 0x00fcfd)], + [UIColor(rgb: 0x2a9ef1), UIColor(rgb: 0x72d5fd)], + [UIColor(rgb: 0xd669ed), UIColor(rgb: 0xe0a2f3)], ] public var font: UIFont { didSet { if oldValue.pointSize != font.pointSize { if let parameters = self.parameters { - self.parameters = AvatarNodeParameters(theme: parameters.theme, accountPeerId: parameters.accountPeerId, peerId: parameters.peerId, letters: parameters.letters, font: self.font, icon: parameters.icon, explicitColorIndex: parameters.explicitColorIndex, hasImage: parameters.hasImage, clipStyle: parameters.clipStyle) + self.parameters = AvatarNodeParameters(theme: parameters.theme, accountPeerId: parameters.accountPeerId, peerId: parameters.peerId, colors: parameters.colors, letters: parameters.letters, font: self.font, icon: parameters.icon, explicitColorIndex: parameters.explicitColorIndex, hasImage: parameters.hasImage, clipStyle: parameters.clipStyle) } if !self.displaySuspended { @@ -193,6 +249,29 @@ public final class AvatarNode: ASDisplayNode { private var state: AvatarNodeState = .empty public var unroundedImage: UIImage? + private var currentImage: UIImage? + + public var badgeView: AvatarBadgeView? { + didSet { + if self.badgeView !== oldValue { + if let badgeView = self.badgeView, let parameters = self.parameters { + if parameters.hasImage { + if let currentImage = self.currentImage { + badgeView.update(content: .image(currentImage)) + } + } else { + let badgeColor: UIColor + if parameters.colors.isEmpty { + badgeColor = .white + } else { + badgeColor = parameters.colors[parameters.colors.count - 1] + } + badgeView.update(content: .color(badgeColor)) + } + } + } + } + } private let imageReady = Promise(false) public var ready: Signal { @@ -217,6 +296,22 @@ public final class AvatarNode: ASDisplayNode { self.imageNode.isLayerBacked = true self.addSubnode(self.imageNode) + + self.imageNode.contentUpdated = { [weak self] image in + guard let self else { + return + } + + self.currentImage = image + + guard let badgeView = self.badgeView, let parameters = self.parameters else { + return + } + + if parameters.hasImage, let image { + badgeView.update(content: .image(image)) + } + } } override public func didLoad() { @@ -365,7 +460,7 @@ public final class AvatarNode: ASDisplayNode { self.editOverlayNode?.isHidden = true } - parameters = AvatarNodeParameters(theme: theme, accountPeerId: account.peerId, peerId: peer.id, letters: peer.displayLetters, font: self.font, icon: icon, explicitColorIndex: nil, hasImage: true, clipStyle: clipStyle) + parameters = AvatarNodeParameters(theme: theme, accountPeerId: account.peerId, peerId: peer.id, colors: calculateColors(explicitColorIndex: nil, peerId: peer.id, icon: icon, theme: theme), letters: peer.displayLetters, font: self.font, icon: icon, explicitColorIndex: nil, hasImage: true, clipStyle: clipStyle) } else { self.imageReady.set(.single(true)) self.displaySuspended = false @@ -374,7 +469,18 @@ public final class AvatarNode: ASDisplayNode { } self.editOverlayNode?.isHidden = true - parameters = AvatarNodeParameters(theme: theme, accountPeerId: account.peerId, peerId: peer?.id ?? EnginePeer.Id(0), letters: peer?.displayLetters ?? [], font: self.font, icon: icon, explicitColorIndex: nil, hasImage: false, clipStyle: clipStyle) + let colors = calculateColors(explicitColorIndex: nil, peerId: peer?.id ?? EnginePeer.Id(0), icon: icon, theme: theme) + parameters = AvatarNodeParameters(theme: theme, accountPeerId: account.peerId, peerId: peer?.id ?? EnginePeer.Id(0), colors: colors, letters: peer?.displayLetters ?? [], font: self.font, icon: icon, explicitColorIndex: nil, hasImage: false, clipStyle: clipStyle) + + if let badgeView = self.badgeView { + let badgeColor: UIColor + if colors.isEmpty { + badgeColor = .white + } else { + badgeColor = colors[colors.count - 1] + } + badgeView.update(content: .color(badgeColor)) + } } if self.parameters == nil || self.parameters != parameters { self.parameters = parameters @@ -400,9 +506,9 @@ public final class AvatarNode: ASDisplayNode { let parameters: AvatarNodeParameters if let icon = icon, case .phone = icon { - parameters = AvatarNodeParameters(theme: nil, accountPeerId: nil, peerId: nil, letters: [], font: self.font, icon: .phoneIcon, explicitColorIndex: explicitIndex, hasImage: false, clipStyle: .round) + parameters = AvatarNodeParameters(theme: nil, accountPeerId: nil, peerId: nil, colors: calculateColors(explicitColorIndex: explicitIndex, peerId: nil, icon: .phoneIcon, theme: nil), letters: [], font: self.font, icon: .phoneIcon, explicitColorIndex: explicitIndex, hasImage: false, clipStyle: .round) } else { - parameters = AvatarNodeParameters(theme: nil, accountPeerId: nil, peerId: nil, letters: letters, font: self.font, icon: .none, explicitColorIndex: explicitIndex, hasImage: false, clipStyle: .round) + parameters = AvatarNodeParameters(theme: nil, accountPeerId: nil, peerId: nil, colors: calculateColors(explicitColorIndex: explicitIndex, peerId: nil, icon: .none, theme: nil), letters: letters, font: self.font, icon: .none, explicitColorIndex: explicitIndex, hasImage: false, clipStyle: .round) } self.displaySuspended = true @@ -433,8 +539,10 @@ public final class AvatarNode: ASDisplayNode { context.fill(bounds) } - let colorIndex: Int + let colors: [UIColor] if let parameters = parameters as? AvatarNodeParameters { + colors = parameters.colors + if case .round = parameters.clipStyle { context.beginPath() context.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: bounds.size.width, height: @@ -445,59 +553,21 @@ public final class AvatarNode: ASDisplayNode { context.addPath(UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: bounds.size.width, height: bounds.size.height), cornerRadius: floor(bounds.size.width * 0.25)).cgPath) context.clip() } - - if let explicitColorIndex = parameters.explicitColorIndex { - colorIndex = explicitColorIndex - } else { - if let peerId = parameters.peerId { - if peerId.namespace == .max { - colorIndex = -1 - } else { - colorIndex = abs(Int(clamping: peerId.id._internalGetInt64Value())) - } - } else { - colorIndex = -1 - } - } } else { - colorIndex = -1 + colors = grayscaleColors } - let colorsArray: NSArray + let colorsArray: NSArray = colors.map(\.cgColor) as NSArray + var iconColor = UIColor.white if let parameters = parameters as? AvatarNodeParameters, parameters.icon != .none { - if case .deletedIcon = parameters.icon { - colorsArray = grayscaleColors - } else if case .phoneIcon = parameters.icon { - colorsArray = grayscaleColors - } else if case .savedMessagesIcon = parameters.icon { - colorsArray = savedMessagesColors - } else if case .repliesIcon = parameters.icon { - colorsArray = savedMessagesColors - } else if case .editAvatarIcon = parameters.icon, let theme = parameters.theme { - colorsArray = [theme.list.itemAccentColor.withAlphaComponent(0.1).cgColor, theme.list.itemAccentColor.withAlphaComponent(0.1).cgColor] - } else if case let .archivedChatsIcon(hiddenByDefault) = parameters.icon, let theme = parameters.theme { - let backgroundColors: (UIColor, UIColor) + if case let .archivedChatsIcon(hiddenByDefault) = parameters.icon, let theme = parameters.theme { if hiddenByDefault { iconColor = theme.chatList.unpinnedArchiveAvatarColor.foregroundColor - backgroundColors = theme.chatList.unpinnedArchiveAvatarColor.backgroundColors.colors } else { iconColor = theme.chatList.pinnedArchiveAvatarColor.foregroundColor - backgroundColors = theme.chatList.pinnedArchiveAvatarColor.backgroundColors.colors } - colorsArray = [backgroundColors.1.cgColor, backgroundColors.0.cgColor] - } else { - colorsArray = grayscaleColors } - } else if colorIndex == -1 { - if let parameters = parameters as? AvatarNodeParameters, let theme = parameters.theme { - let colors = theme.chatList.unpinnedArchiveAvatarColor.backgroundColors.colors - colorsArray = [colors.1.cgColor, colors.0.cgColor] - } else { - colorsArray = grayscaleColors - } - } else { - colorsArray = AvatarNode.gradientColors[colorIndex % AvatarNode.gradientColors.count] } var locations: [CGFloat] = [1.0, 0.0] @@ -636,9 +706,9 @@ public func drawPeerAvatarLetters(context: CGContext, size: CGSize, round: Bool let colorsArray: NSArray if colorIndex == -1 { - colorsArray = grayscaleColors + colorsArray = grayscaleColors.map(\.cgColor) as NSArray } else { - colorsArray = AvatarNode.gradientColors[colorIndex % AvatarNode.gradientColors.count] + colorsArray = AvatarNode.gradientColors[colorIndex % AvatarNode.gradientColors.count].map(\.cgColor) as NSArray } var locations: [CGFloat] = [1.0, 0.0] @@ -707,9 +777,9 @@ public func generateAvatarImage(size: CGSize, icon: UIImage?, iconScale: CGFloat let colorsArray: NSArray if colorIndex == -1 { - colorsArray = grayscaleColors + colorsArray = grayscaleColors.map(\.cgColor) as NSArray } else { - colorsArray = AvatarNode.gradientColors[colorIndex % AvatarNode.gradientColors.count] + colorsArray = AvatarNode.gradientColors[colorIndex % AvatarNode.gradientColors.count].map(\.cgColor) as NSArray } var locations: [CGFloat] = [1.0, 0.0] @@ -734,3 +804,190 @@ public func generateAvatarImage(size: CGSize, icon: UIImage?, iconScale: CGFloat } }) } + +public final class AvatarBadgeView: UIImageView { + enum OriginalContent: Equatable { + case color(UIColor) + case image(UIImage) + + static func ==(lhs: OriginalContent, rhs: OriginalContent) -> Bool { + switch lhs { + case let .color(color): + if case .color(color) = rhs { + return true + } else { + return false + } + case let .image(lhsImage): + if case let .image(rhsImage) = rhs { + return lhsImage === rhsImage + } else { + return false + } + } + } + } + + private struct Parameters: Equatable { + var size: CGSize + var text: String + } + + private var originalContent: OriginalContent? + private var parameters: Parameters? + + override public init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(content: OriginalContent) { + if self.originalContent != content { + self.originalContent = content + self.update() + } + } + + public func update(size: CGSize, text: String) { + let parameters = Parameters(size: size, text: text) + if self.parameters != parameters { + self.parameters = parameters + self.update() + } + } + + private func update() { + guard let originalContent = self.originalContent, let parameters = self.parameters else { + return + } + + let blurredWidth = 16 + let blurredHeight = 16 + guard let blurredContext = DrawingContext(size: CGSize(width: CGFloat(blurredWidth), height: CGFloat(blurredHeight)), scale: 1.0, opaque: true) else { + return + } + let blurredSize = CGSize(width: CGFloat(blurredWidth), height: CGFloat(blurredHeight)) + blurredContext.withContext { c in + switch originalContent { + case let .color(color): + c.setFillColor(color.cgColor) + c.fill(CGRect(origin: CGPoint(), size: blurredSize)) + case let .image(image): + c.scaleBy(x: blurredSize.width / parameters.size.width, y: blurredSize.height / parameters.size.height) + let offsetFactor: CGFloat = 1.0 - 0.7071 + let imageFrame = CGRect(origin: CGPoint(x: parameters.size.width - image.size.width + offsetFactor * parameters.size.width, y: parameters.size.height - image.size.height + offsetFactor * parameters.size.height), size: image.size) + + UIGraphicsPushContext(c) + image.draw(in: imageFrame) + UIGraphicsPopContext() + } + } + + var destinationBuffer = vImage_Buffer() + destinationBuffer.width = UInt(blurredWidth) + destinationBuffer.height = UInt(blurredHeight) + destinationBuffer.data = blurredContext.bytes + destinationBuffer.rowBytes = blurredContext.bytesPerRow + + vImageBoxConvolve_ARGB8888( + &destinationBuffer, + &destinationBuffer, + nil, + 0, 0, + UInt32(15), + UInt32(15), + nil, + vImage_Flags(kvImageEdgeExtend) + ) + + let divisor: Int32 = 0x1000 + + let rwgt: CGFloat = 0.3086 + let gwgt: CGFloat = 0.6094 + let bwgt: CGFloat = 0.0820 + + let adjustSaturation: CGFloat = 1.9 + + let a = (1.0 - adjustSaturation) * rwgt + adjustSaturation + let b = (1.0 - adjustSaturation) * rwgt + let c = (1.0 - adjustSaturation) * rwgt + let d = (1.0 - adjustSaturation) * gwgt + let e = (1.0 - adjustSaturation) * gwgt + adjustSaturation + let f = (1.0 - adjustSaturation) * gwgt + let g = (1.0 - adjustSaturation) * bwgt + let h = (1.0 - adjustSaturation) * bwgt + let i = (1.0 - adjustSaturation) * bwgt + adjustSaturation + + let satMatrix: [CGFloat] = [ + a, b, c, 0, + d, e, f, 0, + g, h, i, 0, + 0, 0, 0, 1 + ] + + var matrix: [Int16] = satMatrix.map { value in + return Int16(value * CGFloat(divisor)) + } + + vImageMatrixMultiply_ARGB8888(&destinationBuffer, &destinationBuffer, &matrix, divisor, nil, nil, vImage_Flags(kvImageDoNotTile)) + + guard let blurredImage = blurredContext.generateImage() else { + return + } + + self.image = generateImage(parameters.size, rotatedContext: { size, context in + UIGraphicsPushContext(context) + + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.setBlendMode(.copy) + context.setFillColor(UIColor.black.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) + + context.setBlendMode(.sourceIn) + + blurredImage.draw(in: CGRect(origin: CGPoint(), size: size), blendMode: .sourceIn, alpha: 1.0) + + context.setBlendMode(.normal) + + context.setFillColor(UIColor(white: 0.0, alpha: 0.05).cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) + + let string = NSAttributedString(string: parameters.text, font: Font.bold(floor(parameters.size.height * 0.48)), textColor: .white) + let stringBounds = string.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil) + string.draw(at: CGPoint(x: stringBounds.minX + floorToScreenPixels((size.width - stringBounds.width) / 2.0), y: stringBounds.minY + floorToScreenPixels((size.height - stringBounds.height) / 2.0))) + + let lineWidth: CGFloat = 1.5 + let lineInset: CGFloat = 2.0 + let lineRadius: CGFloat = size.width * 0.5 - lineInset - lineWidth * 0.5 + context.setLineWidth(lineWidth) + context.setStrokeColor(UIColor.white.cgColor) + context.setLineCap(.round) + + context.addArc(center: CGPoint(x: size.width * 0.5, y: size.height * 0.5), radius: lineRadius, startAngle: CGFloat.pi * 0.5, endAngle: -CGFloat.pi * 0.5, clockwise: false) + context.strokePath() + + + let sectionAngle: CGFloat = CGFloat.pi / 11.0 + + for i in 0 ..< 10 { + if i % 2 == 0 { + continue + } + + let startAngle = CGFloat.pi * 0.5 - CGFloat(i) * sectionAngle - sectionAngle * 0.1 + let endAngle = startAngle - sectionAngle * 0.8 + + context.addArc(center: CGPoint(x: size.width * 0.5, y: size.height * 0.5), radius: lineRadius, startAngle: startAngle, endAngle: endAngle, clockwise: true) + context.strokePath() + } + + //context.strokeEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: 2.0 + lineWidth * 0.5, dy: 2.0 + lineWidth * 0.5)) + + UIGraphicsPopContext() + }) + } +} diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 6170df5de8..04c2b38545 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1228,7 +1228,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } var joined = false - if case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = item.content, let message = messages.first { + if case let .peer(peerData) = item.content, let message = peerData.messages.first { for media in message.media { if let action = media as? TelegramMediaAction, action.action == .peerJoined { joined = true @@ -1242,7 +1242,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController chatListController.navigationPresentation = .master let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: archiveContextMenuItems(context: strongSelf.context, groupId: groupId._asGroup(), chatListController: strongSelf) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) - case let .peer(_, peer, threadInfo, _, _, _, _, _, _, _, promoInfo, _, _, _, _, _): + case let .peer(peerData): + let peer = peerData.peer + let threadInfo = peerData.threadInfo + let promoInfo = peerData.promoInfo + switch item.index { case .chatList: if case let .channel(channel) = peer.peer, channel.flags.contains(.isForum) { diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 3fe9a87df9..95f932bc7a 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -215,7 +215,25 @@ private final class ChatListShimmerNode: ASDisplayNode { ) let readState = EnginePeerReadCounters() - return ChatListItem(presentationData: chatListPresentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: 0, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1))), content: .peer(messages: [message], peer: EngineRenderedPeer(peer: peer1), threadInfo: nil, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false, forumTopicData: nil, topForumTopicItems: []), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + return ChatListItem(presentationData: chatListPresentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: 0, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1))), content: .peer(ChatListItemContent.PeerData( + messages: [message], + peer: EngineRenderedPeer(peer: peer1), + threadInfo: nil, + combinedReadState: readState, + isRemovedFromTotalUnreadCount: false, + presence: nil, + hasUnseenMentions: false, + hasUnseenReactions: false, + draftState: nil, + inputActivities: nil, + promoInfo: nil, + ignoreUnreadBadge: false, + displayAsMessage: false, + hasFailedMessages: false, + forumTopicData: nil, + topForumTopicItems: [], + autoremoveTimeout: nil + )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) } var itemNodes: [ChatListItemNode] = [] diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 2bb442fa56..0b893afaed 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -749,7 +749,25 @@ public enum ChatListSearchEntry: Comparable, Identifiable { index = .chatList( EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: message.index)) } } - return ChatListItem(presentationData: presentationData, context: context, chatListLocation: location, filterData: nil, index: index, content: .peer(messages: [message], peer: peer, threadInfo: chatThreadInfo, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false, forumTopicData: nil, topForumTopicItems: []), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + return ChatListItem(presentationData: presentationData, context: context, chatListLocation: location, filterData: nil, index: index, content: .peer(ChatListItemContent.PeerData( + messages: [message], + peer: peer, + threadInfo: chatThreadInfo, + combinedReadState: readState, + isRemovedFromTotalUnreadCount: false, + presence: nil, + hasUnseenMentions: false, + hasUnseenReactions: false, + draftState: nil, + inputActivities: nil, + promoInfo: nil, + ignoreUnreadBadge: true, + displayAsMessage: false, + hasFailedMessages: false, + forumTopicData: nil, + topForumTopicItems: [], + autoremoveTimeout: nil + )), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) } case let .addContact(phoneNumber, theme, strings): return ContactsAddItem(theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { @@ -1878,8 +1896,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { return } switch item.content { - case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _): - if let peer = peer.peer, let message = messages.first { + case let .peer(peerData): + if let peer = peerData.peer.peer, let message = peerData.messages.first { peerContextAction(peer, .search(message.id), node, gesture, location) } case .groupReference: @@ -2935,8 +2953,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { bounds = selectedItemNode.bounds } switch item.content { - case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _): - return (selectedItemNode.view, bounds, messages.last?.id ?? peer.peerId) + case let .peer(peerData): + return (selectedItemNode.view, bounds, peerData.messages.last?.id ?? peerData.peer.peerId) case let .groupReference(groupId, _, _, _, _): return (selectedItemNode.view, bounds, groupId) } @@ -3129,7 +3147,25 @@ private final class ChatListSearchShimmerNode: ASDisplayNode { associatedThreadInfo: nil ) let readState = EnginePeerReadCounters() - return ChatListItem(presentationData: chatListPresentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: 0, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1))), content: .peer(messages: [message], peer: EngineRenderedPeer(peer: peer1), threadInfo: nil, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false, forumTopicData: nil, topForumTopicItems: []), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + return ChatListItem(presentationData: chatListPresentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: 0, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1))), content: .peer(ChatListItemContent.PeerData( + messages: [message], + peer: EngineRenderedPeer(peer: peer1), + threadInfo: nil, + combinedReadState: readState, + isRemovedFromTotalUnreadCount: false, + presence: nil, + hasUnseenMentions: false, + hasUnseenReactions: false, + draftState: nil, + inputActivities: nil, + promoInfo: nil, + ignoreUnreadBadge: false, + displayAsMessage: false, + hasFailedMessages: false, + forumTopicData: nil, + topForumTopicItems: [], + autoremoveTimeout: nil + )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) case .media: return nil case .links: diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index b683a8e789..cd56edb073 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -64,16 +64,74 @@ public enum ChatListItemContent { return true } } + + public struct PeerData { + public var messages: [EngineMessage] + public var peer: EngineRenderedPeer + public var threadInfo: ThreadInfo? + public var combinedReadState: EnginePeerReadCounters? + public var isRemovedFromTotalUnreadCount: Bool + public var presence: EnginePeer.Presence? + public var hasUnseenMentions: Bool + public var hasUnseenReactions: Bool + public var draftState: DraftState? + public var inputActivities: [(EnginePeer, PeerInputActivity)]? + public var promoInfo: ChatListNodeEntryPromoInfo? + public var ignoreUnreadBadge: Bool + public var displayAsMessage: Bool + public var hasFailedMessages: Bool + public var forumTopicData: EngineChatList.ForumTopicData? + public var topForumTopicItems: [EngineChatList.ForumTopicData] + public var autoremoveTimeout: Int32? + + public init( + messages: [EngineMessage], + peer: EngineRenderedPeer, + threadInfo: ThreadInfo?, + combinedReadState: EnginePeerReadCounters?, + isRemovedFromTotalUnreadCount: Bool, + presence: EnginePeer.Presence?, + hasUnseenMentions: Bool, + hasUnseenReactions: Bool, + draftState: DraftState?, + inputActivities: [(EnginePeer, PeerInputActivity)]?, + promoInfo: ChatListNodeEntryPromoInfo?, + ignoreUnreadBadge: Bool, + displayAsMessage: Bool, + hasFailedMessages: Bool, + forumTopicData: EngineChatList.ForumTopicData?, + topForumTopicItems: [EngineChatList.ForumTopicData], + autoremoveTimeout: Int32? + ) { + self.messages = messages + self.peer = peer + self.threadInfo = threadInfo + self.combinedReadState = combinedReadState + self.isRemovedFromTotalUnreadCount = isRemovedFromTotalUnreadCount + self.presence = presence + self.hasUnseenMentions = hasUnseenMentions + self.hasUnseenReactions = hasUnseenReactions + self.draftState = draftState + self.inputActivities = inputActivities + self.promoInfo = promoInfo + self.ignoreUnreadBadge = ignoreUnreadBadge + self.displayAsMessage = displayAsMessage + self.hasFailedMessages = hasFailedMessages + self.forumTopicData = forumTopicData + self.topForumTopicItems = topForumTopicItems + self.autoremoveTimeout = autoremoveTimeout + } + } - case peer(messages: [EngineMessage], peer: EngineRenderedPeer, threadInfo: ThreadInfo?, combinedReadState: EnginePeerReadCounters?, isRemovedFromTotalUnreadCount: Bool, presence: EnginePeer.Presence?, hasUnseenMentions: Bool, hasUnseenReactions: Bool, draftState: DraftState?, inputActivities: [(EnginePeer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, ignoreUnreadBadge: Bool, displayAsMessage: Bool, hasFailedMessages: Bool, forumTopicData: EngineChatList.ForumTopicData?, topForumTopicItems: [EngineChatList.ForumTopicData]) + case peer(PeerData) case groupReference(groupId: EngineChatList.Group, peers: [EngineChatList.GroupItem.Item], message: EngineMessage?, unreadCount: Int, hiddenByDefault: Bool) public var chatLocation: ChatLocation? { switch self { - case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _): - return .peer(id: peer.peerId) - case .groupReference: - return nil + case let .peer(peerData): + return .peer(id: peerData.peer.peerId) + case .groupReference: + return nil } } } @@ -178,23 +236,23 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour { public func selected(listView: ListView) { switch self.content { - case let .peer(messages, peer, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _, _): - if let message = messages.last, let peer = peer.peer { - var threadId: Int64? - if case let .forum(_, _, threadIdValue, _, _) = self.index { - threadId = threadIdValue - } - if threadId == nil, self.interaction.searchTextHighightState != nil, case let .channel(channel) = peer, channel.flags.contains(.isForum) { - threadId = message.threadId - } - self.interaction.messageSelected(peer, threadId, message, promoInfo) - } else if let peer = peer.peer { - self.interaction.peerSelected(peer, nil, nil, promoInfo) - } else if let peer = peer.peers[peer.peerId] { - self.interaction.peerSelected(peer, nil, nil, promoInfo) + case let .peer(peerData): + if let message = peerData.messages.last, let peer = peerData.peer.peer { + var threadId: Int64? + if case let .forum(_, _, threadIdValue, _, _) = self.index { + threadId = threadIdValue } - case let .groupReference(groupId, _, _, _, _): - self.interaction.groupSelected(groupId) + if threadId == nil, self.interaction.searchTextHighightState != nil, case let .channel(channel) = peerData.peer.peer, channel.flags.contains(.isForum) { + threadId = message.threadId + } + self.interaction.messageSelected(peer, threadId, message, peerData.promoInfo) + } else if let peer = peerData.peer.peer { + self.interaction.peerSelected(peer, nil, nil, peerData.promoInfo) + } else if let peer = peerData.peer.peers[peerData.peer.peerId] { + self.interaction.peerSelected(peer, nil, nil, peerData.promoInfo) + } + case let .groupReference(groupId, _, _, _, _): + self.interaction.groupSelected(groupId) } } @@ -837,6 +895,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var avatarBadgeNode: ChatListBadgeNode? var avatarBadgeBackground: ASImageNode? let onlineNode: PeerOnlineMarkerNode + var avatarTimerBadge: AvatarBadgeView? let pinnedIconNode: ASImageNode var secretIconNode: ASImageNode? var credibilityIconView: ComponentHostView? @@ -905,8 +964,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { result += "\n\(item.presentationData.strings.VoiceOver_Chat_UnreadMessages(Int32(allCount)))" } return result - case let .peer(_, peer, _, combinedReadState, _, _, _, _, _, _, _, _, _, _, _, _): - guard let chatMainPeer = peer.chatMainPeer else { + case let .peer(peerData): + guard let chatMainPeer = peerData.peer.chatMainPeer else { return nil } var result = "" @@ -915,7 +974,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else { result += chatMainPeer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) } - if let combinedReadState = combinedReadState, combinedReadState.count > 0 { + if let combinedReadState = peerData.combinedReadState, combinedReadState.count > 0 { result += "\n\(item.presentationData.strings.VoiceOver_Chat_UnreadMessages(combinedReadState.count))" } return result @@ -965,19 +1024,19 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else { return item.presentationData.strings.VoiceOver_ChatList_MessageEmpty } - case let .peer(messages, peer, _, combinedReadState, _, _, _, _, _, _, _, _, _, _, _, _): - if let message = messages.last { + case let .peer(peerData): + if let message = peerData.messages.last { var result = "" if message.flags.contains(.Incoming) { result += item.presentationData.strings.VoiceOver_ChatList_Message } else { result += item.presentationData.strings.VoiceOver_ChatList_OutgoingMessage } - let (_, initialHideAuthor, messageText, _, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, contentSettings: item.context.currentContentSettings.with { $0 }, messages: messages, chatPeer: peer, accountPeerId: item.context.account.peerId, isPeerGroup: false) + let (_, initialHideAuthor, messageText, _, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, contentSettings: item.context.currentContentSettings.with { $0 }, messages: peerData.messages, chatPeer: peerData.peer, accountPeerId: item.context.account.peerId, isPeerGroup: false) if message.flags.contains(.Incoming), !initialHideAuthor, let author = message.author, case .user = author { result += "\n\(item.presentationData.strings.VoiceOver_ChatList_MessageFrom(author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)).string)" } - if !message.flags.contains(.Incoming), let combinedReadState = combinedReadState, combinedReadState.isOutgoingMessageIndexRead(message.index) { + if !message.flags.contains(.Incoming), let combinedReadState = peerData.combinedReadState, combinedReadState.isOutgoingMessageIndexRead(message.index) { result += "\n\(item.presentationData.strings.VoiceOver_ChatList_MessageRead)" } result += "\n\(messageText)" @@ -1165,7 +1224,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } var threadId: Int64? if let value = strongSelf.hitTest(location, with: nil), value === strongSelf.compoundTextButtonNode?.view { - if case let .peer(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, topForumTopicItems) = item.content, let topicItem = topForumTopicItems.first { + if case let .peer(peerData) = item.content, let topicItem = peerData.topForumTopicItems.first { threadId = topicItem.id } } @@ -1193,14 +1252,14 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var displayAsMessage = false var enablePreview = true switch item.content { - case let .peer(messages, peerValue, _, _, _, _, _, _, _, _, _, _, displayAsMessageValue, _, _, _): - displayAsMessage = displayAsMessageValue - if displayAsMessage, case let .user(author) = messages.last?.author { + case let .peer(peerData): + displayAsMessage = peerData.displayAsMessage + if displayAsMessage, case let .user(author) = peerData.messages.last?.author { peer = .user(author) } else { - peer = peerValue.chatMainPeer + peer = peerData.peer.chatMainPeer } - if peerValue.peerId.namespace == Namespaces.Peer.SecretChat { + if peerData.peer.peerId.namespace == Namespaces.Peer.SecretChat { enablePreview = false } case let .groupReference(_, _, _, _, hiddenByDefault): @@ -1371,8 +1430,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { guard let item = self.item, item.editing else { return } - if case let .peer(_, peer, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _, _) = item.content { - if promoInfo == nil, let mainPeer = peer.peer { + if case let .peer(peerData) = item.content { + if peerData.promoInfo == nil, let mainPeer = peerData.peer.peer { switch item.index { case let .forum(_, _, threadIdValue, _, _): item.interaction.toggleThreadsSelection([threadIdValue], !item.selected) @@ -1429,12 +1488,30 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var threadInfo: ChatListItemContent.ThreadInfo? var forumTopicData: EngineChatList.ForumTopicData? var topForumTopicItems: [EngineChatList.ForumTopicData] = [] - topForumTopicItems.removeAll() + var autoremoveTimeout: Int32? var groupHiddenByDefault = false switch item.content { - case let .peer(messagesValue, peerValue, threadInfoValue, combinedReadStateValue, isRemovedFromTotalUnreadCountValue, peerPresenceValue, hasUnseenMentionsValue, hasUnseenReactionsValue, draftStateValue, inputActivitiesValue, promoInfoValue, ignoreUnreadBadge, displayAsMessageValue, _, forumTopicDataValue, topForumTopicItemsValue): + case let .peer(peerData): + let messagesValue = peerData.messages + let peerValue = peerData.peer + let threadInfoValue = peerData.threadInfo + let combinedReadStateValue = peerData.combinedReadState + let isRemovedFromTotalUnreadCountValue = peerData.isRemovedFromTotalUnreadCount + let peerPresenceValue = peerData.presence + let hasUnseenMentionsValue = peerData.hasUnseenMentions + let hasUnseenReactionsValue = peerData.hasUnseenReactions + let draftStateValue = peerData.draftState + let inputActivitiesValue = peerData.inputActivities + let promoInfoValue = peerData.promoInfo + let ignoreUnreadBadge = peerData.ignoreUnreadBadge + let displayAsMessageValue = peerData.displayAsMessage + let forumTopicDataValue = peerData.forumTopicData + let topForumTopicItemsValue = peerData.topForumTopicItems + + autoremoveTimeout = peerData.autoremoveTimeout + messages = messagesValue contentPeer = .chat(peerValue) combinedReadState = combinedReadStateValue @@ -1582,6 +1659,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let badgeDiameter = floor(item.presentationData.fontSize.baseDisplaySize * 20.0 / 17.0) let avatarBadgeDiameter: CGFloat = floor(floor(item.presentationData.fontSize.itemListBaseFontSize * 22.0 / 17.0)) + let avatarTimerBadgeDiameter: CGFloat = floor(floor(item.presentationData.fontSize.itemListBaseFontSize * 24.0 / 17.0)) let currentAvatarBadgeCleanBackgroundImage: UIImage? = PresentationResourcesChatList.badgeBackgroundBorder(item.presentationData.theme, diameter: avatarBadgeDiameter + 4.0) @@ -1905,8 +1983,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { switch item.content { case let .groupReference(_, _, message, _, _): topIndex = message?.index - case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): - topIndex = messages.first?.index + case let .peer(peerData): + topIndex = peerData.messages.first?.index } if let topIndex { var t = Int(topIndex.timestamp) @@ -2073,8 +2151,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if !isPeerGroup && !isAccountPeer && threadInfo == nil { if displayAsMessage { switch item.content { - case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): - if let peer = messages.last?.author { + case let .peer(peerData): + if let peer = peerData.messages.last?.author { if case let .user(user) = peer, let emojiStatus = user.emojiStatus, !premiumConfiguration.isPremiumDisabled { currentCredibilityIconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2)) } else if peer.isScam { @@ -2258,7 +2336,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var peerRevealOptions: [ItemListRevealOption] var peerLeftRevealOptions: [ItemListRevealOption] switch item.content { - case let .peer(_, renderedPeer, _, _, _, presence, _, _, _, _, _, _, displayAsMessage, _, _, _): + case let .peer(peerData): + let renderedPeer = peerData.peer + let presence = peerData.presence + let displayAsMessage = peerData.displayAsMessage + if !displayAsMessage { if case let .user(peer) = renderedPeer.chatMainPeer, let presence = presence, !isServicePeer(peer) && !peer.flags.contains(.isSupport) && peer.id != item.context.account.peerId { let updatedPresence = EnginePeer.Presence(status: presence.status, lastActivity: 0) @@ -2676,6 +2758,37 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .regular, voiceChat: onlineIsVoiceChat) } strongSelf.onlineNode.setImage(onlineIcon, color: item.presentationData.theme.list.itemCheckColors.foregroundColor, transition: .immediate) + + let autoremoveTimeoutFraction: CGFloat + if online { + autoremoveTimeoutFraction = 0.0 + } else { + autoremoveTimeoutFraction = 1.0 - onlineInlineNavigationFraction + } + + if let autoremoveTimeout = autoremoveTimeout { + let avatarTimerBadge: AvatarBadgeView + var avatarTimerTransition = transition + if let current = strongSelf.avatarTimerBadge { + avatarTimerBadge = current + } else { + avatarTimerTransition = .immediate + avatarTimerBadge = AvatarBadgeView(frame: CGRect()) + strongSelf.avatarTimerBadge = avatarTimerBadge + strongSelf.avatarNode.badgeView = avatarTimerBadge + strongSelf.contextContainer.view.addSubview(avatarTimerBadge) + } + let avatarBadgeSize = CGSize(width: avatarTimerBadgeDiameter, height: avatarTimerBadgeDiameter) + avatarTimerBadge.update(size: avatarBadgeSize, text: shortTimeIntervalString(strings: item.presentationData.strings, value: autoremoveTimeout)) + let avatarBadgeFrame = CGRect(origin: CGPoint(x: avatarFrame.maxX - avatarBadgeSize.width, y: avatarFrame.maxY - avatarBadgeSize.height), size: avatarBadgeSize) + avatarTimerTransition.updatePosition(layer: avatarTimerBadge.layer, position: avatarBadgeFrame.center) + avatarTimerTransition.updateBounds(layer: avatarTimerBadge.layer, bounds: CGRect(origin: CGPoint(), size: avatarBadgeFrame.size)) + avatarTimerTransition.updateTransformScale(layer: avatarTimerBadge.layer, scale: autoremoveTimeoutFraction * 1.0 + (1.0 - autoremoveTimeoutFraction) * 0.001) + } else if let avatarTimerBadge = strongSelf.avatarTimerBadge { + strongSelf.avatarTimerBadge = nil + strongSelf.avatarNode.badgeView = nil + avatarTimerBadge.removeFromSuperview() + } let _ = measureApply() let _ = dateApply() @@ -3194,10 +3307,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { guard let item else { return } - guard case let .peer(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, topForumTopicItems) = item.content else { + guard case let .peer(peerData) = item.content else { return } - guard let topicItem = topForumTopicItems.first else { + guard let topicItem = peerData.topForumTopicItems.first else { return } guard case let .chatList(index) = item.index else { @@ -3522,7 +3635,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { close = false case RevealOptionKey.delete.rawValue: var joined = false - if case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = item.content, let message = messages.first { + if case let .peer(peerData) = item.content, let message = peerData.messages.first { for media in message.media { if let action = media as? TelegramMediaAction, action.action == .peerJoined { joined = true @@ -3558,8 +3671,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { item.interaction.toggleArchivedFolderHiddenByDefault() close = false case RevealOptionKey.hidePsa.rawValue: - if let item = self.item, case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = item.content { - item.interaction.hidePsa(peer.peerId) + if let item = self.item, case let .peer(peerData) = item.content { + item.interaction.hidePsa(peerData.peer.peerId) } close = false self.skipFadeout = true diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 668187334c..1d12cc33e0 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -345,7 +345,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL chatListLocation: location, filterData: filterData, index: index, - content: .peer( + content: .peer(ChatListItemContent.PeerData( messages: peerEntry.messages, peer: peer, threadInfo: threadInfo, @@ -361,8 +361,9 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL displayAsMessage: false, hasFailedMessages: hasFailedMessages, forumTopicData: forumTopicData, - topForumTopicItems: topForumTopicItems - ), + topForumTopicItems: topForumTopicItems, + autoremoveTimeout: peerEntry.autoremoveTimeout + )), editing: editing, hasActiveRevealControls: hasActiveRevealControls, selected: selected, @@ -601,7 +602,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL chatListLocation: location, filterData: filterData, index: index, - content: .peer( + content: .peer(ChatListItemContent.PeerData( messages: peerEntry.messages, peer: peer, threadInfo: threadInfo, @@ -617,8 +618,9 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL displayAsMessage: false, hasFailedMessages: hasFailedMessages, forumTopicData: forumTopicData, - topForumTopicItems: topForumTopicItems - ), + topForumTopicItems: topForumTopicItems, + autoremoveTimeout: peerEntry.autoremoveTimeout + )), editing: editing, hasActiveRevealControls: hasActiveRevealControls, selected: selected, @@ -2084,7 +2086,7 @@ public final class ChatListNode: ListView { var isHiddenItemVisible = false strongSelf.forEachItemNode({ itemNode in if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item { - if case let .peer(_, _, threadInfo, _, _, _, _, _, _, _, _, _, _, _, _, _) = item.content, let threadInfo { + if case let .peer(peerData) = item.content, let threadInfo = peerData.threadInfo { if threadInfo.isHidden { isHiddenItemVisible = true } @@ -2368,8 +2370,8 @@ public final class ChatListNode: ListView { for item in transition.insertItems { if let item = item.item as? ChatListItem { switch item.content { - case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _): - insertedPeerIds.append(peer.peerId) + case let .peer(peerData): + insertedPeerIds.append(peerData.peer.peerId) case .groupReference: break } @@ -2754,8 +2756,8 @@ public final class ChatListNode: ListView { if resultPeer == nil, let itemNode = itemNode as? ListViewItemNode, itemNode.frame.contains(point) { if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item { switch item.content { - case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _): - resultPeer = peer.peer + case let .peer(peerData): + resultPeer = peerData.peer.peer default: break } @@ -2771,8 +2773,8 @@ public final class ChatListNode: ListView { if resultThreadId == nil, let itemNode = itemNode as? ListViewItemNode, itemNode.frame.contains(point) { if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item { switch item.content { - case let .peer(_, _, threadInfo, _, _, _, _, _, _, _, _, _, _, _, _, _): - resultThreadId = threadInfo?.id + case let .peer(peerData): + resultThreadId = peerData.threadInfo?.id default: break } diff --git a/submodules/Display/Source/ImageNode.swift b/submodules/Display/Source/ImageNode.swift index 5b1eb093f0..5d61c11813 100644 --- a/submodules/Display/Source/ImageNode.swift +++ b/submodules/Display/Source/ImageNode.swift @@ -143,6 +143,8 @@ public class ImageNode: ASDisplayNode { } } + public var contentUpdated: ((UIImage?) -> Void)? + public init(enableHasImage: Bool = false, enableEmpty: Bool = false, enableAnimatedTransition: Bool = false) { if enableHasImage { self.hasImage = ValuePromise(false, ignoreRepeated: true) @@ -187,8 +189,10 @@ public class ImageNode: ASDisplayNode { } else { strongSelf.contents = image } + strongSelf.contentUpdated?(next) } else if strongSelf.enableEmpty { strongSelf.contents = nil + strongSelf.contentUpdated?(nil) } if !reportedHasImage { if let hasImage = strongSelf.hasImage { @@ -210,6 +214,7 @@ public class ImageNode: ASDisplayNode { self.contents = nil self.disposable.set(nil) + self.contentUpdated?(nil) } public var image: UIImage? { diff --git a/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveHeaderItem.swift b/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveHeaderItem.swift index 56c77c4572..982aec8f9e 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveHeaderItem.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveHeaderItem.swift @@ -76,7 +76,7 @@ class GlobalAutoremoveHeaderItemNode: ListViewItemNode { func asyncLayout() -> (_ item: GlobalAutoremoveHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { return { item, params, neighbors in //let leftInset: CGFloat = 32.0 + params.leftInset - let topInset: CGFloat = 92.0 + let topInset: CGFloat = 110.0 let contentSize = CGSize(width: params.width, height: topInset) let insets = itemListNeighborsGroupedInsets(neighbors, params) @@ -86,12 +86,12 @@ class GlobalAutoremoveHeaderItemNode: ListViewItemNode { return (layout, { [weak self] in if let strongSelf = self { if strongSelf.item == nil { - strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "GlobalAutoRemove"), width: 192, height: 192, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) + strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "GlobalAutoRemove"), width: 220, height: 220, playbackMode: .loop, mode: .direct(cachePathPrefix: nil)) strongSelf.animationNode.visibility = true } strongSelf.item = item - let iconSize = CGSize(width: 96.0, height: 96.0) + let iconSize = CGSize(width: 110.0, height: 110.0) strongSelf.animationNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0), y: -10.0), size: iconSize) strongSelf.animationNode.updateLayout(size: iconSize) } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift b/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift index 80f52e4639..81976a7631 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift @@ -267,7 +267,7 @@ public func globalAutoremoveScreen(context: AccountContext, initialValue: Int32, presentControllerImpl?(standardTextAlertController( theme: AlertControllerTheme(presentationData: presentationData), title: "Self-Destruct Timer", - text: "Are you sure you want all messages in new chats started by you to be automatically deleted for everyone \(valueText) after they have been sent?", + text: "Are you sure you want all messages in your new private chats and in new groups you create to be automatically deleted for everyone \(valueText) after they have been sent?", actions: [ TextAlertAction(type: .defaultAction, title: "Enable Auto-Deletion", action: { apply(timeout) diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index 18aac8cd6f..8321e35880 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -242,7 +242,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(ChatListIndex(pinningIndex: isPinned ? 0 : nil, messageIndex: MessageIndex(id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 0), timestamp: timestamp))), - content: .peer( + content: .peer(ChatListItemContent.PeerData( messages: [ EngineMessage( stableId: 0, @@ -285,8 +285,9 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView displayAsMessage: false, hasFailedMessages: false, forumTopicData: nil, - topForumTopicItems: [] - ), + topForumTopicItems: [], + autoremoveTimeout: nil + )), editing: false, hasActiveRevealControls: false, selected: false, diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index d7c75c3d76..d6281ac6b5 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -862,7 +862,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(ChatListIndex(pinningIndex: isPinned ? 0 : nil, messageIndex: MessageIndex(id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 0), timestamp: timestamp))), - content: .peer( + content: .peer(ChatListItemContent.PeerData( messages: [ EngineMessage( stableId: 0, @@ -905,8 +905,9 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate displayAsMessage: false, hasFailedMessages: false, forumTopicData: nil, - topForumTopicItems: [] - ), + topForumTopicItems: [], + autoremoveTimeout: nil + )), editing: false, hasActiveRevealControls: false, selected: false, diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index bc9f227c93..f963f864cf 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -385,7 +385,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(ChatListIndex(pinningIndex: isPinned ? 0 : nil, messageIndex: MessageIndex(id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 0), timestamp: timestamp))), - content: .peer( + content: .peer(ChatListItemContent.PeerData( messages: [ EngineMessage( stableId: 0, @@ -428,8 +428,9 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { displayAsMessage: false, hasFailedMessages: false, forumTopicData: nil, - topForumTopicItems: [] - ), + topForumTopicItems: [], + autoremoveTimeout: nil + )), editing: false, hasActiveRevealControls: false, selected: false, diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 7c2e6a37a8..22def89dab 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -336,7 +336,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } case .channelMigratedFromGroup, .groupMigratedToChannel: attributedString = NSAttributedString(string: "", font: titleFont, textColor: primaryTextColor) - case let .messageAutoremoveTimeoutUpdated(timeout, _): + case let .messageAutoremoveTimeoutUpdated(timeout, autoSourcePeerId): let authorString: String if let author = messageMainPeer(message) { authorString = author.compactDisplayTitle @@ -349,37 +349,41 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, if timeout > 0 { let timeValue = timeIntervalString(strings: strings, value: timeout, preferLowerValue: false) - let string: String - if let _ = messagePeer as? TelegramUser { - if message.author?.id == accountPeerId { - string = strings.Conversation_AutoremoveTimerSetUserYou(timeValue).string + if let user = messagePeer as? TelegramUser { + if let autoSourcePeerId = autoSourcePeerId { + if autoSourcePeerId == accountPeerId { + attributedString = NSAttributedString(string: strings.Conversation_AutoremoveTimerSetUserGlobalYou(timeValue).string, font: titleFont, textColor: primaryTextColor) + } else { + attributedString = addAttributesToStringWithRanges(strings.Conversation_AutoremoveTimerSetUserGlobal(authorString, timeValue)._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, user.id)])) + } + } else if message.author?.id == accountPeerId { + attributedString = NSAttributedString(string: strings.Conversation_AutoremoveTimerSetUserYou(timeValue).string, font: titleFont, textColor: primaryTextColor) } else { - string = strings.Conversation_AutoremoveTimerSetUser(authorString, timeValue).string + attributedString = NSAttributedString(string: strings.Conversation_AutoremoveTimerSetUser(authorString, timeValue).string, font: titleFont, textColor: primaryTextColor) } } else if let _ = messagePeer as? TelegramGroup { if message.author?.id == accountPeerId { - string = strings.Conversation_AutoremoveTimerSetUserYou(timeValue).string + attributedString = NSAttributedString(string: strings.Conversation_AutoremoveTimerSetUserYou(timeValue).string, font: titleFont, textColor: primaryTextColor) } else { - string = strings.Conversation_AutoremoveTimerSetGroup(timeValue).string + attributedString = NSAttributedString(string: strings.Conversation_AutoremoveTimerSetGroup(timeValue).string, font: titleFont, textColor: primaryTextColor) } } else if let channel = messagePeer as? TelegramChannel { if message.author?.id == accountPeerId { - string = strings.Conversation_AutoremoveTimerSetUserYou(timeValue).string + attributedString = NSAttributedString(string: strings.Conversation_AutoremoveTimerSetUserYou(timeValue).string, font: titleFont, textColor: primaryTextColor) } else { if case .group = channel.info { - string = strings.Conversation_AutoremoveTimerSetGroup(timeValue).string + attributedString = NSAttributedString(string: strings.Conversation_AutoremoveTimerSetGroup(timeValue).string, font: titleFont, textColor: primaryTextColor) } else { - string = strings.Conversation_AutoremoveTimerSetChannel(timeValue).string + attributedString = NSAttributedString(string: strings.Conversation_AutoremoveTimerSetChannel(timeValue).string, font: titleFont, textColor: primaryTextColor) } } } else { if message.author?.id == accountPeerId { - string = strings.Notification_MessageLifetimeChangedOutgoing(timeValue).string + attributedString = NSAttributedString(string: strings.Notification_MessageLifetimeChangedOutgoing(timeValue).string, font: titleFont, textColor: primaryTextColor) } else { - string = strings.Notification_MessageLifetimeChanged(authorString, timeValue).string + attributedString = NSAttributedString(string: strings.Notification_MessageLifetimeChanged(authorString, timeValue).string, font: titleFont, textColor: primaryTextColor) } } - attributedString = NSAttributedString(string: string, font: titleFont, textColor: primaryTextColor) } else { let string: String if let _ = messagePeer as? TelegramUser { diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index c44b4813c6..2f791a6e9b 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -85,7 +85,7 @@ private enum ChatListSearchEntry: Comparable, Identifiable { chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: message.index)), - content: .peer( + content: .peer(ChatListItemContent.PeerData( messages: [EngineMessage(message)], peer: EngineRenderedPeer(peer), threadInfo: nil, @@ -101,8 +101,9 @@ private enum ChatListSearchEntry: Comparable, Identifiable { displayAsMessage: true, hasFailedMessages: false, forumTopicData: nil, - topForumTopicItems: [] - ), + topForumTopicItems: [], + autoremoveTimeout: nil + )), editing: false, hasActiveRevealControls: false, selected: false, @@ -247,9 +248,9 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe return } switch item.content { - case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _): - if let message = messages.first { - let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peer.peerId), subject: .message(id: .id(message.id), highlight: true, timecode: nil), botStart: nil, mode: .standard(previewing: true)) + case let .peer(peerData): + if let message = peerData.messages.first { + let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peerData.peer.peerId), subject: .message(id: .id(message.id), highlight: true, timecode: nil), botStart: nil, mode: .standard(previewing: true)) chatController.canReadHistory.set(false) let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: .single(ContextController.Items(content: .list([]))), gesture: gesture) presentInGlobalOverlay(contextController)