From 22df2dbdb997b808d054c1f835d350eed862bd5b Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 14 Apr 2026 03:33:34 +0200 Subject: [PATCH 1/2] Various fixes --- .../BrowserUI/Sources/BrowserWebContent.swift | 27 ++-- .../Sources/ContactsPeerItem.swift | 82 +++++++++-- .../Sources/ICloudResources.swift | 23 ++- .../Sources/ItemListPeerItem.swift | 1 + submodules/MimeTypes/Sources/TGMimeTypeMap.m | 2 +- .../Sources/PremiumLimitScreen.swift | 59 ++++---- .../Network/FetchedMediaResource.swift | 5 + .../ParticipantRankMessageAttribute.swift | 2 - .../Resources/PresentationResourcesChat.swift | 6 +- .../Sources/ButtonComponent.swift | 105 +++++++++++--- .../CameraScreen/Sources/CameraScreen.swift | 2 +- .../Sources/ChatMessageBubbleItemNode.swift | 132 +++++++++++------- .../Sources/ChatMessageReplyInfoNode.swift | 7 +- .../Sources/GiftSetupScreen.swift | 67 ++++++++- .../Sources/GlassBackgroundComponent.swift | 42 ++++-- .../Sources/TouchEffect.swift | 22 +-- .../Sources/ListActionItemComponent.swift | 14 +- ...hatControllerOpenBankCardContextMenu.swift | 13 +- ...ChatControllerOpenCommandContextMenu.swift | 11 +- .../ChatControllerOpenDateContextMenu.swift | 13 +- ...ChatControllerOpenHashtagContextMenu.swift | 11 +- .../ChatControllerOpenLinkContextMenu.swift | 9 +- .../ChatControllerOpenPhoneContextMenu.swift | 11 +- ...hatControllerOpenTimecodeContextMenu.swift | 11 +- ...hatControllerOpenUsernameContextMenu.swift | 11 +- .../Sources/UndoOverlayControllerNode.swift | 2 +- submodules/WebUI/Sources/WebAppWebView.swift | 27 ++++ 27 files changed, 480 insertions(+), 237 deletions(-) diff --git a/submodules/BrowserUI/Sources/BrowserWebContent.swift b/submodules/BrowserUI/Sources/BrowserWebContent.swift index afe1d9ed04..09d96a19d3 100644 --- a/submodules/BrowserUI/Sources/BrowserWebContent.swift +++ b/submodules/BrowserUI/Sources/BrowserWebContent.swift @@ -423,16 +423,8 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU case "oauth_request": let url = json?["url"] as? String if let url { - let securityOrigin = message.frameInfo.securityOrigin - var origin = "" - origin.append(securityOrigin.protocol) - origin.append("://") - origin.append(securityOrigin.host) - if securityOrigin.port != 0 { - origin.append(":") - origin.append("\(securityOrigin.port)") - } - + let origin = message.frameInfo.securityOriginString + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let subject: MessageActionUrlSubject = .url(url: url, inAppOrigin: origin) let _ = (self.context.engine.messages.requestMessageActionUrlAuth(subject: subject) @@ -2035,3 +2027,18 @@ private func findScrollView(view: UIView?) -> UIScrollView? { return nil } } + +private extension WKFrameInfo { + var securityOriginString: String { + let securityOrigin = self.securityOrigin + var origin = "" + origin.append(securityOrigin.protocol) + origin.append("://") + origin.append(securityOrigin.host) + if securityOrigin.port != 0 { + origin.append(":") + origin.append("\(securityOrigin.port)") + } + return origin + } +} diff --git a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift index d46b90abf3..a6a17f1b19 100644 --- a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift +++ b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift @@ -475,6 +475,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { private var credibilityIconComponent: EmojiStatusComponent? private var verifiedIconView: ComponentHostView? private var verifiedIconComponent: EmojiStatusComponent? + private var emojiStatusIconView: ComponentHostView? + private var emojiStatusIconComponent: EmojiStatusComponent? public let statusNode: TextNodeWithEntities private var statusIconNode: ASImageNode? private var badgeBackgroundNode: ASImageNode? @@ -552,6 +554,14 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { containerSize: verifiedIconView.bounds.size ) } + if let emojiStatusIconView = self.emojiStatusIconView, let emojiStatusIconComponent = self.emojiStatusIconComponent { + let _ = emojiStatusIconView.update( + transition: .immediate, + component: AnyComponent(emojiStatusIconComponent.withVisibleForAnimations(self.visibilityStatus)), + environment: {}, + containerSize: emojiStatusIconView.bounds.size + ) + } if let avatarIconView = self.avatarIconView, let avatarIconComponent = self.avatarIconComponent { let _ = avatarIconView.update( transition: .immediate, @@ -840,27 +850,29 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 }) - var credibilityIcon: EmojiStatusComponent.Content? - var credibilityParticleColor: UIColor? + var credibilityStatusIcon: EmojiStatusComponent.Content? var verifiedIcon: EmojiStatusComponent.Content? + var emojiStatusIcon: EmojiStatusComponent.Content? + var emojiStatusParticleColor: UIColor? + switch item.peer { case let .peer(peer, _): if let peer = peer, (peer.id != item.context.account.peerId || item.peerMode == .memberList || item.aliasHandling == .standard) { if peer.isScam { - credibilityIcon = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_ScamAccount.uppercased()) + credibilityStatusIcon = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_ScamAccount.uppercased()) } else if peer.isFake { - credibilityIcon = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_FakeAccount.uppercased()) + credibilityStatusIcon = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_FakeAccount.uppercased()) } else if let emojiStatus = peer.emojiStatus, !item.isAd { - credibilityIcon = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 20.0, height: 20.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2)) + emojiStatusIcon = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 20.0, height: 20.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2)) if let color = emojiStatus.color { - credibilityParticleColor = UIColor(rgb: UInt32(bitPattern: color)) + emojiStatusParticleColor = UIColor(rgb: UInt32(bitPattern: color)) } } else if peer.isPremium && !premiumConfiguration.isPremiumDisabled { - credibilityIcon = .premium(color: item.presentationData.theme.list.itemAccentColor) + credibilityStatusIcon = .premium(color: item.presentationData.theme.list.itemAccentColor) } if peer.isVerified { - credibilityIcon = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor, sizeType: .compact) + credibilityStatusIcon = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor, sizeType: .compact) } if let verificationIconFileId = peer.verificationIconFileId { verifiedIcon = .animation(content: .customEmoji(fileId: verificationIconFileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(0)) @@ -1105,9 +1117,9 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { additionalTitleInset += 16.0 } } - if let credibilityIcon { + if let credibilityStatusIcon { additionalTitleInset += 3.0 - switch credibilityIcon { + switch credibilityStatusIcon { case let .text(_, string): let textString = NSAttributedString(string: string, font: Font.bold(10.0), textColor: .black, paragraphAlignment: .center) let stringRect = textString.boundingRect(with: CGSize(width: 100.0, height: 16.0), options: .usesLineFragmentOrigin, context: nil) @@ -1116,6 +1128,10 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { additionalTitleInset += 16.0 } } + if let _ = emojiStatusIcon { + additionalTitleInset += 3.0 + additionalTitleInset += 16.0 + } if let actionButtons = actionButtons { additionalTitleInset += 3.0 for actionButton in actionButtons { @@ -1587,7 +1603,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { } } - if let credibilityIcon { + if let credibilityStatusIcon { let animationCache = item.context.animationCache let animationRenderer = item.context.animationRenderer @@ -1604,8 +1620,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { context: item.context, animationCache: animationCache, animationRenderer: animationRenderer, - content: credibilityIcon, - particleColor: credibilityParticleColor, + content: credibilityStatusIcon, + particleColor: nil, isVisibleForAnimations: strongSelf.visibilityStatus, action: nil, emojiFileUpdated: nil @@ -1627,6 +1643,46 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { credibilityIconView.removeFromSuperview() } + if let emojiStatusIcon { + let animationCache = item.context.animationCache + let animationRenderer = item.context.animationRenderer + + let emojiStatusIconView: ComponentHostView + if let current = strongSelf.emojiStatusIconView { + emojiStatusIconView = current + } else { + emojiStatusIconView = ComponentHostView() + strongSelf.offsetContainerNode.view.addSubview(emojiStatusIconView) + strongSelf.emojiStatusIconView = emojiStatusIconView + } + + let emojiStatusIconComponent = EmojiStatusComponent( + context: item.context, + animationCache: animationCache, + animationRenderer: animationRenderer, + content: emojiStatusIcon, + particleColor: emojiStatusParticleColor, + isVisibleForAnimations: strongSelf.visibilityStatus, + action: nil, + emojiFileUpdated: nil + ) + strongSelf.emojiStatusIconComponent = emojiStatusIconComponent + + let iconSize = emojiStatusIconView.update( + transition: .immediate, + component: AnyComponent(emojiStatusIconComponent), + environment: {}, + containerSize: CGSize(width: 16.0, height: 16.0) + ) + + nextIconX += 4.0 + transition.updateFrame(view: emojiStatusIconView, frame: CGRect(origin: CGPoint(x: nextIconX, y: floorToScreenPixels(titleFrame.midY - iconSize.height / 2.0)), size: iconSize)) + nextIconX += iconSize.width + } else if let emojiStatusIconView = strongSelf.emojiStatusIconView { + strongSelf.emojiStatusIconView = nil + emojiStatusIconView.removeFromSuperview() + } + if let (titleBadgeLayout, titleBadgeApply) = titleBadgeLayoutAndApply { let titleBadgeNode = titleBadgeApply() let backgroundView: UIImageView diff --git a/submodules/ICloudResources/Sources/ICloudResources.swift b/submodules/ICloudResources/Sources/ICloudResources.swift index 2ef67e5e9d..2ccf30e87f 100644 --- a/submodules/ICloudResources/Sources/ICloudResources.swift +++ b/submodules/ICloudResources/Sources/ICloudResources.swift @@ -95,7 +95,8 @@ private func descriptionWithUrl(_ url: URL) -> ICloudFileDescription? { } var audioMetadata: ICloudFileDescription.AudioMetadata? - if ["mp3", "m4a"].contains(url.pathExtension.lowercased()) { + let fileExtension = url.pathExtension.lowercased() + if ["mp3", "m4a"].contains(fileExtension) { let asset = AVURLAsset(url: url) let title = AVMetadataItem.metadataItems(from: asset.commonMetadata, withKey: AVMetadataKey.commonKeyTitle, keySpace: AVMetadataKeySpace.common).first?.stringValue let performer = AVMetadataItem.metadataItems(from: asset.commonMetadata, withKey: AVMetadataKey.commonKeyArtist, keySpace: AVMetadataKeySpace.common).first?.stringValue @@ -103,6 +104,26 @@ private func descriptionWithUrl(_ url: URL) -> ICloudFileDescription? { if duration > 0 { audioMetadata = ICloudFileDescription.AudioMetadata(title: title, performer: performer, duration: Int(duration)) } + } else if fileExtension == "flac" { + let asset = AVURLAsset(url: url) + var title: String? + var performer: String? + let vorbisComment = AVMetadataFormat(rawValue: "org.xiph.vorbis-comment") + if asset.availableMetadataFormats.contains(vorbisComment) { + let items = asset.metadata(forFormat: vorbisComment) + for item in items { + if item.commonKey == AVMetadataKey.commonKeyTitle { + title = item.stringValue + } + if item.commonKey == AVMetadataKey.commonKeyArtist { + performer = item.stringValue + } + } + } + let duration = CMTimeGetSeconds(asset.duration) + if duration > 0 { + audioMetadata = ICloudFileDescription.AudioMetadata(title: title, performer: performer, duration: Int(duration)) + } } let result = ICloudFileDescription(urlData: urlData.base64EncodedString(), fileName: fileName, fileSize: fileSize, audioMetadata: audioMetadata) diff --git a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift index e7612f066c..0cdf4f54d0 100644 --- a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift +++ b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift @@ -952,6 +952,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo if item.peer.isVerified { credibilityIcon = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor, sizeType: .compact) + credibilityParticleColor = nil } if let verificationIconFileId = item.peer.verificationIconFileId { verifiedIcon = .animation(content: .customEmoji(fileId: verificationIconFileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(0)) diff --git a/submodules/MimeTypes/Sources/TGMimeTypeMap.m b/submodules/MimeTypes/Sources/TGMimeTypeMap.m index 89cd7548ef..76d7fe9f65 100644 --- a/submodules/MimeTypes/Sources/TGMimeTypeMap.m +++ b/submodules/MimeTypes/Sources/TGMimeTypeMap.m @@ -95,7 +95,7 @@ static void initializeMapping() mimeToExtension[@"application/x-dms"] = @"dms"; extensionToMime[@"dms"] = @"application/x-dms"; mimeToExtension[@"application/x-doom"] = @"wad"; extensionToMime[@"wad"] = @"application/x-doom"; mimeToExtension[@"application/x-dvi"] = @"dvi"; extensionToMime[@"dvi"] = @"application/x-dvi"; - mimeToExtension[@"application/x-flac"] = @"flac"; extensionToMime[@"flac"] = @"application/x-flac"; + mimeToExtension[@"audio/flac"] = @"flac"; extensionToMime[@"flac"] = @"audio/flac"; mimeToExtension[@"application/x-font"] = @"pfa"; extensionToMime[@"pfa"] = @"application/x-font"; mimeToExtension[@"application/x-font"] = @"pfb"; extensionToMime[@"pfb"] = @"application/x-font"; mimeToExtension[@"application/x-font"] = @"gsf"; extensionToMime[@"gsf"] = @"application/x-font"; diff --git a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift index b64edab58f..b487f1ea42 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift @@ -448,6 +448,34 @@ public class PremiumLimitDisplayComponent: Component { } view.frame = CGRect(origin: CGPoint(x: activityPosition - 12.0 - inactiveValueSize.width, y: floorToScreenPixels((lineHeight - inactiveValueSize.height) / 2.0)), size: inactiveValueSize) } + + let activeValueSize = self.activeValueLabel.update( + transition: .immediate, + component: AnyComponent( + MultilineTextComponent( + text: .plain( + NSAttributedString( + string: component.activeValue, + font: Font.semibold(15.0), + textColor: rightTextColor + ) + ) + ) + ), + environment: {}, + containerSize: availableSize + ) + let activeValueLabelFrame = CGRect(origin: CGPoint(x: containerFrame.width - 12.0 - activeValueSize.width, y: floorToScreenPixels((lineHeight - activeValueSize.height) / 2.0)), size: activeValueSize) + if let view = self.activeValueLabel.view { + if view.superview == nil { + self.container.addSubview(view) + + if component.invertProgress { + self.container.bringSubviewToFront(self.activeContainer) + } + } + view.frame = activeValueLabelFrame + } let activeTitleSize = self.activeTitleLabel.update( transition: .immediate, @@ -470,33 +498,12 @@ public class PremiumLimitDisplayComponent: Component { self.container.addSubview(view) } view.frame = CGRect(origin: CGPoint(x: activityPosition + 12.0, y: floorToScreenPixels((lineHeight - activeTitleSize.height) / 2.0)), size: activeTitleSize) - } - - let activeValueSize = self.activeValueLabel.update( - transition: .immediate, - component: AnyComponent( - MultilineTextComponent( - text: .plain( - NSAttributedString( - string: component.activeValue, - font: Font.semibold(15.0), - textColor: rightTextColor - ) - ) - ) - ), - environment: {}, - containerSize: availableSize - ) - if let view = self.activeValueLabel.view { - if view.superview == nil { - self.container.addSubview(view) - - if component.invertProgress { - self.container.bringSubviewToFront(self.activeContainer) - } + + if view.frame.maxX > activeValueLabelFrame.minX - 8.0 { + view.alpha = 0.0 + } else { + view.alpha = 1.0 } - view.frame = CGRect(origin: CGPoint(x: containerFrame.width - 12.0 - activeValueSize.width, y: floorToScreenPixels((lineHeight - activeValueSize.height) / 2.0)), size: activeValueSize) } } diff --git a/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift b/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift index 553b840e4f..c6e524f8b8 100644 --- a/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift +++ b/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift @@ -1013,6 +1013,11 @@ func revalidateMediaResourceReference(accountPeerId: PeerId, postbox: Postbox, n return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: nil)) } } + if let music = item.music { + if let updatedResource = findUpdatedMediaResource(media: music, previousMedia: nil, resource: resource) { + return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: nil)) + } + } return .fail(.generic) } } diff --git a/submodules/TelegramCore/Sources/SyncCore/ParticipantRankMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/ParticipantRankMessageAttribute.swift index e90d25c956..b4057267bb 100644 --- a/submodules/TelegramCore/Sources/SyncCore/ParticipantRankMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/ParticipantRankMessageAttribute.swift @@ -4,8 +4,6 @@ import Postbox public class ParticipantRankMessageAttribute: MessageAttribute { public let rank: String - public var associatedMessageIds: [MessageId] = [] - public init(rank: String) { self.rank = rank } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index 3bb0f88571..1c9c9a99da 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -1286,7 +1286,7 @@ public struct PresentationResourcesChat { UIGraphicsPopContext() } - }) + })?.withRenderingMode(.alwaysTemplate) }) } @@ -1312,7 +1312,7 @@ public struct PresentationResourcesChat { UIGraphicsPopContext() } - }) + })?.withRenderingMode(.alwaysTemplate) }) } @@ -1338,7 +1338,7 @@ public struct PresentationResourcesChat { UIGraphicsPopContext() } - }) + })?.withRenderingMode(.alwaysTemplate) }) } diff --git a/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift b/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift index 8a14bf06a1..542ac3ba4c 100644 --- a/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift +++ b/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift @@ -282,7 +282,7 @@ public final class ButtonTextContentComponent: Component { } if let badgeSize, let badge = self.badge { - let badgeFrame = CGRect(origin: CGPoint(x: contentFrame.minX + contentSize.width + badgeSpacing, y: floorToScreenPixels((size.height - badgeSize.height) * 0.5) + 1.0), size: badgeSize) + let badgeFrame = CGRect(origin: CGPoint(x: contentFrame.minX + contentSize.width + badgeSpacing, y: floorToScreenPixels((size.height - badgeSize.height) * 0.5) + UIScreenPixel), size: badgeSize) if let badgeView = badge.view { var animateIn = false @@ -461,7 +461,11 @@ public final class ButtonComponent: Component { private var containerView: UIView private var glassContainerView: GlassBackgroundView? + private var glassShadowView: UIImageView? + private var glassShadowCornerRadius: CGFloat? + private var glassHighlightContainerView: UIView? private let button: HighlightTrackingButton + private let legacyGlassHighlightRecognizer: GlassHighlightGestureRecognizer private var shimmeringView: ButtonShimmeringView? private var chromeView: UIImageView? @@ -476,6 +480,7 @@ public final class ButtonComponent: Component { self.containerView.isUserInteractionEnabled = false self.button = HighlightTrackingButton() + self.legacyGlassHighlightRecognizer = GlassHighlightGestureRecognizer(target: nil, action: nil) super.init(frame: frame) @@ -484,6 +489,8 @@ public final class ButtonComponent: Component { self.addSubview(self.containerView) self.addSubview(self.button) + self.addGestureRecognizer(self.legacyGlassHighlightRecognizer) + self.legacyGlassHighlightRecognizer.isEnabled = false self.button.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) @@ -495,28 +502,6 @@ public final class ButtonComponent: Component { self.button.highligthedChanged = { [weak self] highlighted in if let self, let component = self.component, component.isEnabled { switch component.background.style { - case .glass: - let transition = ComponentTransition(animation: .curve(duration: highlighted ? 0.25 : 0.35, curve: .spring)) - if highlighted { - self.layer.shouldRasterize = true - - let highlightedColor = component.background.color.withMultiplied(hue: 1.0, saturation: 0.77, brightness: 1.01) - transition.setBackgroundColor(view: self.containerView, color: highlightedColor) - transition.setScale(view: self.containerView, scale: 1.05, completion: { finished in - if finished { - self.layer.shouldRasterize = false - } - }) - } else { - self.layer.shouldRasterize = true - - transition.setBackgroundColor(view: self.containerView, color: component.background.color) - transition.setScale(view: self.containerView, scale: 1.0, completion: { finished in - if finished { - self.layer.shouldRasterize = false - } - }) - } case .legacy: if highlighted { self.containerView.layer.removeAnimation(forKey: "opacity") @@ -536,6 +521,74 @@ public final class ButtonComponent: Component { preconditionFailure() } + private func ensureGlassShadowView() -> UIImageView { + if let glassShadowView = self.glassShadowView { + return glassShadowView + } + let glassShadowView = UIImageView() + glassShadowView.isUserInteractionEnabled = false + self.glassShadowView = glassShadowView + return glassShadowView + } + + private func ensureGlassHighlightContainerView() -> UIView { + if let glassHighlightContainerView = self.glassHighlightContainerView { + return glassHighlightContainerView + } + let glassHighlightContainerView = UIView() + glassHighlightContainerView.isUserInteractionEnabled = false + glassHighlightContainerView.clipsToBounds = true + self.glassHighlightContainerView = glassHighlightContainerView + return glassHighlightContainerView + } + + private func removeLegacyGlassEffectViews() { + self.legacyGlassHighlightRecognizer.isEnabled = false + self.legacyGlassHighlightRecognizer.highlightContainerView = nil + + if let glassShadowView = self.glassShadowView, glassShadowView.superview != nil { + glassShadowView.removeFromSuperview() + } + if let glassHighlightContainerView = self.glassHighlightContainerView, glassHighlightContainerView.superview != nil { + glassHighlightContainerView.removeFromSuperview() + } + self.glassShadowCornerRadius = nil + + self.layer.removeAnimation(forKey: "sublayerTransform") + self.layer.sublayerTransform = CATransform3DIdentity + } + + private func updateLegacyGlassEffectViews(component: ButtonComponent, size: CGSize, cornerRadius: CGFloat, transition: ComponentTransition) { + let shadowInset: CGFloat = 48.0 + + let glassShadowView = self.ensureGlassShadowView() + if glassShadowView.superview == nil { + self.insertSubview(glassShadowView, at: 0) + } else { + self.sendSubviewToBack(glassShadowView) + } + if self.glassShadowCornerRadius != cornerRadius || glassShadowView.image == nil { + glassShadowView.image = GlassBackgroundView.generateLegacyShadowImage(cornerRadius: cornerRadius, shadowInset: shadowInset, shadowIntensity: 0.18, shadowBlur: 64.0) + self.glassShadowCornerRadius = cornerRadius + } + transition.setFrame(view: glassShadowView, frame: CGRect(origin: .zero, size: size).insetBy(dx: -shadowInset, dy: -shadowInset)) + transition.setAlpha(view: glassShadowView, alpha: 1.0) + + let glassHighlightContainerView = self.ensureGlassHighlightContainerView() + if glassHighlightContainerView.superview == nil { + self.insertSubview(glassHighlightContainerView, aboveSubview: self.containerView) + } else if self.button.superview === self { + self.insertSubview(glassHighlightContainerView, belowSubview: self.button) + } else { + self.bringSubviewToFront(glassHighlightContainerView) + } + transition.setFrame(view: glassHighlightContainerView, frame: CGRect(origin: .zero, size: size)) + transition.setCornerRadius(layer: glassHighlightContainerView.layer, cornerRadius: cornerRadius) + + self.legacyGlassHighlightRecognizer.highlightContainerView = glassHighlightContainerView + self.legacyGlassHighlightRecognizer.isEnabled = component.isEnabled && !component.displaysProgress + } + @objc private func pressed() { guard let component = self.component else { return @@ -641,6 +694,12 @@ public final class ButtonComponent: Component { transition.setCornerRadius(layer: self.containerView.layer, cornerRadius: cornerRadius) } + if component.background.style == .glass { + self.updateLegacyGlassEffectViews(component: component, size: size, cornerRadius: cornerRadius, transition: transition) + } else { + self.removeLegacyGlassEffectViews() + } + if let contentView = contentItem.view.view { var animateIn = false var contentTransition = transition diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift index 456d01e1fc..737457540a 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift @@ -1957,7 +1957,7 @@ private final class CameraScreenComponent: CombinedComponent { availableSize: CGSize(width: 40.0, height: 40.0), transition: .immediate ) - if component.cameraState.isCollageEnabled { + if state.displayingCollageSelection { nextButtonX += 48.0 } var collageButtonX = nextButtonX diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index c36c5ab7d9..719b98bef7 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -5963,44 +5963,64 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI disableDefaultPressAnimation = true } case let .phone(number): - return .action(InternalBubbleTapAction.Action({ [weak self] in - guard let self, let contentNode = self.contextContentNodeForLink(number, rects: rects) else { - return - } - item.controllerInteraction.longTap(.phone(number), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) - }, contextMenuOnLongPress: !tapAction.hasLongTapAction)) + if tapAction.hasLongTapAction { + return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in + guard let self, let contentNode = self.contextContentNodeForLink(number, rects: rects) else { + return + } + item.controllerInteraction.longTap(.phone(number), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture)) + }, contextMenuOnLongPress: false)) + } else { + disableDefaultPressAnimation = true + } case let .peerMention(peerId, mention, _): - return .action(InternalBubbleTapAction.Action { [weak self] in - guard let self, let contentNode = self.contextContentNodeForLink(mention, rects: rects) else { - return - } - item.controllerInteraction.longTap(.peerMention(peerId, mention), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) - }) + if tapAction.hasLongTapAction { + return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in + guard let self, let contentNode = self.contextContentNodeForLink(mention, rects: rects) else { + return + } + item.controllerInteraction.longTap(.peerMention(peerId, mention), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture)) + }, contextMenuOnLongPress: false)) + } else { + disableDefaultPressAnimation = true + } case let .textMention(name): - return .action(InternalBubbleTapAction.Action { [weak self] in - guard let self, let contentNode = self.contextContentNodeForLink(name, rects: rects) else { - return - } - item.controllerInteraction.longTap(.mention(name), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) - }) + if tapAction.hasLongTapAction { + return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in + guard let self, let contentNode = self.contextContentNodeForLink(name, rects: rects) else { + return + } + item.controllerInteraction.longTap(.mention(name), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture)) + }, contextMenuOnLongPress: false)) + } else { + disableDefaultPressAnimation = true + } case let .botCommand(command): - return .action(InternalBubbleTapAction.Action { [weak self] in - guard let self, let contentNode = self.contextContentNodeForLink(command, rects: rects) else { - return - } - item.controllerInteraction.longTap(.command(command), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) - }) + if tapAction.hasLongTapAction { + return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in + guard let self, let contentNode = self.contextContentNodeForLink(command, rects: rects) else { + return + } + item.controllerInteraction.longTap(.command(command), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture)) + }, contextMenuOnLongPress: false)) + } else { + disableDefaultPressAnimation = true + } case let .hashtag(peerName, hashtag): var fullHashtag = hashtag if let peerName { fullHashtag += "@\(peerName)" } - return .action(InternalBubbleTapAction.Action { [weak self] in - guard let self, let contentNode = self.contextContentNodeForLink(fullHashtag, rects: rects) else { - return - } - item.controllerInteraction.longTap(.hashtag(fullHashtag), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) - }) + if tapAction.hasLongTapAction { + return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in + guard let self, let contentNode = self.contextContentNodeForLink(fullHashtag, rects: rects) else { + return + } + item.controllerInteraction.longTap(.hashtag(fullHashtag), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture)) + }, contextMenuOnLongPress: false)) + } else { + disableDefaultPressAnimation = true + } case .instantPage: break case .wallpaper: @@ -6014,21 +6034,29 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI case .openMessage: break case let .timecode(timecode, text): - if let mediaMessage = mediaMessage { - return .action(InternalBubbleTapAction.Action { [weak self] in - guard let self, let contentNode = self.contextContentNodeForLink(text, rects: rects) else { - return - } - item.controllerInteraction.longTap(.timecode(timecode, text), ChatControllerInteraction.LongTapParams(message: mediaMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) - }) + if let mediaMessage { + if tapAction.hasLongTapAction { + return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in + guard let self, let contentNode = self.contextContentNodeForLink(text, rects: rects) else { + return + } + item.controllerInteraction.longTap(.timecode(timecode, text), ChatControllerInteraction.LongTapParams(message: mediaMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture)) + }, contextMenuOnLongPress: false)) + } else { + disableDefaultPressAnimation = true + } } case let .bankCard(number): - return .action(InternalBubbleTapAction.Action { [weak self] in - guard let self, let contentNode = self.contextContentNodeForLink(number, rects: rects) else { - return - } - item.controllerInteraction.longTap(.bankCard(number), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) - }) + if tapAction.hasLongTapAction { + return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in + guard let self, let contentNode = self.contextContentNodeForLink(number, rects: rects) else { + return + } + item.controllerInteraction.longTap(.bankCard(number), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture)) + }, contextMenuOnLongPress: false)) + } else { + disableDefaultPressAnimation = true + } case .tooltip: break case .openPollResults: @@ -6040,13 +6068,17 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI case .customEmoji: break case let .date(date, _): - return .action(InternalBubbleTapAction.Action { [weak self] in - let fullDate = stringForEntityFormattedDate(timestamp: date, format: .full(timeFormat: .short, dateFormat: .long, dayOfWeek: false), strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat) - guard let self, let contentNode = self.contextContentNodeForLink(fullDate, rects: rects) else { - return - } - item.controllerInteraction.longTap(.date(date), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) - }) + if tapAction.hasLongTapAction { + return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in + let fullDate = stringForEntityFormattedDate(timestamp: date, format: .full(timeFormat: .short, dateFormat: .long, dayOfWeek: false), strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat) + guard let self, let contentNode = self.contextContentNodeForLink(fullDate, rects: rects) else { + return + } + item.controllerInteraction.longTap(.date(date), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture)) + }, contextMenuOnLongPress: false)) + } else { + disableDefaultPressAnimation = true + } case .custom: break } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift index 8cf534d057..29a4cda102 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift @@ -430,8 +430,6 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { } let textColor: UIColor - let iconColor: UIColor - switch arguments.type { case let .bubble(incoming): if isExpiredStory || isStory { @@ -441,10 +439,8 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { } else { textColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.primaryTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.primaryTextColor } - iconColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor case .standalone: textColor = titleColor - iconColor = titleColor } var textLeftInset: CGFloat = 0.0 @@ -872,6 +868,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { expiredStoryIconView.frame = CGRect(origin: CGPoint(x: textFrame.minX - 1.0, y: textFrame.minY + 3.0 + UIScreenPixel), size: imageSize) } } + expiredStoryIconView.tintColor = titleColor } else if let expiredStoryIconView = node.expiredStoryIconView { expiredStoryIconView.removeFromSuperview() } @@ -951,7 +948,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { if let todoItemCompleted { let checkLayerFrame = CGRect(origin: CGPoint(x: textFrame.minX - 16.0, y: textFrame.minY + 5.0), size: CGSize(width: 12.0, height: 12.0)) - let checkTheme = CheckNodeTheme(backgroundColor: iconColor, strokeColor: .clear, borderColor: iconColor, overlayBorder: false, hasInset: true, hasShadow: false, borderWidth: 1.0) + let checkTheme = CheckNodeTheme(backgroundColor: titleColor, strokeColor: .clear, borderColor: titleColor, overlayBorder: false, hasInset: true, hasShadow: false, borderWidth: 1.0) let checkLayer: CheckLayer if let current = node.checkLayer { diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift index 36637edf47..0cb8fe6662 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift @@ -104,6 +104,7 @@ private final class GiftSetupScreenComponent: Component { private let backgroundHandleView: UIImageView private let closeButton = ComponentView() + private var doneButton: ComponentView? private let remainingCount = ComponentView() private let auctionFooter = ComponentView() @@ -1150,6 +1151,65 @@ private final class GiftSetupScreenComponent: Component { transition.setFrame(view: closeButtonView, frame: closeButtonFrame) } + if environment.inputHeight > 0.0 { + var doneButtonTransition = transition + let doneButton: ComponentView + if let current = self.doneButton { + doneButton = current + } else { + doneButton = ComponentView() + self.doneButton = doneButton + doneButtonTransition = .immediate + } + + let doneButtonSize = doneButton.update( + transition: .immediate, + component: AnyComponent(GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: environment.theme.list.itemCheckColors.fillColor, + isDark: environment.theme.overallDarkAppearance, + state: .tintedGlass, + component: AnyComponentWithIdentity(id: "done", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Done", + tintColor: environment.theme.list.itemCheckColors.foregroundColor + ) + )), + action: { [weak self] _ in + self?.proceed() + } + )), + environment: {}, + containerSize: CGSize(width: 44.0, height: 44.0) + ) + let doneButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - rawSideInset - 16.0 - doneButtonSize.width, y: 16.0), size: doneButtonSize) + if let doneButtonView = doneButton.view { + if doneButtonView.superview == nil { + doneButtonView.layer.rasterizationScale = UIScreenScale + self.navigationBarContainer.addSubview(doneButtonView) + + if !transition.animation.isImmediate { + doneButtonView.layer.shouldRasterize = true + transition.animateScale(view: doneButtonView, from: 0.01, to: 1.0, completion: { _ in + doneButtonView.layer.shouldRasterize = false + }) + transition.animateAlpha(view: doneButtonView, from: 0.0, to: 1.0) + } + } + doneButtonTransition.setPosition(view: doneButtonView, position: doneButtonFrame.center) + doneButtonTransition.setBounds(view: doneButtonView, bounds: CGRect(origin: .zero, size: doneButtonFrame.size)) + } + } else if let doneButton = self.doneButton { + self.doneButton = nil + if let doneButtonView = doneButton.view { + doneButtonView.layer.shouldRasterize = true + doneButtonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false) + doneButtonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in + doneButtonView.removeFromSuperview() + }) + } + } + let containerInset: CGFloat = environment.statusBarHeight + 10.0 var initialContentHeight = contentHeight @@ -1409,14 +1469,15 @@ private final class GiftSetupScreenComponent: Component { ) )), ], alignment: .left, spacing: 2.0)), - accessory: .custom(ListActionItemComponent.CustomAccessory(component: AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( + icon: ListActionItemComponent.Icon(component: AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: presentationStringsFormattedNumber(Int32(availability.resale), environment.dateTimeFormat.groupingSeparator), font: Font.regular(presentationData.listsFontSize.baseDisplaySize), textColor: theme.list.itemSecondaryTextColor )), - maximumNumberOfLines: 0 - ))), insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 16.0))), + maximumNumberOfLines: 1 + )))), + accessory: .arrow, action: { [weak self] _ in guard let self, let component = self.component, let controller = environment.controller() else { return diff --git a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift index 68bfbc9b92..c87d7d3985 100644 --- a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift +++ b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift @@ -541,18 +541,7 @@ public class GlassBackgroundView: UIView { } if let shadowView = self.shadowView { - let shadowInnerInset: CGFloat = 0.5 - shadowView.image = generateImage(CGSize(width: shadowInset * 2.0 + outerCornerRadius * 2.0, height: shadowInset * 2.0 + outerCornerRadius * 2.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - context.setFillColor(UIColor.black.cgColor) - context.setShadow(offset: CGSize(width: 0.0, height: 1.0), blur: 40.0, color: UIColor(white: 0.0, alpha: 0.04).cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowInset + shadowInnerInset, y: shadowInset + shadowInnerInset), size: CGSize(width: size.width - shadowInset * 2.0 - shadowInnerInset * 2.0, height: size.height - shadowInset * 2.0 - shadowInnerInset * 2.0))) - - context.setFillColor(UIColor.clear.cgColor) - context.setBlendMode(.copy) - context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowInset + shadowInnerInset, y: shadowInset + shadowInnerInset), size: CGSize(width: size.width - shadowInset * 2.0 - shadowInnerInset * 2.0, height: size.height - shadowInset * 2.0 - shadowInnerInset * 2.0))) - })?.stretchableImage(withLeftCapWidth: Int(shadowInset + outerCornerRadius), topCapHeight: Int(shadowInset + outerCornerRadius)) + shadowView.image = Self.generateLegacyShadowImage(cornerRadius: outerCornerRadius, shadowInset: shadowInset) transition.setAlpha(view: shadowView, alpha: isVisible ? 1.0 : 0.0) } @@ -867,6 +856,35 @@ private extension CGContext { } public extension GlassBackgroundView { + static func generateLegacyShadowImage(cornerRadius: CGFloat, shadowInset: CGFloat = 32.0, shadowIntensity: CGFloat = 0.04, shadowBlur: CGFloat = 40.0) -> UIImage? { + let shadowInnerInset: CGFloat = 0.5 + let diameter = max(1.0, cornerRadius * 2.0) + let size = CGSize(width: shadowInset * 2.0 + diameter, height: shadowInset * 2.0 + diameter) + + return generateImage(size, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + let shadowRect = CGRect( + origin: CGPoint(x: shadowInset + shadowInnerInset, y: shadowInset + shadowInnerInset), + size: CGSize( + width: size.width - shadowInset * 2.0 - shadowInnerInset * 2.0, + height: size.height - shadowInset * 2.0 - shadowInnerInset * 2.0 + ) + ) + + context.setFillColor(UIColor.black.cgColor) + context.setShadow(offset: CGSize(width: 0.0, height: 1.0), blur: shadowBlur, color: UIColor(white: 0.0, alpha: shadowIntensity).cgColor) + context.fillEllipse(in: shadowRect) + + context.setFillColor(UIColor.clear.cgColor) + context.setBlendMode(.copy) + context.fillEllipse(in: shadowRect) + })?.stretchableImage( + withLeftCapWidth: Int(shadowInset + cornerRadius), + topCapHeight: Int(shadowInset + cornerRadius) + ) + } + static func generateLegacyGlassImage(size: CGSize, inset: CGFloat, borderWidthFactor: CGFloat = 1.0, isDark: Bool, fillColor: UIColor) -> UIImage { var size = size if size == .zero { diff --git a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/TouchEffect.swift b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/TouchEffect.swift index 643b4086aa..f8bd84416b 100644 --- a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/TouchEffect.swift +++ b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/TouchEffect.swift @@ -2,8 +2,8 @@ import Foundation import UIKit import Display -final class GlassHighlightGestureRecognizer: UIGestureRecognizer, UIGestureRecognizerDelegate { - var highlightContainerView: UIView? +public final class GlassHighlightGestureRecognizer: UIGestureRecognizer, UIGestureRecognizerDelegate { + public weak var highlightContainerView: UIView? private var touchEffect: TouchEffect? private var initialTouchLocation: CGPoint? @@ -14,7 +14,7 @@ final class GlassHighlightGestureRecognizer: UIGestureRecognizer, UIGestureRecog } } - override init(target: Any?, action: Selector?) { + public override init(target: Any?, action: Selector?) { super.init(target: target, action: action) self.delegate = self @@ -24,19 +24,19 @@ final class GlassHighlightGestureRecognizer: UIGestureRecognizer, UIGestureRecog self.requiresExclusiveTouchType = false } - override func canPrevent(_ preventedGestureRecognizer: UIGestureRecognizer) -> Bool { + override public func canPrevent(_ preventedGestureRecognizer: UIGestureRecognizer) -> Bool { return false } - override func canBePrevented(by preventingGestureRecognizer: UIGestureRecognizer) -> Bool { + override public func canBePrevented(by preventingGestureRecognizer: UIGestureRecognizer) -> Bool { return false } - func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } - override func reset() { + override public func reset() { if let touchEffect = self.touchEffect { touchEffect.setIsTracking(false) } @@ -45,7 +45,7 @@ final class GlassHighlightGestureRecognizer: UIGestureRecognizer, UIGestureRecog self.initialTouchLocation = nil } - override func touchesBegan(_ touches: Set, with event: UIEvent) { + override public func touchesBegan(_ touches: Set, with event: UIEvent) { if let view = self.touchEffectView ?? self.view, let touch = touches.first { let touchLocation = touch.location(in: view) let touchEffect = TouchEffect(view: view, highlightContainerView: self.highlightContainerView) @@ -60,21 +60,21 @@ final class GlassHighlightGestureRecognizer: UIGestureRecognizer, UIGestureRecog } } - override func touchesEnded(_ touches: Set, with event: UIEvent) { + override public func touchesEnded(_ touches: Set, with event: UIEvent) { if let touchEffect = self.touchEffect { touchEffect.setIsTracking(false) } self.touchEffect = nil } - override func touchesCancelled(_ touches: Set, with event: UIEvent) { + override public func touchesCancelled(_ touches: Set, with event: UIEvent) { if let touchEffect = self.touchEffect { touchEffect.setIsTracking(false) } self.touchEffect = nil } - override func touchesMoved(_ touches: Set, with event: UIEvent) { + override public func touchesMoved(_ touches: Set, with event: UIEvent) { guard let touchEffect = self.touchEffect, let view = self.touchEffectView ?? self.view, let touch = touches.first, diff --git a/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift b/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift index d4ca89a2b5..e048c1e86b 100644 --- a/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift +++ b/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift @@ -716,7 +716,8 @@ public final class ListActionItemComponent: Component { } } - if case .arrow = component.accessory { + switch component.accessory { + case .arrow: let arrowView: UIImageView var arrowTransition = transition if let current = self.arrowView { @@ -735,14 +736,7 @@ public final class ListActionItemComponent: Component { let arrowFrame = CGRect(origin: CGPoint(x: availableSize.width - 7.0 - image.size.width, y: floor((contentHeight - image.size.height) * 0.5)), size: image.size) arrowTransition.setFrame(view: arrowView, frame: arrowFrame) } - } else { - if let arrowView = self.arrowView { - self.arrowView = nil - arrowView.removeFromSuperview() - } - } - - if case .expandArrows = component.accessory { + case .expandArrows: let arrowView: UIImageView var arrowTransition = transition if let current = self.arrowView { @@ -761,7 +755,7 @@ public final class ListActionItemComponent: Component { let arrowFrame = CGRect(origin: CGPoint(x: availableSize.width - 16.0 - image.size.width, y: floor((contentHeight - image.size.height) * 0.5)), size: image.size) arrowTransition.setFrame(view: arrowView, frame: arrowFrame) } - } else { + default: if let arrowView = self.arrowView { self.arrowView = nil arrowView.removeFromSuperview() diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenBankCardContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenBankCardContextMenu.swift index 0b4e34a494..d28159f825 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenBankCardContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenBankCardContextMenu.swift @@ -31,15 +31,10 @@ extension ChatControllerImpl { } } - let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer - let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture - - let source: ContextContentSource -// if let location = location { -// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y))) -// } else { - source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) -// } + let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = params.gesture + let gesture: ContextGesture? = nil + + let source: ContextContentSource = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) params.progress?.set(.single(true)) diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenCommandContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenCommandContextMenu.swift index bddb29baca..d27e6ea8e1 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenCommandContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenCommandContextMenu.swift @@ -31,15 +31,10 @@ extension ChatControllerImpl { } } - let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer - let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture + let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = params.gesture + let gesture: ContextGesture? = nil - let source: ContextContentSource -// if let location = location { -// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y))) -// } else { - source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) -// } + let source: ContextContentSource = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) var items: [ContextMenuItem] = [] diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenDateContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenDateContextMenu.swift index 6eb88e48b4..c1aadf5f56 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenDateContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenDateContextMenu.swift @@ -35,15 +35,10 @@ extension ChatControllerImpl: EKEventEditViewDelegate { } } - let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer - let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture - - let source: ContextContentSource -// if let location = location { -// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y))) -// } else { - source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) -// } + let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = params.gesture + let gesture: ContextGesture? = nil + + let source: ContextContentSource = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) var items: [ContextMenuItem] = [] items.append( diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenHashtagContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenHashtagContextMenu.swift index 4cdca9f8bb..a7f7076dcc 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenHashtagContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenHashtagContextMenu.swift @@ -31,15 +31,10 @@ extension ChatControllerImpl { } } - let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer - let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture + let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = params.gesture + let gesture: ContextGesture? = nil - let source: ContextContentSource -// if let location = location { -// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y))) -// } else { - source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) -// } + let source: ContextContentSource = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) var items: [ContextMenuItem] = [] diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenLinkContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenLinkContextMenu.swift index cc9acdf90f..ef935a4587 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenLinkContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenLinkContextMenu.swift @@ -142,14 +142,9 @@ extension ChatControllerImpl { } let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = params.gesture - let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture + let gesture: ContextGesture? = nil - let source: ContextContentSource -// if let location = location { -// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y))) -// } else { - source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) -// } + let source: ContextContentSource = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) var items: [ContextMenuItem] = [] diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPhoneContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPhoneContextMenu.swift index 4441bc44f7..abe4b813a7 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPhoneContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPhoneContextMenu.swift @@ -35,15 +35,10 @@ extension ChatControllerImpl: MFMessageComposeViewControllerDelegate { } } - let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer - let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture + let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = params.gesture + let gesture: ContextGesture? = nil - let source: ContextContentSource -// if let location = location { -// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y))) -// } else { - source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) -// } + let source: ContextContentSource = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) params.progress?.set(.single(true)) diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenTimecodeContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenTimecodeContextMenu.swift index 086d87ae1a..6d1870731e 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenTimecodeContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenTimecodeContextMenu.swift @@ -31,15 +31,10 @@ extension ChatControllerImpl { } } - let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer - let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture + let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = params.gesture + let gesture: ContextGesture? = nil - let source: ContextContentSource -// if let location = location { -// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y))) -// } else { - source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) -// } + let source: ContextContentSource = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) var items: [ContextMenuItem] = [] diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenUsernameContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenUsernameContextMenu.swift index 61b871b9bd..60c97a35bc 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenUsernameContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenUsernameContextMenu.swift @@ -19,15 +19,10 @@ extension ChatControllerImpl { return } - let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer - let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture + let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = params.gesture + let gesture: ContextGesture? = nil - let source: ContextContentSource -// if let location = location { -// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y))) -// } else { - source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) -// } + let source: ContextContentSource = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) params.progress?.set(.single(true)) diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index 47f9cd7ab1..8c7432495d 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -361,7 +361,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.avatarNode = nil self.iconNode = nil self.iconCheckNode = nil - self.animationNode = AnimationNode(animation: "anim_banned", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0) + self.animationNode = AnimationNode(animation: "anim_banned", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 0.9) self.animatedStickerNode = nil let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white) diff --git a/submodules/WebUI/Sources/WebAppWebView.swift b/submodules/WebUI/Sources/WebAppWebView.swift index 436d8c36db..a7fb70770c 100644 --- a/submodules/WebUI/Sources/WebAppWebView.swift +++ b/submodules/WebUI/Sources/WebAppWebView.swift @@ -277,4 +277,31 @@ final class WebAppWebView: WKWebView { override var inputAccessoryView: UIView? { return nil } + + var origin: String? { + guard let url = self.url, let scheme = url.scheme, let host = url.host else { + return nil + } + let port = url.port + var origin = "\(scheme)://\(host)" + if let port { + origin += ":\(port)" + } + return origin + } +} + +extension WKFrameInfo { + var securityOriginString: String { + let securityOrigin = self.securityOrigin + var origin = "" + origin.append(securityOrigin.protocol) + origin.append("://") + origin.append(securityOrigin.host) + if securityOrigin.port != 0 { + origin.append(":") + origin.append("\(securityOrigin.port)") + } + return origin + } } From 314f02d36da460c0de5f55fe06582084944157f2 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 14 Apr 2026 04:53:01 +0200 Subject: [PATCH 2/2] Various fixes --- .../Sources/Receipt.swift | 249 +++++++++++++----- .../Sources/ShareWithPeersScreen.swift | 2 +- .../Sources/DataUsageScreen.swift | 2 +- .../Sources/StoryPeerListComponent.swift | 1 + .../Sources/StoryPeerListItemComponent.swift | 2 +- .../Sources/VideoMessageCameraScreen.swift | 2 +- .../Chat/ChatControllerMediaRecording.swift | 6 +- 7 files changed, 190 insertions(+), 74 deletions(-) diff --git a/submodules/InAppPurchaseManager/Sources/Receipt.swift b/submodules/InAppPurchaseManager/Sources/Receipt.swift index fbb5d1b9d4..f16667bcbd 100644 --- a/submodules/InAppPurchaseManager/Sources/Receipt.swift +++ b/submodules/InAppPurchaseManager/Sources/Receipt.swift @@ -16,97 +16,157 @@ private struct Asn1Entry { let length: Int } -private func parse(_ data: Data, startIndex: Int = 0) -> Asn1Entry { +private func dataByte(_ data: Data, at index: Int) -> UInt8? { + guard index >= 0 && index < data.count else { + return nil + } + return data[index] +} + +private func parse(_ data: Data, startIndex: Int = 0) -> Asn1Entry? { + guard startIndex >= 0 && startIndex < data.count else { + return nil + } + var index = startIndex - var value = data[index] + guard var value = dataByte(data, at: index) else { + return nil + } index += 1 var tagValue = Int32(value & 0x1f) if tagValue == 31 { - value = data[index] - index += 1 - while (value & 0x80) != 0 { + repeat { + guard let nextValue = dataByte(data, at: index) else { + return nil + } + value = nextValue + index += 1 + guard tagValue <= Int32.max >> 8 else { + return nil + } tagValue <<= 8 tagValue |= Int32(value & 0x7f) - value = data[index] - index += 1 - } - tagValue <<= 8 - tagValue |= Int32(value & 0x7f) + } while (value & 0x80) != 0 } var length = 0 - var nextTag = 0 - value = data[index] + guard let lengthValue = dataByte(data, at: index) else { + return nil + } + value = lengthValue + let isIndefiniteLength = value == 0x80 index += 1 - if value & 0x80 == 0 { + if !isIndefiniteLength && value & 0x80 == 0 { length = Int(value) - nextTag = index + length - } else if value != 0x80 { + } else if !isIndefiniteLength { let octetsCount = Int(value & 0x7f) + guard octetsCount > 0 else { + return nil + } for _ in 0 ..< octetsCount { + guard length <= Int.max >> 8 else { + return nil + } length <<= 8 - value = data[index] + guard let nextValue = dataByte(data, at: index) else { + return nil + } + value = nextValue index += 1 length |= Int(value) & 0xff } - nextTag = index + length } else { var scanIndex = index - while data[scanIndex] != 0 && data[scanIndex + 1] != 0 { + while true { + guard scanIndex + 1 < data.count else { + return nil + } + if data[scanIndex] == 0 && data[scanIndex + 1] == 0 { + break + } scanIndex += 1 } length = scanIndex - index - nextTag = scanIndex + 2 } - return Asn1Entry(tag: tagValue, data: data.subdata(in: index ..< (index + length)), length: nextTag - startIndex) + + guard length >= 0, length <= data.count - index else { + return nil + } + + let payloadEndIndex = index + length + let entryEndIndex: Int + if isIndefiniteLength { + entryEndIndex = payloadEndIndex + 2 + guard entryEndIndex <= data.count else { + return nil + } + } else { + entryEndIndex = payloadEndIndex + } + + return Asn1Entry(tag: tagValue, data: data.subdata(in: index ..< payloadEndIndex), length: entryEndIndex - startIndex) } -private func parseSequence(_ data: Data) -> [Asn1Entry] { +private func parseSequence(_ data: Data) -> [Asn1Entry]? { var result : [Asn1Entry] = [] var index = 0 while index < data.count { - let entry = parse(data, startIndex: index) + guard let entry = parse(data, startIndex: index), entry.length > 0 else { + return nil + } result.append(entry) index += entry.length } return result } -private func parseInteger(_ data: Data) -> Int32 { - let length = data.count - var value: Int32 = 0 - for i in 0 ..< length { - if i == 0 { - value = Int32(data[i] & 0x7f) - } else { - value <<= 8 - value |= Int32(data[i]) - } +private func parseInteger(_ data: Data) -> Int32? { + guard !data.isEmpty, data.count <= MemoryLayout.size else { + return nil } - if length > 0 && data[0] & 0x80 != 0 { - let complement: Int32 = 1 << (length * 8) - value -= complement + + var value: UInt32 = 0 + for byte in data { + value = (value << 8) | UInt32(byte) } - return value + + if let firstByte = data.first, firstByte & 0x80 != 0 && data.count < MemoryLayout.size { + let shift = UInt32(data.count * 8) + value |= ~UInt32(0) << shift + } + + return Int32(bitPattern: value) } -private func parseObjectIdentifier(_ data: Data, startIndex: Int = 0, length: Int? = nil) -> [Int32] { +private func parseObjectIdentifier(_ data: Data, startIndex: Int = 0, length: Int? = nil) -> [Int32]? { let dataLen = length ?? data.count + guard startIndex >= 0, dataLen > 0, startIndex <= data.count, dataLen <= data.count - startIndex else { + return nil + } + + let endIndex = startIndex + dataLen var index = startIndex var identifier: [Int32] = [] - while index < startIndex + dataLen { - var subidentifier: Int32 = 0 - var value = data[index] - index += 1 - while (value & 0x80) != 0 { - subidentifier <<= 7 - subidentifier |= Int32(value & 0x7f) - value = data[index] + while index < endIndex { + var subidentifier: Int64 = 0 + while true { + guard let value = dataByte(data, at: index) else { + return nil + } index += 1 + guard subidentifier <= Int64(Int32.max >> 7) else { + return nil + } + subidentifier <<= 7 + subidentifier |= Int64(value & 0x7f) + if (value & 0x80) == 0 { + break + } + guard index < endIndex else { + return nil + } } - subidentifier <<= 7 - subidentifier |= Int32(value & 0x7f) - identifier.append(subidentifier) + identifier.append(Int32(subidentifier)) } return identifier } @@ -137,51 +197,71 @@ struct Receipt { } func parseReceipt(_ data: Data) -> Receipt? { - let root = parseSequence(data) + guard let root = parseSequence(data) else { + return nil + } guard root.count == 1 && root[0].tag == Asn1Tag.sequence else { return nil } - let rootSeq = parseSequence(root[0].data) + guard let rootSeq = parseSequence(root[0].data) else { + return nil + } guard rootSeq.count == 2 && rootSeq[0].tag == Asn1Tag.objectIdentifier && parseObjectIdentifier(rootSeq[0].data) == ObjectIdentifier.pkcs7SignedData else { return nil } - let signedData = parseSequence(rootSeq[1].data) + guard let signedData = parseSequence(rootSeq[1].data) else { + return nil + } guard signedData.count == 1 && signedData[0].tag == Asn1Tag.sequence else { return nil } - let signedDataSeq = parseSequence(signedData[0].data) + guard let signedDataSeq = parseSequence(signedData[0].data) else { + return nil + } guard signedDataSeq.count > 3 && signedDataSeq[2].tag == Asn1Tag.sequence else { return nil } - let contentData = parseSequence(signedDataSeq[2].data) + guard let contentData = parseSequence(signedDataSeq[2].data) else { + return nil + } guard contentData.count == 2 && contentData[0].tag == Asn1Tag.objectIdentifier && parseObjectIdentifier(contentData[0].data) == ObjectIdentifier.pkcs7Data else { return nil } - let payload = parse(contentData[1].data) + guard let payload = parse(contentData[1].data) else { + return nil + } guard payload.tag == Asn1Tag.octetString else { return nil } - let payloadRoot = parse(payload.data) + guard let payloadRoot = parse(payload.data) else { + return nil + } guard payloadRoot.tag == Asn1Tag.set else { return nil } var purchases: [Receipt.Purchase] = [] - let receiptAttributes = parseSequence(payloadRoot.data) + guard let receiptAttributes = parseSequence(payloadRoot.data) else { + return nil + } for attribute in receiptAttributes { if attribute.tag != Asn1Tag.sequence { continue } - let attributeEntries = parseSequence(attribute.data) + guard let attributeEntries = parseSequence(attribute.data) else { + return nil + } guard attributeEntries.count == 3 && attributeEntries[0].tag == Asn1Tag.integer && attributeEntries[1].tag == Asn1Tag.integer && attributeEntries[2].tag == Asn1Tag.octetString else { return nil } - let type = parseInteger(attributeEntries[0].data) + guard let type = parseInteger(attributeEntries[0].data) else { + return nil + } let value = attributeEntries[2].data switch (type) { case Receipt.Tag.purchases: @@ -202,22 +282,41 @@ private func parseRfc3339Date(_ str: String) -> Date? { formatter1.locale = posixLocale formatter1.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssX5" formatter1.timeZone = TimeZone(secondsFromGMT: 0) - - let result = formatter1.date(from: str) + + var result = formatter1.date(from: str) if result != nil { return result } - + let formatter2 = DateFormatter() formatter2.locale = posixLocale formatter2.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSSSSSX5" formatter2.timeZone = TimeZone(secondsFromGMT: 0) + + result = formatter2.date(from: str) + if result != nil { + return result + } - return formatter2.date(from: str) + let formatterWithFractionalSeconds = ISO8601DateFormatter() + formatterWithFractionalSeconds.timeZone = TimeZone(secondsFromGMT: 0) + formatterWithFractionalSeconds.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + + if let result = formatterWithFractionalSeconds.date(from: str) { + return result + } + + let formatterWithoutFractionalSeconds = ISO8601DateFormatter() + formatterWithoutFractionalSeconds.timeZone = TimeZone(secondsFromGMT: 0) + formatterWithoutFractionalSeconds.formatOptions = [.withInternetDateTime] + + return formatterWithoutFractionalSeconds.date(from: str) } private func parsePurchaseAttributes(_ data: Data) -> Receipt.Purchase? { - let root = parse(data) + guard let root = parse(data) else { + return nil + } guard root.tag == Asn1Tag.set else { return nil } @@ -226,26 +325,38 @@ private func parsePurchaseAttributes(_ data: Data) -> Receipt.Purchase? { var transactionId: String? var expirationDate: Date? - let receiptAttributes = parseSequence(root.data) + guard let receiptAttributes = parseSequence(root.data) else { + return nil + } for attribute in receiptAttributes { if attribute.tag != Asn1Tag.sequence { continue } - let attributeEntries = parseSequence(attribute.data) + guard let attributeEntries = parseSequence(attribute.data) else { + return nil + } guard attributeEntries.count == 3 && attributeEntries[0].tag == Asn1Tag.integer && attributeEntries[1].tag == Asn1Tag.integer && attributeEntries[2].tag == Asn1Tag.octetString else { return nil } - let type = parseInteger(attributeEntries[0].data) + guard let type = parseInteger(attributeEntries[0].data) else { + return nil + } let value = attributeEntries[2].data switch (type) { case Receipt.Purchase.Tag.productIdentifier: - let valEntry = parse(value) + guard let valEntry = parse(value) else { + return nil + } guard valEntry.tag == Asn1Tag.utf8String else { return nil } productId = String(bytes: valEntry.data, encoding: .utf8) case Receipt.Purchase.Tag.transactionIdentifier: - let valEntry = parse(value) + guard let valEntry = parse(value) else { + return nil + } guard valEntry.tag == Asn1Tag.utf8String else { return nil } transactionId = String(bytes: valEntry.data, encoding: .utf8) case Receipt.Purchase.Tag.expirationDate: - let valEntry = parse(value) + guard let valEntry = parse(value) else { + return nil + } guard valEntry.tag == Asn1Tag.date else { return nil } expirationDate = parseRfc3339Date(String(bytes: valEntry.data, encoding: .utf8) ?? "") default: diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift index 120b2b2f5c..7fcfdb0b49 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift @@ -2765,7 +2765,7 @@ final class ShareWithPeersScreenComponent: Component { let navigationLeftButtonSize = self.navigationLeftButton.update( transition: transition, component: AnyComponent(GlassBarButtonComponent( - size: CGSize(width: 40.0, height: 40.0), + size: CGSize(width: 44.0, height: 44.0), backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor, isDark: environment.theme.overallDarkAppearance, state: .generic, diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift index 7b5f78fb54..5598017901 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift @@ -911,7 +911,7 @@ final class DataUsageScreenComponent: Component { self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(AnimationHint(value: .modeChanged))) })), environment: {}, - containerSize: CGSize(width: availableSize.width - (environment.safeInsets.left + 16.0 + 44.0 + 10.0) * 2.0, height: 100.0) + containerSize: CGSize(width: availableSize.width - (environment.safeInsets.left + 48.0) * 2.0, height: 100.0) ) let segmentedControlFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - segmentedSize.width) * 0.5), y: contentHeight), size: segmentedSize) if let segmentedControlComponentView = self.segmentedControlView.view { diff --git a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift index 3b0074726e..1abccc6051 100644 --- a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift @@ -377,6 +377,7 @@ public final class StoryPeerListComponent: Component { self.scrollView.alwaysBounceVertical = false self.scrollView.alwaysBounceHorizontal = true self.scrollView.clipsToBounds = false + self.scrollView.scrollsToTop = false self.scrollContainerView = UIView() self.scrollContainerView.clipsToBounds = true diff --git a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift index cc012a79f3..99d913e031 100644 --- a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift @@ -965,7 +965,7 @@ public final class StoryPeerListItemComponent: Component { var titleTransition = transition if let previousAnimation = previousComponent?.ringAnimation, case .progress = previousAnimation, component.ringAnimation == nil { if let titleView = self.title.view, let snapshotView = titleView.snapshotView(afterScreenUpdates: false) { - self.button.addSubview(snapshotView) + self.titleContainer.addSubview(snapshotView) snapshotView.frame = titleView.frame snapshotView.layer.animateAlpha(from: component.expandedAlphaFraction, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak snapshotView] _ in snapshotView?.removeFromSuperview() diff --git a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift index 4cc16219bc..99a8cd5fe5 100644 --- a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift +++ b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift @@ -536,7 +536,7 @@ private final class VideoMessageCameraScreenComponent: CombinedComponent { let availableSize = context.component.containerSize var sideInset: CGFloat = 8.0 - if component.safeInsets.bottom <= 32.0 { + if component.safeInsets.bottom <= 32.0 && environment.inputHeight == 0.0 { sideInset += 18.0 } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift index 662ce26d34..56b4cc2aba 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift @@ -606,7 +606,11 @@ extension ChatControllerImpl { } } - let location = CGRect(origin: CGPoint(x: screenWidth - layout.safeInsets.right - 50.0, y: layout.size.height - insets.bottom - 128.0), size: CGSize()) + var sideOffset: CGFloat = 18.0 + if let inputHeight = layout.inputHeight, inputHeight > 0.0 { + sideOffset = 0.0 + } + let location = CGRect(origin: CGPoint(x: screenWidth - layout.safeInsets.right - 50.0 - sideOffset, y: layout.size.height - insets.bottom - 128.0), size: CGSize()) let tooltipController = TooltipScreen( account: self.context.account,