diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 99c62a222e..e58d2d0c61 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -10771,3 +10771,5 @@ Sorry for the inconvenience."; "ChatList.PremiumXmasGiftTitle" = "Send gifts to **your friends**! 🎄"; "ChatList.PremiumXmasGiftText" = "Gift Telegram Premium for Christmas."; + +"ReassignBoost.DescriptionWithLink" = "To boost **%1$@**, reassign a previous boost or [gift Telegram Premium]() to a friend to get **%2$@** additional boosts."; diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index 4f4da613da..0fffa6a76e 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -52,6 +52,7 @@ public final class ChatMessageItemAssociatedData: Equatable { public let maxReadStoryId: Int32? public let recommendedChannels: RecommendedChannels? public let audioTranscriptionTrial: AudioTranscription.TrialState + public let chatThemes: [TelegramTheme] public init( automaticDownloadPeerType: MediaAutoDownloadPeerType, @@ -77,7 +78,8 @@ public final class ChatMessageItemAssociatedData: Equatable { translateToLanguage: String? = nil, maxReadStoryId: Int32? = nil, recommendedChannels: RecommendedChannels? = nil, - audioTranscriptionTrial: AudioTranscription.TrialState = .defaultValue + audioTranscriptionTrial: AudioTranscription.TrialState = .defaultValue, + chatThemes: [TelegramTheme] = [] ) { self.automaticDownloadPeerType = automaticDownloadPeerType self.automaticDownloadPeerId = automaticDownloadPeerId @@ -103,6 +105,7 @@ public final class ChatMessageItemAssociatedData: Equatable { self.maxReadStoryId = maxReadStoryId self.recommendedChannels = recommendedChannels self.audioTranscriptionTrial = audioTranscriptionTrial + self.chatThemes = chatThemes } public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool { @@ -175,6 +178,9 @@ public final class ChatMessageItemAssociatedData: Equatable { if lhs.audioTranscriptionTrial != rhs.audioTranscriptionTrial { return false } + if lhs.chatThemes != rhs.chatThemes { + return false + } return true } } diff --git a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift index bb50b5420b..30c75c6e2b 100644 --- a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift +++ b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift @@ -634,14 +634,17 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS let fieldMaxHeight = textFieldMaxHeight(maxHeight, metrics: metrics) var textFieldMinHeight: CGFloat = 35.0 + var textFieldRealInsets = UIEdgeInsets() if let presentationInterfaceState = self.presentationInterfaceState { textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, metrics: metrics) + textFieldRealInsets = calculateTextFieldRealInsets(presentationInterfaceState) } let textFieldHeight: CGFloat if let textInputNode = self.textInputNode { let maxTextWidth = width - textFieldInsets.left - textFieldInsets.right - self.textInputViewInternalInsets.left - self.textInputViewInternalInsets.right - let measuredHeight = textInputNode.textHeightForWidth(maxTextWidth, rightInset: 0.0) + + let measuredHeight = textInputNode.textHeightForWidth(maxTextWidth, rightInset: textFieldRealInsets.right) let unboundTextFieldHeight = max(textFieldMinHeight, ceil(measuredHeight)) let maxNumberOfLines = min(12, (Int(fieldMaxHeight - 11.0) - 33) / 22) diff --git a/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift b/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift index 87b098b81a..8425a7586a 100644 --- a/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift @@ -722,15 +722,38 @@ private final class LimitSheetContent: CombinedComponent { var levelsHeight: CGFloat = 0.0 var levelItems: [AnyComponentWithIdentity] = [] + + var nameColorsAtLevel: [(Int32, Int32)] = [] + var nameColorsCountMap: [Int32: Int32] = [:] + for color in context.component.context.peerNameColors.displayOrder { + if let level = context.component.context.peerNameColors.nameColorsChannelMinRequiredBoostLevel[color] { + if let current = nameColorsCountMap[level] { + nameColorsCountMap[level] = current + 1 + } else { + nameColorsCountMap[level] = 1 + } + } + } + for (key, value) in nameColorsCountMap { + nameColorsAtLevel.append((key, value)) + } + if let nextLevels { for level in nextLevels { var perks: [LevelSectionComponent.Perk] = [] perks.append(.story(level)) perks.append(.reaction(level)) - - if level >= premiumConfiguration.minChannelNameColorLevel { - perks.append(.nameColor(7)) + + var nameColorsCount: Int32 = 0 + for (colorLevel, count) in nameColorsAtLevel { + if level >= colorLevel && colorLevel == 1 { + nameColorsCount = count + } } + if nameColorsCount > 0 { + perks.append(.nameColor(nameColorsCount)) + } + if level >= premiumConfiguration.minChannelProfileColorLevel { let delta = min(level - premiumConfiguration.minChannelProfileColorLevel + 1, 2) perks.append(.profileColor(8 * delta)) @@ -738,10 +761,17 @@ private final class LimitSheetContent: CombinedComponent { if level >= premiumConfiguration.minChannelProfileIconLevel { perks.append(.profileIcon) } - if level >= premiumConfiguration.minChannelNameColorLevel { - let delta = min(level - premiumConfiguration.minChannelNameColorLevel + 1, 3) - perks.append(.linkColor(7 * delta)) + + var linkColorsCount: Int32 = 0 + for (colorLevel, count) in nameColorsAtLevel { + if level >= colorLevel { + linkColorsCount += count + } } + if linkColorsCount > 0 { + perks.append(.linkColor(linkColorsCount)) + } + if level >= premiumConfiguration.minChannelNameIconLevel { perks.append(.linkIcon) } diff --git a/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift b/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift index 3d4e24f4d1..048e0fe72a 100644 --- a/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift +++ b/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift @@ -28,14 +28,16 @@ private final class ReplaceBoostScreenComponent: CombinedComponent { let initiallySelectedSlot: Int32? let selectedSlotsUpdated: ([Int32]) -> Void let presentController: (ViewController) -> Void + let giftPremium: () -> Void - init(context: AccountContext, peerId: EnginePeer.Id, myBoostStatus: MyBoostStatus, initiallySelectedSlot: Int32?, selectedSlotsUpdated: @escaping ([Int32]) -> Void, presentController: @escaping (ViewController) -> Void) { + init(context: AccountContext, peerId: EnginePeer.Id, myBoostStatus: MyBoostStatus, initiallySelectedSlot: Int32?, selectedSlotsUpdated: @escaping ([Int32]) -> Void, presentController: @escaping (ViewController) -> Void, giftPremium: @escaping () -> Void) { self.context = context self.peerId = peerId self.myBoostStatus = myBoostStatus self.initiallySelectedSlot = initiallySelectedSlot self.selectedSlotsUpdated = selectedSlotsUpdated self.presentController = presentController + self.giftPremium = giftPremium } static func ==(lhs: ReplaceBoostScreenComponent, rhs: ReplaceBoostScreenComponent) -> Bool { @@ -170,14 +172,26 @@ private final class ReplaceBoostScreenComponent: CombinedComponent { if channelName.count > 48 { channelName = "\(channelName.prefix(48))..." } - let descriptionString = strings.ReassignBoost_Description(channelName, "\(premiumConfiguration.boostsPerGiftCount)").string + let descriptionString = strings.ReassignBoost_DescriptionWithLink(channelName, "\(premiumConfiguration.boostsPerGiftCount)").string + let giftPremium = context.component.giftPremium let description = description.update( component: MultilineTextComponent( text: .markdown(text: descriptionString, attributes: markdownAttributes), horizontalAlignment: .center, maximumNumberOfLines: 0, - lineSpacing: 0.1 + lineSpacing: 0.1, + highlightColor: environment.theme.list.itemAccentColor.withAlphaComponent(0.2), + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + }, + tapAction: { _, _ in + giftPremium() + } ), environment: {}, availableSize: CGSize(width: availableSize.width - sideInset * 2.0 - textSideInset, height: availableSize.height), @@ -831,10 +845,13 @@ public class ReplaceBoostScreen: ViewController { var selectedSlotsUpdatedImpl: (([Int32]) -> Void)? var presentControllerImpl: ((ViewController) -> Void)? + var giftPremiumImpl: (() -> Void)? self.init(context: context, component: ReplaceBoostScreenComponent(context: context, peerId: peerId, myBoostStatus: myBoostStatus, initiallySelectedSlot: initiallySelectedSlot, selectedSlotsUpdated: { slots in selectedSlotsUpdatedImpl?(slots) }, presentController: { c in presentControllerImpl?(c) + }, giftPremium: { + giftPremiumImpl?() })) self.title = presentationData.strings.ReassignBoost_Title @@ -856,6 +873,17 @@ public class ReplaceBoostScreen: ViewController { if let initiallySelectedSlot { self.node.selectedSlots = [initiallySelectedSlot] } + + giftPremiumImpl = { [weak self] in + guard let self else { + return + } + let navigationController = self.navigationController + self.dismiss(animated: true, completion: { + let giftController = context.sharedContext.makePremiumGiftController(context: context) + navigationController?.pushViewController(giftController, animated: true) + }) + } } private init(context: AccountContext, component: C, theme: PresentationTheme? = nil) where C.EnvironmentType == ViewControllerComponentContainer.Environment { diff --git a/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift index 67afdc211a..e2a6db088d 100644 --- a/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift +++ b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift @@ -202,6 +202,12 @@ public func combineLatest(queue: Queue? = nil, _ s1: Signal, _ s2: Signal, _ s3: Signal, _ s4: Signal, _ s5: Signal, _ s6: Signal, _ s7: Signal, _ s8: Signal, _ s9: Signal, _ s10: Signal, _ s11: Signal, _ s12: Signal, _ s13: Signal, _ s14: Signal, _ s15: Signal, _ s16: Signal, _ s17: Signal, _ s18: Signal, _ s19: Signal, _ s20: Signal) -> Signal<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20), E> { + return combineLatestAny([signalOfAny(s1), signalOfAny(s2), signalOfAny(s3), signalOfAny(s4), signalOfAny(s5), signalOfAny(s6), signalOfAny(s7), signalOfAny(s8), signalOfAny(s9), signalOfAny(s10), signalOfAny(s11), signalOfAny(s12), signalOfAny(s13), signalOfAny(s14), signalOfAny(s15), signalOfAny(s16), signalOfAny(s17), signalOfAny(s18), signalOfAny(s19), signalOfAny(s20)], combine: { values in + return (values[0] as! T1, values[1] as! T2, values[2] as! T3, values[3] as! T4, values[4] as! T5, values[5] as! T6, values[6] as! T7, values[7] as! T8, values[8] as! T9, values[9] as! T10, values[10] as! T11, values[11] as! T12, values[12] as! T13, values[13] as! T14, values[14] as! T15, values[15] as! T16, values[16] as! T17, values[17] as! T18, values[18] as! T19, values[19] as! T20) + }, initialValues: [:], queue: queue) +} + public func combineLatest(queue: Queue? = nil, _ signals: [Signal]) -> Signal<[T], E> { if signals.count == 0 { return single([T](), E.self) diff --git a/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift index 89db221498..89dd7adb28 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift @@ -587,7 +587,13 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP self.statusBar.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) self.containerView.layer.animateScale(from: 1.04, to: 1.0, duration: 0.3) - self.containerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.containerView.layer.allowsGroupOpacity = true + self.containerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, completion: { [weak self] _ in + guard let self else { + return + } + self.containerView.layer.allowsGroupOpacity = false + }) } } diff --git a/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift b/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift index 553f2bf350..a70300baee 100644 --- a/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift +++ b/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift @@ -721,6 +721,7 @@ public final class MediaStreamComponent: CombinedComponent { var infoItem: AnyComponent? if let originInfo = context.state.originInfo { infoItem = AnyComponent(OriginInfoComponent( + strings: environment.strings, memberCount: originInfo.memberCount )) } @@ -933,6 +934,7 @@ public final class MediaStreamComponent: CombinedComponent { let sheet = sheet.update( component: StreamSheetComponent( + strings: environment.strings, topOffset: topOffset, sheetHeight: sheetHeight, backgroundColor: (isFullscreen && !state.hasVideo) ? .clear : (isFullyDragged ? fullscreenBackgroundColor : panelBackgroundColor), @@ -1691,15 +1693,21 @@ private final class StreamTitleComponent: Component { private final class OriginInfoComponent: CombinedComponent { + let strings: PresentationStrings let participantsCount: Int init( + strings: PresentationStrings, memberCount: Int ) { + self.strings = strings self.participantsCount = memberCount } static func ==(lhs: OriginInfoComponent, rhs: OriginInfoComponent) -> Bool { + if lhs.strings !== rhs.strings { + return false + } if lhs.participantsCount != rhs.participantsCount { return false } @@ -1713,6 +1721,7 @@ private final class OriginInfoComponent: CombinedComponent { return { context in let viewerCounter = viewerCounter.update( component: ParticipantsComponent( + strings: context.component.strings, count: context.component.participantsCount, showsSubtitle: true, fontSize: 18.0, diff --git a/submodules/TelegramCallsUI/Sources/Components/ParticipantsComponent.swift b/submodules/TelegramCallsUI/Sources/Components/ParticipantsComponent.swift index a2aefbe5cb..29e1b9d25e 100644 --- a/submodules/TelegramCallsUI/Sources/Components/ParticipantsComponent.swift +++ b/submodules/TelegramCallsUI/Sources/Components/ParticipantsComponent.swift @@ -9,12 +9,14 @@ private let purple = UIColor(rgb: 0x3252ef) private let pink = UIColor(rgb: 0xe4436c) final class ParticipantsComponent: Component { + private let strings: PresentationStrings private let count: Int private let showsSubtitle: Bool private let fontSize: CGFloat private let gradientColors: [CGColor] - init(count: Int, showsSubtitle: Bool = true, fontSize: CGFloat = 48.0, gradientColors: [CGColor] = [pink.cgColor, purple.cgColor, purple.cgColor]) { + init(strings: PresentationStrings, count: Int, showsSubtitle: Bool = true, fontSize: CGFloat = 48.0, gradientColors: [CGColor] = [pink.cgColor, purple.cgColor, purple.cgColor]) { + self.strings = strings self.count = count self.showsSubtitle = showsSubtitle self.fontSize = fontSize @@ -22,6 +24,9 @@ final class ParticipantsComponent: Component { } static func == (lhs: ParticipantsComponent, rhs: ParticipantsComponent) -> Bool { + if lhs.strings !== rhs.strings { + return false + } if lhs.count != rhs.count { return false } @@ -41,8 +46,7 @@ final class ParticipantsComponent: Component { func update(view: View, availableSize: CGSize, state: ComponentFlow.EmptyComponentState, environment: ComponentFlow.Environment, transition: ComponentFlow.Transition) -> CGSize { view.counter.update( countString: self.count > 0 ? presentationStringsFormattedNumber(Int32(count), ",") : "", - // TODO: localize - subtitle: self.showsSubtitle ? (self.count > 0 ? /*environment.strings.LiveStream_Watching*/"watching" : /*environment.strings.LiveStream_NoViewers.lowercased()*/"no viewers") : "", + subtitle: self.showsSubtitle ? (self.count > 0 ? self.strings.LiveStream_Watching.lowercased() : self.strings.LiveStream_NoViewers.lowercased()) : "", fontSize: self.fontSize, gradientColors: self.gradientColors ) diff --git a/submodules/TelegramCallsUI/Sources/Components/StreamSheetComponent.swift b/submodules/TelegramCallsUI/Sources/Components/StreamSheetComponent.swift index c9735c3967..54b037d457 100644 --- a/submodules/TelegramCallsUI/Sources/Components/StreamSheetComponent.swift +++ b/submodules/TelegramCallsUI/Sources/Components/StreamSheetComponent.swift @@ -6,8 +6,10 @@ import AccountContext import AVKit import MultilineTextComponent import Display +import TelegramPresentationData final class StreamSheetComponent: CombinedComponent { + let strings: PresentationStrings let sheetHeight: CGFloat let topOffset: CGFloat let backgroundColor: UIColor @@ -22,6 +24,7 @@ final class StreamSheetComponent: CombinedComponent { let fullscreenBottomComponent: AnyComponent init( + strings: PresentationStrings, topOffset: CGFloat, sheetHeight: CGFloat, backgroundColor: UIColor, @@ -34,6 +37,7 @@ final class StreamSheetComponent: CombinedComponent { fullscreenTopComponent: AnyComponent, fullscreenBottomComponent: AnyComponent ) { + self.strings = strings self.topOffset = topOffset self.sheetHeight = sheetHeight self.backgroundColor = backgroundColor @@ -49,6 +53,9 @@ final class StreamSheetComponent: CombinedComponent { } static func ==(lhs: StreamSheetComponent, rhs: StreamSheetComponent) -> Bool { + if lhs.strings !== rhs.strings { + return false + } if lhs.topOffset != rhs.topOffset { return false } @@ -159,7 +166,7 @@ final class StreamSheetComponent: CombinedComponent { ) let viewerCounter = viewerCounter.update( - component: ParticipantsComponent(count: context.component.participantsCount, fontSize: 44.0), + component: ParticipantsComponent(strings: context.component.strings, count: context.component.participantsCount, fontSize: 44.0), availableSize: CGSize(width: context.availableSize.width, height: 70), transition: context.transition ) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift index 825cfb9c37..fbe82f18c4 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift @@ -343,10 +343,10 @@ public extension TelegramEngine.EngineData.Item { return nil } switch cachedPeerData { - case let channel as CachedChannelData: - return channel.wallpaper case let user as CachedUserData: return user.wallpaper + case let channel as CachedChannelData: + return channel.wallpaper default: return nil } diff --git a/submodules/TelegramPresentationData/Sources/WallpaperUtils.swift b/submodules/TelegramPresentationData/Sources/WallpaperUtils.swift index 3a9c6ba5d6..fd95382431 100644 --- a/submodules/TelegramPresentationData/Sources/WallpaperUtils.swift +++ b/submodules/TelegramPresentationData/Sources/WallpaperUtils.swift @@ -49,6 +49,15 @@ public extension TelegramWallpaper { } } + var isEmoticon: Bool { + switch self { + case .emoticon: + return true + default: + return false + } + } + var dimensions: CGSize? { if case let .file(file) = self { return file.file.dimensions?.cgSize diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift index cc79946718..14c750a2bd 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift @@ -875,26 +875,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { animation.animator.updateFrame(layer: self.transformContainer.layer, frame: CGRect(origin: CGPoint(), size: actualSize), completion: nil) let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top), size: CGSize(width: actualSize.width - backgroundInsets.left - backgroundInsets.right, height: actualSize.height - backgroundInsets.top - backgroundInsets.bottom)) - - if displayLine { - let backgroundView: MessageInlineBlockBackgroundView - if let current = self.backgroundView { - backgroundView = current - animation.animator.updateFrame(layer: backgroundView.layer, frame: backgroundFrame, completion: nil) - backgroundView.update(size: backgroundFrame.size, isTransparent: false, primaryColor: mainColor, secondaryColor: secondaryColor, thirdColor: tertiaryColor, backgroundColor: nil, pattern: nil, animation: animation) - } else { - backgroundView = MessageInlineBlockBackgroundView() - self.backgroundView = backgroundView - backgroundView.frame = backgroundFrame - self.transformContainer.view.insertSubview(backgroundView, at: 0) - backgroundView.update(size: backgroundFrame.size, isTransparent: false, primaryColor: mainColor, secondaryColor: secondaryColor, thirdColor: tertiaryColor, backgroundColor: nil, pattern: nil, animation: .None) - } - } else { - if let backgroundView = self.backgroundView { - self.backgroundView = nil - backgroundView.removeFromSuperview() - } - } + var patternTopRightPosition = CGPoint() if let (inlineMediaValue, inlineMediaSize) = inlineMediaAndSize { var inlineMediaFrame = CGRect(origin: CGPoint(x: actualSize.width - insets.right - inlineMediaSize.width, y: backgroundInsets.top + inlineMediaEdgeInset), size: inlineMediaSize) @@ -902,6 +883,8 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { inlineMediaFrame.origin.x = insets.left } + patternTopRightPosition.x = insets.right + inlineMediaSize.width - 6.0 + let inlineMedia: TransformImageNode var updateMedia = false if let current = self.inlineMedia { @@ -1134,6 +1117,21 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { if let item = contentDisplayOrder.first(where: { $0.item == .media }), let (contentMediaSize, contentMediaApply) = contentMediaSizeAndApply { let contentMediaFrame = CGRect(origin: CGPoint(x: insets.left, y: item.offsetY), size: contentMediaSize) + var offsetPatternForMedia = false + if let index = contentLayoutOrder.firstIndex(where: { $0 == .media }), index != contentLayoutOrder.count - 1 { + for i in (index + 1) ..< contentLayoutOrder.count { + switch contentLayoutOrder[i] { + case .title, .subtitle, .text: + offsetPatternForMedia = true + default: + break + } + } + } + if offsetPatternForMedia { + patternTopRightPosition.y = contentMediaFrame.maxY + 6.0 + } + let contentMedia = contentMediaApply(animation, synchronousLoads) if self.contentMedia !== contentMedia { self.contentMedia?.removeFromSupernode() @@ -1303,6 +1301,38 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { self.view.removeGestureRecognizer(tapRecognizer) } } + + if displayLine { + var pattern: MessageInlineBlockBackgroundView.Pattern? + if let backgroundEmojiId = author?.backgroundEmojiId { + pattern = MessageInlineBlockBackgroundView.Pattern( + context: context, + fileId: backgroundEmojiId, + file: message.associatedMedia[MediaId( + namespace: Namespaces.Media.CloudFile, + id: backgroundEmojiId + )] as? TelegramMediaFile + ) + } + + let backgroundView: MessageInlineBlockBackgroundView + if let current = self.backgroundView { + backgroundView = current + animation.animator.updateFrame(layer: backgroundView.layer, frame: backgroundFrame, completion: nil) + backgroundView.update(size: backgroundFrame.size, isTransparent: false, primaryColor: mainColor, secondaryColor: secondaryColor, thirdColor: tertiaryColor, backgroundColor: nil, pattern: pattern, patternTopRightPosition: patternTopRightPosition, animation: animation) + } else { + backgroundView = MessageInlineBlockBackgroundView() + self.backgroundView = backgroundView + backgroundView.frame = backgroundFrame + self.transformContainer.view.insertSubview(backgroundView, at: 0) + backgroundView.update(size: backgroundFrame.size, isTransparent: false, primaryColor: mainColor, secondaryColor: secondaryColor, thirdColor: tertiaryColor, backgroundColor: nil, pattern: pattern, patternTopRightPosition: patternTopRightPosition, animation: .None) + } + } else { + if let backgroundView = self.backgroundView { + self.backgroundView = nil + backgroundView.removeFromSuperview() + } + } }) }) }) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index c3a345ec04..ebc87a3363 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -5464,4 +5464,13 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI return (image, self.backgroundNode.frame) } + + public func isServiceLikeMessage() -> Bool { + for contentNode in self.contentNodes { + if contentNode is ChatMessageActionBubbleContentNode { + return true + } + } + return false + } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift index 6df56367d9..6f87d78134 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift @@ -791,7 +791,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr } case .themeSettings, .image: unboundSize = CGSize(width: 160.0, height: 240.0).fitted(CGSize(width: 240.0, height: 240.0)) - case .color, .gradient: + case .color, .gradient, .emoticon: unboundSize = CGSize(width: 128.0, height: 128.0) } } else if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia { @@ -1339,6 +1339,8 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr return solidColorImage(color) case let .gradient(colors, rotation): return gradientImage(colors.map(UIColor.init(rgb:)), rotation: rotation ?? 0) + case .emoticon: + return solidColorImage(.black) } } @@ -1404,7 +1406,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr |> map { resourceStatus -> (MediaResourceStatus, MediaResourceStatus?) in return (resourceStatus, nil) } - case .themeSettings, .color, .gradient, .image: + case .themeSettings, .color, .gradient, .image, .emoticon: updatedStatusSignal = .single((.Local, nil)) } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/Sources/ChatMessageWallpaperBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/Sources/ChatMessageWallpaperBubbleContentNode.swift index 01d593eb0f..eec6701260 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/Sources/ChatMessageWallpaperBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/Sources/ChatMessageWallpaperBubbleContentNode.swift @@ -332,7 +332,13 @@ public class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode var imageSize = boundingSize let updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError> var patternArguments: PatternWallpaperArguments? - switch media.content { + + var mediaContent = media.content + if case let .emoticon(emoticon) = mediaContent, let theme = item.associatedData.chatThemes.first(where: { $0.emoticon?.strippedEmoji == emoticon.strippedEmoji }), let themeWallpaper = theme.settings?.first?.wallpaper, let themeWallpaperContent = WallpaperPreviewMedia(wallpaper: themeWallpaper)?.content { + mediaContent = themeWallpaperContent + } + + switch mediaContent { case let .file(file, patternColors, rotation, intensity, _, _): var representations: [ImageRepresentationWithReference] = file.previewRepresentations.map({ ImageRepresentationWithReference(representation: $0, reference: AnyMediaReference.message(message: MessageReference(item.message), media: file).resourceReference($0.resource)) }) if file.mimeType == "image/svg+xml" || file.mimeType == "application/x-tgwallpattern" { @@ -386,6 +392,8 @@ public class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode updateImageSignal = gradientImage(colors.map(UIColor.init(rgb:)), rotation: rotation ?? 0) case .themeSettings: updateImageSignal = .complete() + case .emoticon: + updateImageSignal = .complete() } strongSelf.imageNode.setSignal(updateImageSignal, attemptSynchronously: synchronousLoads) diff --git a/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift b/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift index e09b349b29..9daa53bf3a 100644 --- a/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift +++ b/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift @@ -459,6 +459,7 @@ public final class MessageInlineBlockBackgroundView: UIView { var thirdColor: UIColor? var backgroundColor: UIColor? var pattern: Pattern? + var patternTopRightPosition: CGPoint? var displayProgress: Bool init( @@ -469,6 +470,7 @@ public final class MessageInlineBlockBackgroundView: UIView { thirdColor: UIColor?, backgroundColor: UIColor?, pattern: Pattern?, + patternTopRightPosition: CGPoint?, displayProgress: Bool ) { self.size = size @@ -478,6 +480,7 @@ public final class MessageInlineBlockBackgroundView: UIView { self.thirdColor = thirdColor self.backgroundColor = backgroundColor self.pattern = pattern + self.patternTopRightPosition = patternTopRightPosition self.displayProgress = displayProgress } } @@ -608,6 +611,7 @@ public final class MessageInlineBlockBackgroundView: UIView { thirdColor: UIColor?, backgroundColor: UIColor?, pattern: Pattern?, + patternTopRightPosition: CGPoint? = nil, animation: ListViewItemUpdateAnimation ) { let params = Params( @@ -618,6 +622,7 @@ public final class MessageInlineBlockBackgroundView: UIView { thirdColor: thirdColor, backgroundColor: backgroundColor, pattern: pattern, + patternTopRightPosition: patternTopRightPosition, displayProgress: self.displayProgress ) if self.params == params { @@ -751,8 +756,14 @@ public final class MessageInlineBlockBackgroundView: UIView { } patternContentLayer.contents = self.patternContentsTarget?.contents + var patternOrigin = CGPoint(x: size.width, y: 0.0) + if let patternTopRightPosition { + patternOrigin.x -= patternTopRightPosition.x + patternOrigin.y += patternTopRightPosition.y + } + let itemSize = CGSize(width: placement.size / 3.0, height: placement.size / 3.0) - patternContentLayer.frame = CGRect(origin: CGPoint(x: size.width - placement.position.x / 3.0 - itemSize.width * 0.5, y: placement.position.y / 3.0 - itemSize.height * 0.5), size: itemSize) + patternContentLayer.frame = CGRect(origin: CGPoint(x: patternOrigin.x - placement.position.x / 3.0 - itemSize.width * 0.5, y: patternOrigin.y + placement.position.y / 3.0 - itemSize.height * 0.5), size: itemSize) var alphaFraction = abs(placement.position.x / 3.0) / min(500.0, size.width) alphaFraction = min(1.0, max(0.0, alphaFraction)) patternContentLayer.opacity = 0.3 * Float(1.0 - alphaFraction) diff --git a/submodules/TelegramUI/Components/DustEffect/Metal/DustEffectShaders.metal b/submodules/TelegramUI/Components/DustEffect/Metal/DustEffectShaders.metal index 152adb1c90..0f9f8fee11 100644 --- a/submodules/TelegramUI/Components/DustEffect/Metal/DustEffectShaders.metal +++ b/submodules/TelegramUI/Components/DustEffect/Metal/DustEffectShaders.metal @@ -93,13 +93,9 @@ kernel void dustEffectUpdateParticle( float particleXFraction = float(particleX) / float(size.x); float particleFraction = particleEaseInValueAt(effectFraction, particleXFraction); - //Loki rng = Loki(gid, uint(phase * timeStep)); - //float2 offsetNorm = float2(1.0, 1.0) * 10.0 * timeStep; - Particle particle = particles[gid]; particle.offsetFromBasePosition += (particle.velocity * timeStep) * particleFraction; - //particle.velocity += ((-offsetNorm) * 0.5 + float2(rng.rand(), rng.rand()) * offsetNorm) * particleFraction; - //particle.velocity = particle.velocity * (1.0 - particleFraction) + particle.velocity * 1.001 * particleFraction; + particle.velocity += float2(0.0, timeStep * 120.0) * particleFraction; particle.lifetime = max(0.0, particle.lifetime - timeStep * particleFraction); particles[gid] = particle; diff --git a/submodules/TelegramUI/Components/DustEffect/Sources/DustEffectLayer.swift b/submodules/TelegramUI/Components/DustEffect/Sources/DustEffectLayer.swift index 3cc57e5adb..fec29202b3 100644 --- a/submodules/TelegramUI/Components/DustEffect/Sources/DustEffectLayer.swift +++ b/submodules/TelegramUI/Components/DustEffect/Sources/DustEffectLayer.swift @@ -162,7 +162,6 @@ public final class DustEffectLayer: MetalEngineSubjectLayer, MetalEngineSubject } self.lastTimeStep = deltaTimeValue - //print("updateItems: \(deltaTime), localDeltaTime: \(localDeltaTime)") var didRemoveItems = false for i in (0 ..< self.items.count).reversed() { @@ -224,19 +223,6 @@ public final class DustEffectLayer: MetalEngineSubjectLayer, MetalEngineSubject if item.particleBuffer == nil { if let particleBuffer = MetalEngine.shared.sharedBuffer(spec: BufferSpec(length: particleCount * 4 * (4 + 1))) { item.particleBuffer = particleBuffer - - /*let particles = particleBuffer.buffer.contents().assumingMemoryBound(to: Float.self) - for i in 0 ..< particleCount { - particles[i * 5 + 0] = 0.0; - particles[i * 5 + 1] = 0.0; - - let direction = Float.random(in: 0.0 ..< Float.pi * 2.0) - let velocity = Float.random(in: 0.1 ... 0.2) * 420.0 - particles[i * 5 + 2] = cos(direction) * velocity - particles[i * 5 + 3] = sin(direction) * velocity - - particles[i * 5 + 4] = Float.random(in: 0.7 ... 1.5) - }*/ } } } @@ -279,6 +265,7 @@ public final class DustEffectLayer: MetalEngineSubjectLayer, MetalEngineSubject var phase = item.phase computeEncoder.setBytes(&phase, length: 4, index: 2) var timeStep: Float = Float(lastTimeStep) / Float(UIView.animationDurationFactor()) + timeStep *= 2.0 computeEncoder.setBytes(&timeStep, length: 4, index: 3) computeEncoder.dispatchThreadgroups(threadgroupCount, threadsPerThreadgroup: threadgroupSize) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButton.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButton.swift index eca8b8c05e..566be83523 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButton.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButton.swift @@ -104,6 +104,7 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { private var key: PeerInfoHeaderNavigationButtonKey? private var contentsColor: UIColor = .white + private var canBeExpanded: Bool = false var action: ((ASDisplayNode, ContextGesture?) -> Void)? @@ -172,6 +173,7 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { func updateContentsColor(backgroundColor: UIColor, contentsColor: UIColor, canBeExpanded: Bool, transition: ContainedViewLayoutTransition) { self.contentsColor = contentsColor + self.canBeExpanded = canBeExpanded self.backgroundNode.updateColor(color: backgroundColor, transition: transition) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButtonContainerNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButtonContainerNode.swift index 18b3dd3f19..7d6e435dfa 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButtonContainerNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButtonContainerNode.swift @@ -43,6 +43,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode { func updateContentsColor(backgroundContentColor: UIColor, contentsColor: UIColor, canBeExpanded: Bool, transition: ContainedViewLayoutTransition) { self.backgroundContentColor = backgroundContentColor self.contentsColor = contentsColor + self.canBeExpanded = canBeExpanded for (_, button) in self.leftButtonNodes { button.updateContentsColor(backgroundColor: self.backgroundContentColor, contentsColor: self.contentsColor, canBeExpanded: canBeExpanded, transition: transition) @@ -110,6 +111,8 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode { buttonNode.alpha = 0.0 transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor) buttonNode.updateContentsColor(backgroundColor: self.backgroundContentColor, contentsColor: self.contentsColor, canBeExpanded: self.canBeExpanded, transition: .immediate) + + transition.updateSublayerTransformOffset(layer: buttonNode.layer, offset: CGPoint(x: canBeExpanded ? -8.0 : 0.0, y: 0.0)) } else { transition.updateFrameAdditiveToCenter(node: buttonNode, frame: buttonFrame) transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor) @@ -214,6 +217,8 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode { buttonNode.frame = buttonFrame buttonNode.alpha = 0.0 transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor) + + transition.updateSublayerTransformOffset(layer: buttonNode.layer, offset: CGPoint(x: canBeExpanded ? 8.0 : 0.0, y: 0.0)) } else { transition.updateFrameAdditiveToCenter(node: buttonNode, frame: buttonFrame) transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor) diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift index af67f19b8b..91cda9a04c 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift @@ -156,13 +156,13 @@ final class ChannelAppearanceScreenComponent: Component { private final class ContentsData { let peer: EnginePeer? - let wallpaper: TelegramWallpaper? + let peerWallpaper: TelegramWallpaper? let subscriberCount: Int? let availableThemes: [TelegramTheme] - init(peer: EnginePeer?, wallpaper: TelegramWallpaper?, subscriberCount: Int?, availableThemes: [TelegramTheme]) { + init(peer: EnginePeer?, peerWallpaper: TelegramWallpaper?, subscriberCount: Int?, availableThemes: [TelegramTheme]) { self.peer = peer - self.wallpaper = wallpaper + self.peerWallpaper = peerWallpaper self.subscriberCount = subscriberCount self.availableThemes = availableThemes } @@ -180,7 +180,7 @@ final class ChannelAppearanceScreenComponent: Component { let (peer, subscriberCount, wallpaper) = peerData return ContentsData( peer: peer, - wallpaper: wallpaper, + peerWallpaper: wallpaper, subscriberCount: subscriberCount, availableThemes: cloudThemes ) @@ -195,6 +195,21 @@ final class ChannelAppearanceScreenComponent: Component { } private struct ResolvedState { + struct Changes: OptionSet { + var rawValue: Int32 + + init(rawValue: Int32) { + self.rawValue = rawValue + } + + static let nameColor = Changes(rawValue: 1 << 0) + static let profileColor = Changes(rawValue: 1 << 1) + static let replyFileId = Changes(rawValue: 1 << 2) + static let backgroundFileId = Changes(rawValue: 1 << 3) + static let emojiStatus = Changes(rawValue: 1 << 4) + static let wallpaper = Changes(rawValue: 1 << 5) + } + var nameColor: PeerNameColor var profileColor: PeerNameColor? var replyFileId: Int64? @@ -202,16 +217,16 @@ final class ChannelAppearanceScreenComponent: Component { var emojiStatus: PeerEmojiStatus? var wallpaper: TelegramWallpaper? - var hasChanges: Bool + var changes: Changes - init(nameColor: PeerNameColor, profileColor: PeerNameColor?, replyFileId: Int64?, backgroundFileId: Int64?, emojiStatus: PeerEmojiStatus?, wallpaper: TelegramWallpaper?, hasChanges: Bool) { + init(nameColor: PeerNameColor, profileColor: PeerNameColor?, replyFileId: Int64?, backgroundFileId: Int64?, emojiStatus: PeerEmojiStatus?, wallpaper: TelegramWallpaper?, changes: Changes) { self.nameColor = nameColor self.profileColor = profileColor self.replyFileId = replyFileId self.backgroundFileId = backgroundFileId self.emojiStatus = emojiStatus self.wallpaper = wallpaper - self.hasChanges = hasChanges + self.changes = changes } } @@ -334,7 +349,7 @@ final class ChannelAppearanceScreenComponent: Component { return nil } - var hasChanges = false + var changes: ResolvedState.Changes = [] let nameColor: PeerNameColor if let updatedPeerNameColor = self.updatedPeerNameColor { @@ -345,7 +360,7 @@ final class ChannelAppearanceScreenComponent: Component { nameColor = .blue } if nameColor != peer.nameColor { - hasChanges = true + changes.insert(.nameColor) } let profileColor: PeerNameColor? @@ -357,7 +372,7 @@ final class ChannelAppearanceScreenComponent: Component { profileColor = nil } if profileColor != peer.profileColor { - hasChanges = true + changes.insert(.profileColor) } let replyFileId: Int64? @@ -367,7 +382,7 @@ final class ChannelAppearanceScreenComponent: Component { replyFileId = peer.backgroundEmojiId } if replyFileId != peer.backgroundEmojiId { - hasChanges = true + changes.insert(.replyFileId) } let backgroundFileId: Int64? @@ -377,7 +392,7 @@ final class ChannelAppearanceScreenComponent: Component { backgroundFileId = peer.profileBackgroundEmojiId } if backgroundFileId != peer.profileBackgroundEmojiId { - hasChanges = true + changes.insert(.backgroundFileId) } let emojiStatus: PeerEmojiStatus? @@ -387,7 +402,7 @@ final class ChannelAppearanceScreenComponent: Component { emojiStatus = peer.emojiStatus } if emojiStatus != peer.emojiStatus { - hasChanges = true + changes.insert(.emojiStatus) } let wallpaper: TelegramWallpaper? @@ -396,13 +411,13 @@ final class ChannelAppearanceScreenComponent: Component { case .remove: wallpaper = nil case let .emoticon(emoticon): - wallpaper = contentsData.availableThemes.first(where: { $0.emoticon == emoticon })?.settings?.first?.wallpaper + wallpaper = .emoticon(emoticon) case .custom: wallpaper = self.temporaryPeerWallpaper } - hasChanges = true + changes.insert(.wallpaper) } else { - wallpaper = contentsData.wallpaper + wallpaper = contentsData.peerWallpaper } return ResolvedState( @@ -412,7 +427,7 @@ final class ChannelAppearanceScreenComponent: Component { backgroundFileId: backgroundFileId, emojiStatus: emojiStatus, wallpaper: wallpaper, - hasChanges: hasChanges + changes: changes ) } @@ -430,7 +445,7 @@ final class ChannelAppearanceScreenComponent: Component { return } - if !resolvedState.hasChanges { + if resolvedState.changes.isEmpty { self.environment?.controller()?.dismiss() return } @@ -446,28 +461,43 @@ final class ChannelAppearanceScreenComponent: Component { case generic } - if let updatedPeerWallpaper { - switch updatedPeerWallpaper { - case .remove: - let _ = component.context.engine.themes.setChatWallpaper(peerId: component.peerId, wallpaper: nil, forBoth: false).start() - case let .emoticon(emoticon): - let _ = component.context.engine.themes.setChatWallpaper(peerId: component.peerId, wallpaper: .emoticon(emoticon), forBoth: false).start() - case let .custom(wallpaperEntry, options, editedImage, cropRect, brightness): - uploadCustomPeerWallpaper(context: component.context, wallpaper: wallpaperEntry, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, peerId: component.peerId, forBoth: false, completion: {}) - } - } - - self.applyDisposable = (combineLatest([ - component.context.engine.peers.updatePeerNameColorAndEmoji(peerId: component.peerId, nameColor: resolvedState.nameColor, backgroundEmojiId: resolvedState.replyFileId, profileColor: resolvedState.profileColor, profileBackgroundEmojiId: resolvedState.backgroundFileId) + var signals: [Signal] = [] + if !resolvedState.changes.intersection([.nameColor, .profileColor, .replyFileId, .backgroundFileId]).isEmpty { + signals.append(component.context.engine.peers.updatePeerNameColorAndEmoji(peerId: component.peerId, nameColor: resolvedState.nameColor, backgroundEmojiId: resolvedState.replyFileId, profileColor: resolvedState.profileColor, profileBackgroundEmojiId: resolvedState.backgroundFileId) |> ignoreValues |> mapError { _ -> ApplyError in return .generic - }, - component.context.engine.peers.updatePeerEmojiStatus(peerId: component.peerId, fileId: statusFileId, expirationDate: nil) + }) + } + if resolvedState.changes.contains(.emojiStatus) { + signals.append(component.context.engine.peers.updatePeerEmojiStatus(peerId: component.peerId, fileId: statusFileId, expirationDate: nil) + |> ignoreValues |> mapError { _ -> ApplyError in return .generic + }) + } + if resolvedState.changes.contains(.wallpaper) { + if let updatedPeerWallpaper { + switch updatedPeerWallpaper { + case .remove: + signals.append(component.context.engine.themes.setChatWallpaper(peerId: component.peerId, wallpaper: nil, forBoth: false) + |> ignoreValues + |> mapError { _ -> ApplyError in + return .generic + }) + case let .emoticon(emoticon): + signals.append(component.context.engine.themes.setChatWallpaper(peerId: component.peerId, wallpaper: .emoticon(emoticon), forBoth: false) + |> ignoreValues + |> mapError { _ -> ApplyError in + return .generic + }) + case let .custom(wallpaperEntry, options, editedImage, cropRect, brightness): + uploadCustomPeerWallpaper(context: component.context, wallpaper: wallpaperEntry, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, peerId: component.peerId, forBoth: false, completion: {}) + } } - ]) + } + + self.applyDisposable = (combineLatest(signals) |> deliverOnMainQueue).start(error: { [weak self] _ in guard let self, let component = self.component else { return @@ -550,7 +580,7 @@ final class ChannelAppearanceScreenComponent: Component { self.updatedPeerWallpaper = result switch result { case let .emoticon(emoticon): - if let selectedTheme = contentsData.availableThemes.first(where: { $0.emoticon == emoticon }) { + if let selectedTheme = contentsData.availableThemes.first(where: { $0.emoticon?.strippedEmoji == emoticon.strippedEmoji }) { self.currentTheme = .cloud(PresentationCloudTheme(theme: selectedTheme, resolvedWallpaper: nil, creatorAccountId: nil)) } self.temporaryPeerWallpaper = nil @@ -700,13 +730,22 @@ final class ChannelAppearanceScreenComponent: Component { if self.contentsDataDisposable == nil { self.contentsDataDisposable = (ContentsData.get(context: component.context, peerId: component.peerId) |> deliverOnMainQueue).start(next: { [weak self] contentsData in - guard let self else { + guard let self, let component = self.component else { return } if self.contentsData == nil, case let .channel(channel) = contentsData.peer { self.boostLevel = channel.approximateBoostLevel.flatMap(Int.init) } + if self.contentsData == nil, let peerWallpaper = contentsData.peerWallpaper { + for cloudTheme in contentsData.availableThemes { + if case let .emoticon(emoticon) = peerWallpaper, cloudTheme.emoticon?.strippedEmoji == emoticon.strippedEmoji { + self.currentTheme = .cloud(PresentationCloudTheme(theme: cloudTheme, resolvedWallpaper: nil, creatorAccountId: cloudTheme.isCreator ? component.context.account.id : nil)) + break + } + } + } self.contentsData = contentsData + if !self.isUpdating { self.state?.updated(transition: .immediate) } @@ -733,20 +772,11 @@ final class ChannelAppearanceScreenComponent: Component { var requiredBoostSubjects: [BoostSubject] = [.nameColors(colors: resolvedState.nameColor)] - let replyIconLevel = 5 - var profileIconLevel = 7 - var emojiStatusLevel = 8 - let themeLevel = 9 + let replyIconLevel = Int(BoostSubject.nameIcon.requiredLevel(context: component.context, configuration: premiumConfiguration)) + let profileIconLevel = Int(BoostSubject.profileIcon.requiredLevel(context: component.context, configuration: premiumConfiguration)) + let emojiStatusLevel = Int(BoostSubject.emojiStatus.requiredLevel(context: component.context, configuration: premiumConfiguration)) + let themeLevel = Int(BoostSubject.wallpaper.requiredLevel(context: component.context, configuration: premiumConfiguration)) - if let data = component.context.currentAppConfiguration.with({ $0 }).data { - if let value = data["channel_profile_color_level_min"] as? Double { - profileIconLevel = Int(value) - } - if let value = data["channel_emoji_status_level_min"] as? Double { - emojiStatusLevel = Int(value) - } - } - let replyFileId = resolvedState.replyFileId if replyFileId != nil { requiredBoostSubjects.append(.nameIcon) @@ -810,13 +840,13 @@ final class ChannelAppearanceScreenComponent: Component { })) } } else if self.currentTheme == nil { - self.resolvedCurrentTheme = nil self.resolvingCurrentTheme?.disposable.dispose() self.resolvingCurrentTheme = nil + self.resolvedCurrentTheme = nil } if let wallpaper = resolvedState.wallpaper { - if wallpaper.isPattern { + if wallpaper.isEmoticon { requiredBoostSubjects.append(.wallpaper) } else { requiredBoostSubjects.append(.customWallpaper) @@ -901,6 +931,8 @@ final class ChannelAppearanceScreenComponent: Component { if let wallpaper = resolvedCurrentTheme.wallpaper { chatPreviewWallpaper = wallpaper } + } else if let initialWallpaper = contentsData.peerWallpaper, !initialWallpaper.isEmoticon { + chatPreviewWallpaper = initialWallpaper } let replySectionSize = self.replySection.update( @@ -1002,7 +1034,7 @@ final class ChannelAppearanceScreenComponent: Component { var currentTheme = self.currentTheme var selectedWallpaper: TelegramWallpaper? - if currentTheme == nil, let wallpaper = resolvedState.wallpaper, !wallpaper.isPattern { + if currentTheme == nil, let wallpaper = resolvedState.wallpaper, !wallpaper.isEmoticon { let theme: PresentationThemeReference = .builtin(.day) currentTheme = theme selectedWallpaper = wallpaper diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift index edeaa31173..d572e3ad0a 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift @@ -306,7 +306,7 @@ final class ThemeGridControllerNode: ASDisplayNode { self.gridNode.addSubnode(self.colorItemNode) } self.gridNode.addSubnode(self.galleryItemNode) - if case let .peer(_, _, wallpaper, _, _) = mode, let wallpaper, !wallpaper.isPattern { + if case let .peer(_, _, wallpaper, _, _) = mode, let wallpaper, !wallpaper.isEmoticon { self.gridNode.addSubnode(self.removeItemNode) } self.gridNode.addSubnode(self.descriptionItemNode) @@ -404,7 +404,7 @@ final class ThemeGridControllerNode: ASDisplayNode { selectedWallpaper = wallpaper } - if let selectedWallpaper, !selectedWallpaper.isPattern { + if let selectedWallpaper, !selectedWallpaper.isEmoticon { entries.append(ThemeGridControllerEntry(index: index, theme: presentationData.theme, wallpaper: selectedWallpaper, channelMode: true, isEditable: false, isSelected: true)) } else { let emojiFile = context.animatedEmojiStickers["❌"]?.first?.file @@ -414,19 +414,24 @@ final class ThemeGridControllerNode: ASDisplayNode { index += 1 for theme in themes { - guard let wallpaper = theme.settings?.first?.wallpaper, let emoticon = theme.emoticon else { + guard let wallpaper = theme.settings?.first?.wallpaper, let themeEmoticon = theme.emoticon else { continue } var updatedWallpaper = wallpaper if let settings = wallpaper.settings { var updatedSettings = settings - updatedSettings.emoticon = emoticon + updatedSettings.emoticon = themeEmoticon updatedWallpaper = wallpaper.withUpdatedSettings(updatedSettings) } + + var isSelected = false + if let selectedWallpaper, case let .emoticon(emoticon) = selectedWallpaper, emoticon.strippedEmoji == themeEmoticon.strippedEmoji { + isSelected = true + } - let emoji = context.animatedEmojiStickers[emoticon] - entries.append(ThemeGridControllerEntry(index: index, theme: presentationData.theme, wallpaper: updatedWallpaper, emoji: emoji?.first?.file, channelMode: true, isEditable: false, isSelected: selectedWallpaper?.isBasicallyEqual(to: wallpaper) ?? false)) + let emoji = context.animatedEmojiStickers[themeEmoticon] + entries.append(ThemeGridControllerEntry(index: index, theme: presentationData.theme, wallpaper: updatedWallpaper, emoji: emoji?.first?.file, channelMode: true, isEditable: false, isSelected: isSelected)) index += 1 } } else { diff --git a/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/Sources/StoryFooterPanelComponent.swift b/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/Sources/StoryFooterPanelComponent.swift index 1f815f4fdb..1b1b481aa4 100644 --- a/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/Sources/StoryFooterPanelComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/Sources/StoryFooterPanelComponent.swift @@ -407,299 +407,6 @@ public final class StoryFooterPanelComponent: Component { self.viewStatsButton.isEnabled = displayViewLists - var rightContentOffset: CGFloat = availableSize.width - 12.0 - - if component.isChannel { - var likeStatsTransition = transition - var forwardStatsTransition = transition - - if transition.animation.isImmediate, !isFirstTime, let previousComponent, previousComponent.storyItem.id == component.storyItem.id, previousComponent.expandFraction == component.expandFraction { - likeStatsTransition = .easeInOut(duration: 0.2) - forwardStatsTransition = .easeInOut(duration: 0.2) - } - - let likeStatsText: AnimatedCountLabelView - if let current = self.likeStatsText { - likeStatsText = current - } else { - likeStatsTransition = likeStatsTransition.withAnimation(.none) - likeStatsText = AnimatedCountLabelView(frame: CGRect()) - likeStatsText.isUserInteractionEnabled = false - self.likeStatsText = likeStatsText - } - - let reactionStatsLayout = likeStatsText.update( - size: CGSize(width: availableSize.width, height: size.height), - segments: [ - .number(reactionCount, NSAttributedString(string: "\(reactionCount)", font: Font.with(size: 15.0, traits: .monospacedNumbers), textColor: .white)) - ], - transition: (isFirstTime || likeStatsTransition.animation.isImmediate) ? .immediate : ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut) - ) - var likeStatsFrame = CGRect(origin: CGPoint(x: rightContentOffset - reactionStatsLayout.size.width, y: floor((size.height - reactionStatsLayout.size.height) * 0.5)), size: reactionStatsLayout.size) - likeStatsFrame.origin.y += component.expandFraction * 45.0 - - likeStatsTransition.setPosition(view: likeStatsText, position: likeStatsFrame.center) - likeStatsTransition.setBounds(view: likeStatsText, bounds: CGRect(origin: CGPoint(), size: likeStatsFrame.size)) - var likeStatsAlpha: CGFloat = (1.0 - component.expandFraction) - if reactionCount == 0 { - likeStatsAlpha = 0.0 - } - likeStatsTransition.setAlpha(view: likeStatsText, alpha: likeStatsAlpha) - likeStatsTransition.setScale(view: likeStatsText, scale: reactionCount == 0 ? 0.001 : 1.0) - - if reactionCount != 0 { - rightContentOffset -= reactionStatsLayout.size.width + 1.0 - } - - let likeButton: ComponentView - if let current = self.likeButton { - likeButton = current - } else { - likeButton = ComponentView() - self.likeButton = likeButton - } - - let likeButtonSize = likeButton.update( - transition: likeStatsTransition, - component: AnyComponent(MessageInputActionButtonComponent( - mode: .like(reaction: component.myReaction?.reaction, file: component.myReaction?.file, animationFileId: component.myReaction?.animationFileId), - storyId: component.storyItem.id, - action: { [weak self] _, action, _ in - guard let self, let component = self.component else { - return - } - guard case .up = action else { - return - } - component.likeAction() - }, - longPressAction: nil, - switchMediaInputMode: { - }, - updateMediaCancelFraction: { _ in - }, - lockMediaRecording: { - }, - stopAndPreviewMediaRecording: { - }, - moreAction: { _, _ in }, - context: component.context, - theme: component.theme, - strings: component.strings, - presentController: { _ in }, - audioRecorder: nil, - videoRecordingStatus: nil - )), - environment: {}, - containerSize: CGSize(width: 33.0, height: 33.0) - ) - if let likeButtonView = likeButton.view as? MessageInputActionButtonComponent.View { - if likeButtonView.superview == nil { - self.addSubview(likeButtonView) - } - var likeButtonFrame = CGRect(origin: CGPoint(x: rightContentOffset - likeButtonSize.width, y: floor((size.height - likeButtonSize.height) * 0.5)), size: likeButtonSize) - likeButtonFrame.origin.y += component.expandFraction * 45.0 - - if let likeButtonTracingOffsetView = self.likeButtonTracingOffsetView { - let difference = CGPoint(x: likeButtonFrame.midX - likeButtonView.layer.position.x, y: likeButtonFrame.midY - likeButtonView.layer.position.y) - if difference != CGPoint() { - likeStatsTransition.setPosition(view: likeButtonTracingOffsetView, position: likeButtonTracingOffsetView.layer.position.offsetBy(dx: difference.x, dy: difference.y)) - } - } - - likeStatsTransition.setPosition(view: likeButtonView, position: likeButtonFrame.center) - likeStatsTransition.setBounds(view: likeButtonView, bounds: CGRect(origin: CGPoint(), size: likeButtonFrame.size)) - likeStatsTransition.setAlpha(view: likeButtonView, alpha: 1.0 - component.expandFraction) - - rightContentOffset -= likeButtonSize.width + 14.0 - - if likeStatsText.superview == nil { - likeButtonView.button.view.addSubview(likeStatsText) - } - - likeStatsFrame.origin.x -= likeButtonFrame.minX - likeStatsFrame.origin.y -= likeButtonFrame.minY - likeStatsTransition.setPosition(view: likeStatsText, position: likeStatsFrame.center) - likeStatsTransition.setBounds(view: likeStatsText, bounds: CGRect(origin: CGPoint(), size: likeStatsFrame.size)) - } - - if component.canShare { - let forwardStatsText: AnimatedCountLabelView - if let current = self.forwardStatsText { - forwardStatsText = current - } else { - forwardStatsTransition = forwardStatsTransition.withAnimation(.none) - forwardStatsText = AnimatedCountLabelView(frame: CGRect()) - forwardStatsText.isUserInteractionEnabled = false - self.forwardStatsText = forwardStatsText - } - - let forwardStatsLayout = forwardStatsText.update( - size: CGSize(width: availableSize.width, height: size.height), - segments: [ - .number(forwardCount, NSAttributedString(string: "\(forwardCount)", font: Font.with(size: 15.0, traits: .monospacedNumbers), textColor: .white)) - ], - transition: (isFirstTime || likeStatsTransition.animation.isImmediate) ? .immediate : ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut) - ) - var forwardStatsFrame = CGRect(origin: CGPoint(x: rightContentOffset - forwardStatsLayout.size.width, y: floor((size.height - forwardStatsLayout.size.height) * 0.5)), size: forwardStatsLayout.size) - forwardStatsFrame.origin.y += component.expandFraction * 45.0 - - var forwardStatsAlpha: CGFloat = (1.0 - component.expandFraction) - if forwardCount == 0 { - forwardStatsAlpha = 0.0 - } - forwardStatsTransition.setAlpha(view: forwardStatsText, alpha: forwardStatsAlpha) - forwardStatsTransition.setScale(view: forwardStatsText, scale: forwardCount == 0 ? 0.001 : 1.0) - - if forwardCount != 0 { - rightContentOffset -= forwardStatsLayout.size.width + 1.0 - } - - let repostButton: ComponentView - if let current = self.repostButton { - repostButton = current - } else { - repostButton = ComponentView() - self.repostButton = repostButton - } - - let forwardButton: ComponentView - if let current = self.forwardButton { - forwardButton = current - } else { - forwardButton = ComponentView() - self.forwardButton = forwardButton - } - - let repostButtonSize = repostButton.update( - transition: likeStatsTransition, - component: AnyComponent(MessageInputActionButtonComponent( - mode: .repost, - storyId: component.storyItem.id, - action: { [weak self] _, action, _ in - guard let self, let component = self.component else { - return - } - guard case .up = action else { - return - } - component.repostAction() - }, - longPressAction: nil, - switchMediaInputMode: { - }, - updateMediaCancelFraction: { _ in - }, - lockMediaRecording: { - }, - stopAndPreviewMediaRecording: { - }, - moreAction: { _, _ in }, - context: component.context, - theme: component.theme, - strings: component.strings, - presentController: { _ in }, - audioRecorder: nil, - videoRecordingStatus: nil - )), - environment: {}, - containerSize: CGSize(width: 33.0, height: 33.0) - ) - if let repostButtonView = repostButton.view as? MessageInputActionButtonComponent.View { - if repostButtonView.superview == nil { - self.addSubview(repostButtonView) - } - var repostButtonFrame = CGRect(origin: CGPoint(x: rightContentOffset - repostButtonSize.width, y: floor((size.height - repostButtonSize.height) * 0.5)), size: repostButtonSize) - repostButtonFrame.origin.y += component.expandFraction * 45.0 - - forwardStatsTransition.setPosition(view: repostButtonView, position: repostButtonFrame.center) - forwardStatsTransition.setBounds(view: repostButtonView, bounds: CGRect(origin: CGPoint(), size: repostButtonFrame.size)) - forwardStatsTransition.setAlpha(view: repostButtonView, alpha: 1.0 - component.expandFraction) - - rightContentOffset -= repostButtonSize.width + 14.0 - - if forwardStatsText.superview == nil { - repostButtonView.button.view.addSubview(forwardStatsText) - } - - forwardStatsFrame.origin.x -= repostButtonFrame.minX - forwardStatsFrame.origin.y -= repostButtonFrame.minY - forwardStatsTransition.setPosition(view: forwardStatsText, position: forwardStatsFrame.center) - forwardStatsTransition.setBounds(view: forwardStatsText, bounds: CGRect(origin: CGPoint(), size: forwardStatsFrame.size)) - } - - let forwardButtonSize = forwardButton.update( - transition: likeStatsTransition, - component: AnyComponent(MessageInputActionButtonComponent( - mode: .forward, - storyId: component.storyItem.id, - action: { [weak self] _, action, _ in - guard let self, let component = self.component else { - return - } - guard case .up = action else { - return - } - component.forwardAction() - }, - longPressAction: nil, - switchMediaInputMode: { - }, - updateMediaCancelFraction: { _ in - }, - lockMediaRecording: { - }, - stopAndPreviewMediaRecording: { - }, - moreAction: { _, _ in }, - context: component.context, - theme: component.theme, - strings: component.strings, - presentController: { _ in }, - audioRecorder: nil, - videoRecordingStatus: nil - )), - environment: {}, - containerSize: CGSize(width: 33.0, height: 33.0) - ) - if let forwardButtonView = forwardButton.view { - if forwardButtonView.superview == nil { - self.addSubview(forwardButtonView) - } - var forwardButtonFrame = CGRect(origin: CGPoint(x: rightContentOffset - likeButtonSize.width, y: floor((size.height - forwardButtonSize.height) * 0.5)), size: forwardButtonSize) - forwardButtonFrame.origin.y += component.expandFraction * 45.0 - - likeStatsTransition.setPosition(view: forwardButtonView, position: forwardButtonFrame.center) - likeStatsTransition.setBounds(view: forwardButtonView, bounds: CGRect(origin: CGPoint(), size: forwardButtonFrame.size)) - likeStatsTransition.setAlpha(view: forwardButtonView, alpha: 1.0 - component.expandFraction) - - rightContentOffset -= forwardButtonSize.width + 8.0 - } - } else { - if let repostButton = self.repostButton { - self.repostButton = nil - repostButton.view?.removeFromSuperview() - } - if let forwardButton = self.forwardButton { - self.forwardButton = nil - forwardButton.view?.removeFromSuperview() - } - } - } else { - if let likeButton = self.likeButton { - self.likeButton = nil - likeButton.view?.removeFromSuperview() - } - if let repostButton = self.repostButton { - self.repostButton = nil - repostButton.view?.removeFromSuperview() - } - if let forwardButton = self.forwardButton { - self.forwardButton = nil - forwardButton.view?.removeFromSuperview() - } - } - var regularSegments: [AnimatedCountLabelView.Segment] = [] if viewCount != 0 { regularSegments.append(.number(viewCount, NSAttributedString(string: countString(Int64(viewCount)), font: Font.with(size: 15.0, traits: .monospacedNumbers), textColor: .white))) @@ -986,6 +693,300 @@ public final class StoryFooterPanelComponent: Component { contentX += repostsTextSize.width } + var rightContentOffset: CGFloat = availableSize.width - 12.0 + + if component.isChannel { + var likeStatsTransition = transition + var forwardStatsTransition = transition + + if transition.animation.isImmediate, !isFirstTime, let previousComponent, previousComponent.storyItem.id == component.storyItem.id, previousComponent.expandFraction == component.expandFraction { + likeStatsTransition = .easeInOut(duration: 0.2) + forwardStatsTransition = .easeInOut(duration: 0.2) + } + + let likeStatsText: AnimatedCountLabelView + if let current = self.likeStatsText { + likeStatsText = current + } else { + likeStatsTransition = likeStatsTransition.withAnimation(.none) + likeStatsText = AnimatedCountLabelView(frame: CGRect()) + likeStatsText.isUserInteractionEnabled = false + self.likeStatsText = likeStatsText + } + + let reactionStatsLayout = likeStatsText.update( + size: CGSize(width: availableSize.width, height: size.height), + segments: [ + .number(reactionCount, NSAttributedString(string: "\(reactionCount)", font: Font.with(size: 15.0, traits: .monospacedNumbers), textColor: .white)) + ], + transition: (isFirstTime || likeStatsTransition.animation.isImmediate) ? .immediate : ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut) + ) + var likeStatsFrame = CGRect(origin: CGPoint(x: rightContentOffset - reactionStatsLayout.size.width, y: floor((size.height - reactionStatsLayout.size.height) * 0.5)), size: reactionStatsLayout.size) + likeStatsFrame.origin.y += component.expandFraction * 45.0 + //likeStatsFrame.origin.x = (1.0 - component.expandFraction) * likeStatsFrame.origin.x + component.expandFraction * (contentX) + + likeStatsTransition.setPosition(view: likeStatsText, position: likeStatsFrame.center) + likeStatsTransition.setBounds(view: likeStatsText, bounds: CGRect(origin: CGPoint(), size: likeStatsFrame.size)) + var likeStatsAlpha: CGFloat = (1.0 - component.expandFraction) + if reactionCount == 0 { + likeStatsAlpha = 0.0 + } + likeStatsTransition.setAlpha(view: likeStatsText, alpha: likeStatsAlpha) + likeStatsTransition.setScale(view: likeStatsText, scale: reactionCount == 0 ? 0.001 : 1.0) + + if reactionCount != 0 { + rightContentOffset -= reactionStatsLayout.size.width + 1.0 + } + + let likeButton: ComponentView + if let current = self.likeButton { + likeButton = current + } else { + likeButton = ComponentView() + self.likeButton = likeButton + } + + let likeButtonSize = likeButton.update( + transition: likeStatsTransition, + component: AnyComponent(MessageInputActionButtonComponent( + mode: .like(reaction: component.myReaction?.reaction, file: component.myReaction?.file, animationFileId: component.myReaction?.animationFileId), + storyId: component.storyItem.id, + action: { [weak self] _, action, _ in + guard let self, let component = self.component else { + return + } + guard case .up = action else { + return + } + component.likeAction() + }, + longPressAction: nil, + switchMediaInputMode: { + }, + updateMediaCancelFraction: { _ in + }, + lockMediaRecording: { + }, + stopAndPreviewMediaRecording: { + }, + moreAction: { _, _ in }, + context: component.context, + theme: component.theme, + strings: component.strings, + presentController: { _ in }, + audioRecorder: nil, + videoRecordingStatus: nil + )), + environment: {}, + containerSize: CGSize(width: 33.0, height: 33.0) + ) + if let likeButtonView = likeButton.view as? MessageInputActionButtonComponent.View { + if likeButtonView.superview == nil { + self.addSubview(likeButtonView) + } + var likeButtonFrame = CGRect(origin: CGPoint(x: rightContentOffset - likeButtonSize.width, y: floor((size.height - likeButtonSize.height) * 0.5)), size: likeButtonSize) + likeButtonFrame.origin.y += component.expandFraction * 45.0 + + if let likeButtonTracingOffsetView = self.likeButtonTracingOffsetView { + let difference = CGPoint(x: likeButtonFrame.midX - likeButtonView.layer.position.x, y: likeButtonFrame.midY - likeButtonView.layer.position.y) + if difference != CGPoint() { + likeStatsTransition.setPosition(view: likeButtonTracingOffsetView, position: likeButtonTracingOffsetView.layer.position.offsetBy(dx: difference.x, dy: difference.y)) + } + } + + likeStatsTransition.setPosition(view: likeButtonView, position: likeButtonFrame.center) + likeStatsTransition.setBounds(view: likeButtonView, bounds: CGRect(origin: CGPoint(), size: likeButtonFrame.size)) + likeStatsTransition.setAlpha(view: likeButtonView, alpha: 1.0 - component.expandFraction) + + rightContentOffset -= likeButtonSize.width + 14.0 + + if likeStatsText.superview == nil { + likeButtonView.button.view.addSubview(likeStatsText) + } + + likeStatsFrame.origin.x -= likeButtonFrame.minX + likeStatsFrame.origin.y -= likeButtonFrame.minY + likeStatsTransition.setPosition(view: likeStatsText, position: likeStatsFrame.center) + likeStatsTransition.setBounds(view: likeStatsText, bounds: CGRect(origin: CGPoint(), size: likeStatsFrame.size)) + } + + if component.canShare { + let forwardStatsText: AnimatedCountLabelView + if let current = self.forwardStatsText { + forwardStatsText = current + } else { + forwardStatsTransition = forwardStatsTransition.withAnimation(.none) + forwardStatsText = AnimatedCountLabelView(frame: CGRect()) + forwardStatsText.isUserInteractionEnabled = false + self.forwardStatsText = forwardStatsText + } + + let forwardStatsLayout = forwardStatsText.update( + size: CGSize(width: availableSize.width, height: size.height), + segments: [ + .number(forwardCount, NSAttributedString(string: "\(forwardCount)", font: Font.with(size: 15.0, traits: .monospacedNumbers), textColor: .white)) + ], + transition: (isFirstTime || likeStatsTransition.animation.isImmediate) ? .immediate : ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut) + ) + var forwardStatsFrame = CGRect(origin: CGPoint(x: rightContentOffset - forwardStatsLayout.size.width, y: floor((size.height - forwardStatsLayout.size.height) * 0.5)), size: forwardStatsLayout.size) + forwardStatsFrame.origin.y += component.expandFraction * 45.0 + + var forwardStatsAlpha: CGFloat = (1.0 - component.expandFraction) + if forwardCount == 0 { + forwardStatsAlpha = 0.0 + } + forwardStatsTransition.setAlpha(view: forwardStatsText, alpha: forwardStatsAlpha) + forwardStatsTransition.setScale(view: forwardStatsText, scale: forwardCount == 0 ? 0.001 : 1.0) + + if forwardCount != 0 { + rightContentOffset -= forwardStatsLayout.size.width + 1.0 + } + + let repostButton: ComponentView + if let current = self.repostButton { + repostButton = current + } else { + repostButton = ComponentView() + self.repostButton = repostButton + } + + let forwardButton: ComponentView + if let current = self.forwardButton { + forwardButton = current + } else { + forwardButton = ComponentView() + self.forwardButton = forwardButton + } + + let repostButtonSize = repostButton.update( + transition: likeStatsTransition, + component: AnyComponent(MessageInputActionButtonComponent( + mode: .repost, + storyId: component.storyItem.id, + action: { [weak self] _, action, _ in + guard let self, let component = self.component else { + return + } + guard case .up = action else { + return + } + component.repostAction() + }, + longPressAction: nil, + switchMediaInputMode: { + }, + updateMediaCancelFraction: { _ in + }, + lockMediaRecording: { + }, + stopAndPreviewMediaRecording: { + }, + moreAction: { _, _ in }, + context: component.context, + theme: component.theme, + strings: component.strings, + presentController: { _ in }, + audioRecorder: nil, + videoRecordingStatus: nil + )), + environment: {}, + containerSize: CGSize(width: 33.0, height: 33.0) + ) + if let repostButtonView = repostButton.view as? MessageInputActionButtonComponent.View { + if repostButtonView.superview == nil { + self.addSubview(repostButtonView) + } + var repostButtonFrame = CGRect(origin: CGPoint(x: rightContentOffset - repostButtonSize.width, y: floor((size.height - repostButtonSize.height) * 0.5)), size: repostButtonSize) + repostButtonFrame.origin.y += component.expandFraction * 45.0 + + forwardStatsTransition.setPosition(view: repostButtonView, position: repostButtonFrame.center) + forwardStatsTransition.setBounds(view: repostButtonView, bounds: CGRect(origin: CGPoint(), size: repostButtonFrame.size)) + forwardStatsTransition.setAlpha(view: repostButtonView, alpha: 1.0 - component.expandFraction) + + rightContentOffset -= repostButtonSize.width + 14.0 + + if forwardStatsText.superview == nil { + repostButtonView.button.view.addSubview(forwardStatsText) + } + + forwardStatsFrame.origin.x -= repostButtonFrame.minX + forwardStatsFrame.origin.y -= repostButtonFrame.minY + forwardStatsTransition.setPosition(view: forwardStatsText, position: forwardStatsFrame.center) + forwardStatsTransition.setBounds(view: forwardStatsText, bounds: CGRect(origin: CGPoint(), size: forwardStatsFrame.size)) + } + + let forwardButtonSize = forwardButton.update( + transition: likeStatsTransition, + component: AnyComponent(MessageInputActionButtonComponent( + mode: .forward, + storyId: component.storyItem.id, + action: { [weak self] _, action, _ in + guard let self, let component = self.component else { + return + } + guard case .up = action else { + return + } + component.forwardAction() + }, + longPressAction: nil, + switchMediaInputMode: { + }, + updateMediaCancelFraction: { _ in + }, + lockMediaRecording: { + }, + stopAndPreviewMediaRecording: { + }, + moreAction: { _, _ in }, + context: component.context, + theme: component.theme, + strings: component.strings, + presentController: { _ in }, + audioRecorder: nil, + videoRecordingStatus: nil + )), + environment: {}, + containerSize: CGSize(width: 33.0, height: 33.0) + ) + if let forwardButtonView = forwardButton.view { + if forwardButtonView.superview == nil { + self.addSubview(forwardButtonView) + } + var forwardButtonFrame = CGRect(origin: CGPoint(x: rightContentOffset - likeButtonSize.width, y: floor((size.height - forwardButtonSize.height) * 0.5)), size: forwardButtonSize) + forwardButtonFrame.origin.y += component.expandFraction * 45.0 + + likeStatsTransition.setPosition(view: forwardButtonView, position: forwardButtonFrame.center) + likeStatsTransition.setBounds(view: forwardButtonView, bounds: CGRect(origin: CGPoint(), size: forwardButtonFrame.size)) + likeStatsTransition.setAlpha(view: forwardButtonView, alpha: 1.0 - component.expandFraction) + + rightContentOffset -= forwardButtonSize.width + 8.0 + } + } else { + if let repostButton = self.repostButton { + self.repostButton = nil + repostButton.view?.removeFromSuperview() + } + if let forwardButton = self.forwardButton { + self.forwardButton = nil + forwardButton.view?.removeFromSuperview() + } + } + } else { + if let likeButton = self.likeButton { + self.likeButton = nil + likeButton.view?.removeFromSuperview() + } + if let repostButton = self.repostButton { + self.repostButton = nil + repostButton.view?.removeFromSuperview() + } + if let forwardButton = self.forwardButton { + self.forwardButton = nil + forwardButton.view?.removeFromSuperview() + } + } + let statsButtonWidth = availableSize.width - 80.0 transition.setFrame(view: self.viewStatsButton, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: statsButtonWidth, height: baseHeight))) diff --git a/submodules/TelegramUI/Components/WallpaperPreviewMedia/Sources/WallpaperPreviewMedia.swift b/submodules/TelegramUI/Components/WallpaperPreviewMedia/Sources/WallpaperPreviewMedia.swift index 7903e414e8..7db183dbf5 100644 --- a/submodules/TelegramUI/Components/WallpaperPreviewMedia/Sources/WallpaperPreviewMedia.swift +++ b/submodules/TelegramUI/Components/WallpaperPreviewMedia/Sources/WallpaperPreviewMedia.swift @@ -9,6 +9,7 @@ public enum WallpaperPreviewMediaContent: Equatable { case color(UIColor) case gradient([UInt32], Int32?) case themeSettings(TelegramThemeSettings) + case emoticon(String) } public final class WallpaperPreviewMedia: Media { @@ -64,6 +65,8 @@ public extension WallpaperPreviewMedia { self.init(content: .file(file: file.file, colors: file.settings.colors, rotation: file.settings.rotation, intensity: file.settings.intensity, false, false)) case let .image(representations, _): self.init(content: .image(representations: representations)) + case let .emoticon(emoticon): + self.init(content: .emoticon(emoticon)) default: return nil } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 7d96e3fd13..f63dd01c65 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -919,6 +919,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.presentThemeSelection() return true } + if peer is TelegramChannel { + return true + } strongSelf.chatDisplayNode.dismissInput() var options = WallpaperPresentationOptions() var intensity: Int32? @@ -3128,9 +3131,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor) }, action: { [weak self] controller, f in if let strongSelf = self { - if strongSelf.context.sharedContext.immediateExperimentalUISettings.dustEffect { - strongSelf.chatDisplayNode.historyNode.setCurrentDeleteAnimationCorrelationIds([id]) - } let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: [id], type: .forLocalPeer).startStandalone() } f(.dismissWithoutContent) @@ -8744,15 +8744,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } if isAction && (actions.options == .deleteGlobally || actions.options == .deleteLocally) { - if strongSelf.context.sharedContext.immediateExperimentalUISettings.dustEffect { - strongSelf.chatDisplayNode.historyNode.setCurrentDeleteAnimationCorrelationIds(messageIds) - } let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: actions.options == .deleteLocally ? .forLocalPeer : .forEveryone).startStandalone() completion(.dismissWithoutContent) } else if (messages.first?.flags.isSending ?? false) { - if strongSelf.context.sharedContext.immediateExperimentalUISettings.dustEffect { - strongSelf.chatDisplayNode.historyNode.setCurrentDeleteAnimationCorrelationIds(messageIds) - } let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone, deleteAllInGroup: true).startStandalone() completion(.dismissWithoutContent) } else { @@ -17215,9 +17209,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let _ = strongSelf.context.engine.messages.deleteAllMessagesWithAuthor(peerId: peerId, authorId: author.id, namespace: Namespaces.Message.Cloud).startStandalone() let _ = strongSelf.context.engine.messages.clearAuthorHistory(peerId: peerId, memberId: author.id).startStandalone() } else if actions.contains(0) { - if strongSelf.context.sharedContext.immediateExperimentalUISettings.dustEffect { - strongSelf.chatDisplayNode.historyNode.setCurrentDeleteAnimationCorrelationIds(messageIds) - } let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).startStandalone() } if actions.contains(1) { @@ -17256,9 +17247,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G actionSheet?.dismissAnimated() if let strongSelf = self { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }) - if strongSelf.context.sharedContext.immediateExperimentalUISettings.dustEffect { - strongSelf.chatDisplayNode.historyNode.setCurrentDeleteAnimationCorrelationIds(messageIds) - } let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).startStandalone() } })) @@ -17283,7 +17271,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else { globalTitle = self.presentationData.strings.Conversation_DeleteMessagesForEveryone } - contextItems.append(.action(ContextMenuActionItem(text: globalTitle, textColor: .destructive, icon: { _ in nil }, action: { [weak self] _, f in + contextItems.append(.action(ContextMenuActionItem(text: globalTitle, textColor: .destructive, icon: { _ in nil }, action: { [weak self] c, f in if let strongSelf = self { var giveaway: TelegramMediaGiveaway? for messageId in messageIds { @@ -17296,9 +17284,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } let commit = { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }) - if strongSelf.context.sharedContext.immediateExperimentalUISettings.dustEffect { - strongSelf.chatDisplayNode.historyNode.setCurrentDeleteAnimationCorrelationIds(messageIds) - } let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).startStandalone() } if let giveaway { @@ -17311,8 +17296,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } f(.default) } else { - commit() - f(.dismissWithoutContent) + c.dismiss(completion: { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: { + commit() + }) + }) } } }))) @@ -17320,9 +17308,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G actionSheet?.dismissAnimated() if let strongSelf = self { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }) - if strongSelf.context.sharedContext.immediateExperimentalUISettings.dustEffect { - strongSelf.chatDisplayNode.historyNode.setCurrentDeleteAnimationCorrelationIds(messageIds) - } let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).startStandalone() } })) @@ -17346,29 +17331,20 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let strongSelf = self { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }) - if strongSelf.context.sharedContext.immediateExperimentalUISettings.dustEffect { - c.dismiss(completion: { [weak strongSelf] in - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: { - guard let strongSelf else { - return - } - strongSelf.chatDisplayNode.historyNode.setCurrentDeleteAnimationCorrelationIds(messageIds) - let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: unsendPersonalMessages ? .forEveryone : .forLocalPeer).startStandalone() - }) + c.dismiss(completion: { [weak strongSelf] in + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: { + guard let strongSelf else { + return + } + let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: unsendPersonalMessages ? .forEveryone : .forLocalPeer).startStandalone() }) - } else { - f(.dismissWithoutContent) - let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: unsendPersonalMessages ? .forEveryone : .forLocalPeer).startStandalone() - } + }) } }))) items.append(ActionSheetButtonItem(title: localOptionText, color: .destructive, action: { [weak self, weak actionSheet] in actionSheet?.dismissAnimated() if let strongSelf = self { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }) - if strongSelf.context.sharedContext.immediateExperimentalUISettings.dustEffect { - strongSelf.chatDisplayNode.historyNode.setCurrentDeleteAnimationCorrelationIds(messageIds) - } let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: unsendPersonalMessages ? .forEveryone : .forLocalPeer).startStandalone() } diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 5b01fa8432..7e5e265cb0 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -29,6 +29,7 @@ import ChatBotInfoItem import ChatMessageItem import ChatMessageItemImpl import ChatMessageItemView +import ChatMessageBubbleItemNode import ChatMessageTransitionNode import ChatControllerInteraction import DustEffect @@ -329,7 +330,8 @@ private func extractAssociatedData( translateToLanguage: String?, maxReadStoryId: Int32?, recommendedChannels: RecommendedChannels?, - audioTranscriptionTrial: AudioTranscription.TrialState + audioTranscriptionTrial: AudioTranscription.TrialState, + chatThemes: [TelegramTheme] ) -> ChatMessageItemAssociatedData { var automaticDownloadPeerId: EnginePeer.Id? var automaticMediaDownloadPeerType: MediaAutoDownloadPeerType = .channel @@ -384,7 +386,7 @@ private func extractAssociatedData( automaticDownloadPeerId = message.messageId.peerId } - return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadPeerId: automaticDownloadPeerId, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, defaultReaction: defaultReaction, isPremium: isPremium, accountPeer: accountPeer, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, topicAuthorId: topicAuthorId, hasBots: hasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial) + return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadPeerId: automaticDownloadPeerId, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, defaultReaction: defaultReaction, isPremium: isPremium, accountPeer: accountPeer, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, topicAuthorId: topicAuthorId, hasBots: hasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes) } private extension ChatHistoryLocationInput { @@ -677,6 +679,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto private var toLang: String? + private var allowDustEffect: Bool = true private var dustEffectLayer: DustEffectLayer? public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal), chatLocation: ChatLocation, chatLocationContextHolder: Atomic, tagMask: MessageTags?, source: ChatHistoryListSource, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal?, NoError>, mode: ChatHistoryListMode = .bubbles, messageTransitionNode: @escaping () -> ChatMessageTransitionNodeImpl?) { @@ -694,8 +697,13 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto self.controllerInteraction = controllerInteraction self.mode = mode - if let data = context.currentAppConfiguration.with({ $0 }).data, let _ = data["ios_killswitch_disable_unread_alignment"] { - self.enableUnreadAlignment = false + if let data = context.currentAppConfiguration.with({ $0 }).data { + if let _ = data["ios_killswitch_disable_unread_alignment"] { + self.enableUnreadAlignment = false + } + if let _ = data["ios_killswitch_disable_dust_effect"] { + self.allowDustEffect = false + } } let presentationData = updatedPresentationData.initial @@ -1367,6 +1375,8 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto let audioTranscriptionTrial = self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.AudioTranscriptionTrial()) + let chatThemes = self.context.engine.themes.getChatThemes(accountManager: self.context.sharedContext.accountManager) + let messageViewQueue = Queue.mainQueue() let historyViewTransitionDisposable = combineLatest(queue: messageViewQueue, historyViewUpdate, @@ -1387,8 +1397,9 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto translationState, maxReadStoryId, recommendedChannels, - audioTranscriptionTrial - ).startStrict(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, availableReactions, defaultReaction, accountPeer, suggestAudioTranscription, promises, topicAuthorId, translationState, maxReadStoryId, recommendedChannels, audioTranscriptionTrial in + audioTranscriptionTrial, + chatThemes + ).startStrict(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, availableReactions, defaultReaction, accountPeer, suggestAudioTranscription, promises, topicAuthorId, translationState, maxReadStoryId, recommendedChannels, audioTranscriptionTrial, chatThemes in let (historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, currentlyPlayingMessageIdAndType, scrollToMessageId, chatHasBots, allAdMessages) = promises func applyHole() { @@ -1544,7 +1555,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto translateToLanguage = languageCode } - let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageIdAndType?.0, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, defaultReaction: defaultReaction, isPremium: isPremium, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, accountPeer: accountPeer, topicAuthorId: topicAuthorId, hasBots: chatHasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial) + let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageIdAndType?.0, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, defaultReaction: defaultReaction, isPremium: isPremium, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, accountPeer: accountPeer, topicAuthorId: topicAuthorId, hasBots: chatHasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes) let filteredEntries = chatHistoryEntriesForView( location: chatLocation, @@ -2961,22 +2972,16 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto self.hasActiveTransition = true let transition = self.enqueuedHistoryViewTransitions.removeFirst() - var expiredMessageIds = Set() + var expiredMessageStableIds = Set() if let previousHistoryView = self.historyView, transition.options.contains(.AnimateInsertion) { - let demoDustEffect = self.context.sharedContext.immediateExperimentalUISettings.dustEffect - - var existingIds = Set() + var existingStableIds = Set() for entry in transition.historyView.filteredEntries { switch entry { case let .MessageEntry(message, _, _, _, _, _): - if message.autoremoveAttribute != nil || demoDustEffect { - existingIds.insert(message.id) - } + existingStableIds.insert(message.stableId) case let .MessageGroupEntry(_, messages, _): for message in messages { - if message.0.autoremoveAttribute != nil || demoDustEffect { - existingIds.insert(message.0.id) - } + existingStableIds.insert(message.0.stableId) } default: break @@ -2986,25 +2991,25 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto for entry in previousHistoryView.filteredEntries { switch entry { case let .MessageEntry(message, _, _, _, _, _): - if !existingIds.contains(message.id) { + if !existingStableIds.contains(message.stableId) { if let autoremoveAttribute = message.autoremoveAttribute, let countdownBeginTime = autoremoveAttribute.countdownBeginTime { let exipiresAt = countdownBeginTime + autoremoveAttribute.timeout if exipiresAt >= currentTimestamp - 1 { - expiredMessageIds.insert(message.id) + expiredMessageStableIds.insert(message.stableId) } - } else if demoDustEffect { - expiredMessageIds.insert(message.id) + } else { + expiredMessageStableIds.insert(message.stableId) } } case let .MessageGroupEntry(_, messages, _): for message in messages { - if !existingIds.contains(message.0.id) { + if !existingStableIds.contains(message.0.stableId) { if let autoremoveAttribute = message.0.autoremoveAttribute, let countdownBeginTime = autoremoveAttribute.countdownBeginTime { let exipiresAt = countdownBeginTime + autoremoveAttribute.timeout if exipiresAt >= currentTimestamp - 1 { - expiredMessageIds.insert(message.0.id) - } else if demoDustEffect { - expiredMessageIds.insert(message.0.id) + expiredMessageStableIds.insert(message.0.stableId) + } else { + expiredMessageStableIds.insert(message.0.stableId) } } } @@ -3014,17 +3019,23 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto } } } - self.currentDeleteAnimationCorrelationIds.formUnion(expiredMessageIds) + self.currentDeleteAnimationCorrelationIds.formUnion(expiredMessageStableIds) - var appliedDeleteAnimationCorrelationIds = Set() - if !self.currentDeleteAnimationCorrelationIds.isEmpty { + var appliedDeleteAnimationCorrelationIds = Set() + if !self.currentDeleteAnimationCorrelationIds.isEmpty && self.allowDustEffect { var foundItemNodes: [ChatMessageItemView] = [] self.forEachItemNode { itemNode in if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item { for (message, _) in item.content { - if self.currentDeleteAnimationCorrelationIds.contains(message.id) { - appliedDeleteAnimationCorrelationIds.insert(message.id) - self.currentDeleteAnimationCorrelationIds.remove(message.id) + if let itemNode = itemNode as? ChatMessageBubbleItemNode { + if itemNode.isServiceLikeMessage() { + continue + } + } + + if self.currentDeleteAnimationCorrelationIds.contains(message.stableId) { + appliedDeleteAnimationCorrelationIds.insert(message.stableId) + self.currentDeleteAnimationCorrelationIds.remove(message.stableId) foundItemNodes.append(itemNode) } } @@ -4055,11 +4066,11 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto self.currentSendAnimationCorrelationIds = value } - private var currentDeleteAnimationCorrelationIds = Set() - func setCurrentDeleteAnimationCorrelationIds(_ value: Set) { + private var currentDeleteAnimationCorrelationIds = Set() + func setCurrentDeleteAnimationCorrelationIds(_ value: Set) { self.currentDeleteAnimationCorrelationIds = value } - private var currentAppliedDeleteAnimationCorrelationIds = Set() + private var currentAppliedDeleteAnimationCorrelationIds = Set() var animationCorrelationMessagesFound: (([Int64: ChatMessageItemView]) -> Void)? @@ -4159,8 +4170,8 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto if !self.currentAppliedDeleteAnimationCorrelationIds.isEmpty { if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item { for (message, _) in item.content { - if self.currentAppliedDeleteAnimationCorrelationIds.contains(message.id) { - return 1.5 + if self.currentAppliedDeleteAnimationCorrelationIds.contains(message.stableId) { + return 0.8 } } }