From e232a854a29359219a619857e95714f031b11610 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 2 Jun 2026 10:48:17 +0200 Subject: [PATCH 1/2] Various fixes --- .../Sources/ItemListRevealOptionsNode.swift | 214 ++++++++++-------- .../Sources/ChannelAdminController.swift | 2 +- .../WebBrowserDomainExceptionItem.swift | 43 +++- 3 files changed, 164 insertions(+), 95 deletions(-) diff --git a/submodules/ItemListUI/Sources/ItemListRevealOptionsNode.swift b/submodules/ItemListUI/Sources/ItemListRevealOptionsNode.swift index c76abf5b89..5f2d5e233b 100644 --- a/submodules/ItemListUI/Sources/ItemListRevealOptionsNode.swift +++ b/submodules/ItemListUI/Sources/ItemListRevealOptionsNode.swift @@ -76,17 +76,17 @@ public struct ItemListRevealOption: Equatable { private let titleFont = Font.regular(11.0) private let iconlessTitleFont = Font.regular(13.0) -private let optionSpacing: CGFloat = 10.0 -private let optionEdgeInset: CGFloat = 10.0 -private let optionTitleSpacing: CGFloat = 4.0 -private let optionRevealStartOverlap: CGFloat = 12.0 -private let optionRevealEndDistance: CGFloat = 10.0 -private let optionExpandedActivationWidthFactor: CGFloat = 3.0 -private let optionExpandedTransitionDistance: CGFloat = 16.0 -private let optionPillTitleHorizontalPadding: CGFloat = 6.0 -private let optionIconlessTitleAdditionalHorizontalPadding: CGFloat = 8.0 -private let optionIconAnimationResponse: CGFloat = 18.0 -private let optionIconAnimationSnapDistance: CGFloat = 0.5 +private let spacing: CGFloat = 10.0 +private let edgeInset: CGFloat = 10.0 +private let ritleSpacing: CGFloat = 4.0 +private let revealStartOverlap: CGFloat = 12.0 +private let revealEndDistance: CGFloat = 10.0 +private let expandedActivationWidthFactor: CGFloat = 3.0 +private let expandedTransitionDistance: CGFloat = 16.0 +private let iconlessTitleExpandedHorizontalPadding: CGFloat = 8.0 +private let iconlessTitleHorizontalPadding: CGFloat = 8.0 +private let iconAnimationResponse: CGFloat = 18.0 +private let iconAnimationSnapDistance: CGFloat = 0.5 private extension ItemListRevealOptionIcon { var hasVisualIcon: Bool { @@ -108,7 +108,7 @@ private struct ItemListRevealOptionLayoutMetrics { let expandedIconInset: CGFloat var contentHeight: CGFloat { - return self.shapeSize.height + optionTitleSpacing + ceil(titleFont.lineHeight) + return self.shapeSize.height + ritleSpacing + ceil(titleFont.lineHeight) } var slotShapeInset: CGFloat { @@ -118,7 +118,7 @@ private struct ItemListRevealOptionLayoutMetrics { static func metrics(for height: CGFloat, hasVisualIcons: Bool) -> ItemListRevealOptionLayoutMetrics { let regularShapeSize = CGSize(width: 50.0, height: 50.0) let compactShapeSize = CGSize(width: 60.0, height: 32.0) - let regularContentHeight = regularShapeSize.height + optionTitleSpacing + ceil(titleFont.lineHeight) + let regularContentHeight = regularShapeSize.height + ritleSpacing + ceil(titleFont.lineHeight) if height < regularContentHeight || !hasVisualIcons { return ItemListRevealOptionLayoutMetrics(shapeSize: compactShapeSize, slotWidth: 70.0, titleWidth: 70.0, iconMaxSide: 24.0, cornerRadius: 16.0, expandedIconInset: 16.0) } else { @@ -127,16 +127,16 @@ private struct ItemListRevealOptionLayoutMetrics { } func withGroupTitleWidth(_ maxTitleWidth: CGFloat) -> ItemListRevealOptionLayoutMetrics { - if maxTitleWidth <= self.shapeSize.width - optionPillTitleHorizontalPadding { + if maxTitleWidth <= self.shapeSize.width - iconlessTitleExpandedHorizontalPadding { return self } - let updatedShapeWidth = ceil(maxTitleWidth + optionPillTitleHorizontalPadding) + let updatedShapeWidth = ceil(maxTitleWidth + iconlessTitleExpandedHorizontalPadding) let slotWidthDelta = self.slotWidth - self.shapeSize.width return ItemListRevealOptionLayoutMetrics( shapeSize: CGSize(width: updatedShapeWidth, height: self.shapeSize.height), slotWidth: updatedShapeWidth + slotWidthDelta, - titleWidth: max(self.titleWidth, updatedShapeWidth - optionPillTitleHorizontalPadding), + titleWidth: max(self.titleWidth, updatedShapeWidth - iconlessTitleExpandedHorizontalPadding), iconMaxSide: self.iconMaxSide, cornerRadius: self.cornerRadius, expandedIconInset: self.expandedIconInset @@ -147,7 +147,7 @@ private struct ItemListRevealOptionLayoutMetrics { if count == 0 { return 0.0 } - return optionEdgeInset * 2.0 + self.shapeSize.width * CGFloat(count) + optionSpacing * CGFloat(count - 1) + return edgeInset * 2.0 + self.shapeSize.width * CGFloat(count) + spacing * CGFloat(count - 1) } } @@ -174,10 +174,10 @@ private final class ItemListRevealOptionNode: ASDisplayNode { private var animationNodeOffset: CGFloat = 0.0 private var animationNodeFlip = false - private var iconAnimationLink: SharedDisplayLinkDriver.Link? - private weak var manuallyAnimatedIconNode: ASDisplayNode? - private var currentIconCenter: CGPoint? - private var targetIconCenter: CGPoint? + private var contentAnimationLink: SharedDisplayLinkDriver.Link? + private weak var manuallyAnimatedContentNode: ASDisplayNode? + private var currentContentCenter: CGPoint? + private var targetContentCenter: CGPoint? private var didApplyLayout = false var isExpanded: Bool = false @@ -189,7 +189,7 @@ private final class ItemListRevealOptionNode: ASDisplayNode { var titleWidthForGroupPillSizing: CGFloat { var titleWidth = self.titleNode.updateLayout(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)).width if self.displaysTitleInsidePill { - titleWidth += optionIconlessTitleAdditionalHorizontalPadding + titleWidth += iconlessTitleHorizontalPadding } return titleWidth } @@ -264,7 +264,7 @@ private final class ItemListRevealOptionNode: ASDisplayNode { } deinit { - self.stopManualIconAnimation() + self.stopManualContentAnimation() } func setHighlighted(_ highlighted: Bool) { @@ -280,81 +280,81 @@ private final class ItemListRevealOptionNode: ASDisplayNode { func resetAnimation() { self.animationNode?.reset() - self.stopManualIconAnimation() + self.stopManualContentAnimation() } - private func currentIconPresentationCenter(iconNode: ASDisplayNode) -> CGPoint { - return iconNode.layer.presentation()?.position ?? iconNode.position + private func currentContentPresentationCenter(contentNode: ASDisplayNode) -> CGPoint { + return contentNode.layer.presentation()?.position ?? contentNode.position } - private func isManualIconAnimationAtTarget(center: CGPoint) -> Bool { - guard let targetIconCenter = self.targetIconCenter else { + private func isManualContentAnimationAtTarget(center: CGPoint) -> Bool { + guard let targetContentCenter = self.targetContentCenter else { return true } - let centerDeltaX = targetIconCenter.x - center.x - let centerDeltaY = targetIconCenter.y - center.y + let centerDeltaX = targetContentCenter.x - center.x + let centerDeltaY = targetContentCenter.y - center.y let centerDistance = sqrt(centerDeltaX * centerDeltaX + centerDeltaY * centerDeltaY) - return centerDistance <= optionIconAnimationSnapDistance + return centerDistance <= iconAnimationSnapDistance } - private func stopManualIconAnimation() { - self.iconAnimationLink?.isPaused = true - self.iconAnimationLink?.invalidate() - self.iconAnimationLink = nil - self.manuallyAnimatedIconNode = nil - self.currentIconCenter = nil - self.targetIconCenter = nil + private func stopManualContentAnimation() { + self.contentAnimationLink?.isPaused = true + self.contentAnimationLink?.invalidate() + self.contentAnimationLink = nil + self.manuallyAnimatedContentNode = nil + self.currentContentCenter = nil + self.targetContentCenter = nil } - private func updateManualIconCenter(iconNode: ASDisplayNode, targetCenter: CGPoint, forceImmediate: Bool) { - iconNode.layer.removeAnimation(forKey: "position") + private func updateManualContentCenter(contentNode: ASDisplayNode, targetCenter: CGPoint, forceImmediate: Bool) { + contentNode.layer.removeAnimation(forKey: "position") - if self.manuallyAnimatedIconNode !== iconNode || self.currentIconCenter == nil { - self.currentIconCenter = self.currentIconPresentationCenter(iconNode: iconNode) - self.manuallyAnimatedIconNode = iconNode + if self.manuallyAnimatedContentNode !== contentNode || self.currentContentCenter == nil { + self.currentContentCenter = self.currentContentPresentationCenter(contentNode: contentNode) + self.manuallyAnimatedContentNode = contentNode } - self.targetIconCenter = targetCenter + self.targetContentCenter = targetCenter if forceImmediate { - iconNode.position = targetCenter - self.stopManualIconAnimation() + contentNode.position = targetCenter + self.stopManualContentAnimation() return } - if let currentIconCenter = self.currentIconCenter, self.isManualIconAnimationAtTarget(center: currentIconCenter) { - iconNode.position = targetCenter - self.stopManualIconAnimation() + if let currentContentCenter = self.currentContentCenter, self.isManualContentAnimationAtTarget(center: currentContentCenter) { + contentNode.position = targetCenter + self.stopManualContentAnimation() return } - if self.iconAnimationLink == nil { - self.iconAnimationLink = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { [weak self] deltaTime in - self?.tickManualIconAnimation(deltaTime: deltaTime) + if self.contentAnimationLink == nil { + self.contentAnimationLink = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { [weak self] deltaTime in + self?.tickManualContentAnimation(deltaTime: deltaTime) }) - self.iconAnimationLink?.isPaused = false + self.contentAnimationLink?.isPaused = false } } - private func tickManualIconAnimation(deltaTime: CGFloat) { - guard let iconNode = self.manuallyAnimatedIconNode, let currentIconCenter = self.currentIconCenter, let targetIconCenter = self.targetIconCenter else { - self.stopManualIconAnimation() + private func tickManualContentAnimation(deltaTime: CGFloat) { + guard let contentNode = self.manuallyAnimatedContentNode, let currentContentCenter = self.currentContentCenter, let targetContentCenter = self.targetContentCenter else { + self.stopManualContentAnimation() return } let clampedDeltaTime = min(0.05, max(0.0, deltaTime)) - let progress = 1.0 - exp(-clampedDeltaTime * optionIconAnimationResponse) + let progress = 1.0 - exp(-clampedDeltaTime * iconAnimationResponse) let updatedCenter = CGPoint( - x: currentIconCenter.x + (targetIconCenter.x - currentIconCenter.x) * progress, - y: currentIconCenter.y + (targetIconCenter.y - currentIconCenter.y) * progress + x: currentContentCenter.x + (targetContentCenter.x - currentContentCenter.x) * progress, + y: currentContentCenter.y + (targetContentCenter.y - currentContentCenter.y) * progress ) - if self.isManualIconAnimationAtTarget(center: updatedCenter) { - iconNode.position = targetIconCenter - self.stopManualIconAnimation() + if self.isManualContentAnimationAtTarget(center: updatedCenter) { + contentNode.position = targetContentCenter + self.stopManualContentAnimation() } else { - self.currentIconCenter = updatedCenter - iconNode.position = updatedCenter + self.currentContentCenter = updatedCenter + contentNode.position = updatedCenter } } @@ -375,7 +375,7 @@ private final class ItemListRevealOptionNode: ASDisplayNode { if self.displaysTitleInsidePill { shapeY = floorToScreenPixels((bounds.height - pillSize.height) / 2.0) } else { - let contentHeight = pillSize.height + optionTitleSpacing + titleSize.height + let contentHeight = pillSize.height + ritleSpacing + titleSize.height shapeY = floorToScreenPixels((bounds.height - contentHeight) / 2.0) } @@ -412,7 +412,7 @@ private final class ItemListRevealOptionNode: ASDisplayNode { transition.updateTransform(node: self.contentContainerNode, transform: CGAffineTransform(scaleX: contentScale, y: contentScale)) let titleAlpha: CGFloat = isPrimary && !self.displaysTitleInsidePill ? (1.0 - expandedProgress) : 1.0 - var didApplyManualIconCenter = false + var didApplyManualContentCenter = false let centeredIconCenterX = isPrimary ? backgroundFrame.midX : shapeFrame.midX let iconCenterX: CGFloat @@ -440,15 +440,15 @@ private final class ItemListRevealOptionNode: ASDisplayNode { let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(iconCenterX - imageSize.width / 2.0), y: floorToScreenPixels(iconCenterY - imageSize.height / 2.0) + (6.0 + self.animationNodeOffset) * scaleFraction), size: imageSize) if isPrimary { - didApplyManualIconCenter = true + didApplyManualContentCenter = true transition.updateBounds(node: animationNode, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) let targetCenter = frameCenter(iconFrame) if didApplyLayout && wasExpanded != isExpanded && revealProgress >= CGFloat.ulpOfOne { - self.updateManualIconCenter(iconNode: animationNode, targetCenter: targetCenter, forceImmediate: false) - } else if self.manuallyAnimatedIconNode === animationNode && self.iconAnimationLink != nil && revealProgress >= CGFloat.ulpOfOne { - self.updateManualIconCenter(iconNode: animationNode, targetCenter: targetCenter, forceImmediate: false) + self.updateManualContentCenter(contentNode: animationNode, targetCenter: targetCenter, forceImmediate: false) + } else if self.manuallyAnimatedContentNode === animationNode && self.contentAnimationLink != nil && revealProgress >= CGFloat.ulpOfOne { + self.updateManualContentCenter(contentNode: animationNode, targetCenter: targetCenter, forceImmediate: false) } else { - self.stopManualIconAnimation() + self.stopManualContentAnimation() transition.updatePosition(node: animationNode, position: targetCenter) } } else { @@ -470,35 +470,63 @@ private final class ItemListRevealOptionNode: ASDisplayNode { } let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(iconCenterX - fittedSize.width / 2.0), y: floorToScreenPixels(iconCenterY - fittedSize.height / 2.0)), size: fittedSize) if isPrimary { - didApplyManualIconCenter = true + didApplyManualContentCenter = true transition.updateBounds(node: iconNode, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) let targetCenter = frameCenter(iconFrame) if didApplyLayout && wasExpanded != isExpanded && revealProgress >= CGFloat.ulpOfOne { - self.updateManualIconCenter(iconNode: iconNode, targetCenter: targetCenter, forceImmediate: false) - } else if self.manuallyAnimatedIconNode === iconNode && self.iconAnimationLink != nil && revealProgress >= CGFloat.ulpOfOne { - self.updateManualIconCenter(iconNode: iconNode, targetCenter: targetCenter, forceImmediate: false) + self.updateManualContentCenter(contentNode: iconNode, targetCenter: targetCenter, forceImmediate: false) + } else if self.manuallyAnimatedContentNode === iconNode && self.contentAnimationLink != nil && revealProgress >= CGFloat.ulpOfOne { + self.updateManualContentCenter(contentNode: iconNode, targetCenter: targetCenter, forceImmediate: false) } else { - self.stopManualIconAnimation() + self.stopManualContentAnimation() transition.updatePosition(node: iconNode, position: targetCenter) } } else { transition.updateFrame(node: iconNode, frame: iconFrame) } } - - if !didApplyManualIconCenter { - self.stopManualIconAnimation() - } transition.updateAlpha(node: self.titleNode, alpha: titleAlpha) let titleFrame: CGRect if self.displaysTitleInsidePill { titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(backgroundFrame.midX - titleSize.width / 2.0), y: floorToScreenPixels(backgroundFrame.midY - titleSize.height / 2.0)), size: titleSize) + if isPrimary { + didApplyManualContentCenter = true + transition.updateBounds(node: self.titleNode, bounds: CGRect(origin: CGPoint(), size: titleFrame.size)) + let titleCenterX: CGFloat + if expandedProgress > 0.0 { + let titleEdgeInset = max(metrics.expandedIconInset, titleSize.width / 2.0 + iconlessTitleExpandedHorizontalPadding) + let expandedTitleCenterX: CGFloat + if isLeft { + expandedTitleCenterX = backgroundFrame.maxX - titleEdgeInset + } else { + expandedTitleCenterX = backgroundFrame.minX + titleEdgeInset + } + titleCenterX = backgroundFrame.midX + (expandedTitleCenterX - backgroundFrame.midX) * expandedProgress + } else { + titleCenterX = backgroundFrame.midX + } + let targetCenter = CGPoint(x: titleCenterX, y: backgroundFrame.midY) + if didApplyLayout && wasExpanded != isExpanded && revealProgress >= CGFloat.ulpOfOne { + self.updateManualContentCenter(contentNode: self.titleNode, targetCenter: targetCenter, forceImmediate: false) + } else if self.manuallyAnimatedContentNode === self.titleNode && self.contentAnimationLink != nil && revealProgress >= CGFloat.ulpOfOne { + self.updateManualContentCenter(contentNode: self.titleNode, targetCenter: targetCenter, forceImmediate: false) + } else { + self.stopManualContentAnimation() + transition.updatePosition(node: self.titleNode, position: targetCenter) + } + } else { + transition.updateFrame(node: self.titleNode, frame: titleFrame) + } } else { let titleCenterX = isPrimary ? backgroundFrame.midX : shapeFrame.midX - titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(titleCenterX - titleSize.width / 2.0), y: shapeFrame.maxY + optionTitleSpacing), size: titleSize) + titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(titleCenterX - titleSize.width / 2.0), y: shapeFrame.maxY + ritleSpacing), size: titleSize) + transition.updateFrame(node: self.titleNode, frame: titleFrame) + } + + if !didApplyManualContentCenter { + self.stopManualContentAnimation() } - transition.updateFrame(node: self.titleNode, frame: titleFrame) } override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { @@ -616,10 +644,10 @@ public final class ItemListRevealOptionsNode: ASDisplayNode { let revealedDistance = abs(self.revealOffset) let boundedRevealedDistance = min(revealedDistance, size.width) let overswipeDistance = max(0.0, revealedDistance - size.width) - let overswipeProgress = clampToUnitInterval(overswipeDistance / optionExpandedTransitionDistance) - let expandedActivationDistance = 50.0 * (optionExpandedActivationWidthFactor - 1.0) + let overswipeProgress = clampToUnitInterval(overswipeDistance / expandedTransitionDistance) + let expandedActivationDistance = 50.0 * (expandedActivationWidthFactor - 1.0) let primaryIndex = self.isLeft ? 0 : self.optionNodes.count - 1 - let stride = metrics.shapeSize.width + optionSpacing + let stride = metrics.shapeSize.width + spacing let clippingFrameX: CGFloat if self.isLeft { @@ -657,17 +685,17 @@ public final class ItemListRevealOptionsNode: ASDisplayNode { let revealProgress: CGFloat if self.isLeft { - let baseCircleLeft = size.width - boundedRevealedDistance + self.sideInset + optionEdgeInset + CGFloat(i) * stride + let baseCircleLeft = size.width - boundedRevealedDistance + self.sideInset + edgeInset + CGFloat(i) * stride baseCircleFrame = CGRect(origin: CGPoint(x: baseCircleLeft, y: 0.0), size: metrics.shapeSize) let distanceFromShutterEdge = size.width - baseCircleFrame.maxX - revealProgress = clampToUnitInterval((distanceFromShutterEdge + optionRevealStartOverlap) / (optionRevealStartOverlap + optionRevealEndDistance)) + revealProgress = clampToUnitInterval((distanceFromShutterEdge + revealStartOverlap) / (revealStartOverlap + revealEndDistance)) if isStretched { - let primaryLeft = size.width - boundedRevealedDistance + self.sideInset + optionEdgeInset + let primaryLeft = size.width - boundedRevealedDistance + self.sideInset + edgeInset let primaryRight: CGFloat if self.optionNodes.count > 1 { let neighborLeft = primaryLeft + stride + overswipeDistance - primaryRight = max(primaryLeft + metrics.shapeSize.width, neighborLeft - optionSpacing) + primaryRight = max(primaryLeft + metrics.shapeSize.width, neighborLeft - spacing) } else { primaryRight = primaryLeft + metrics.shapeSize.width + overswipeDistance } @@ -677,16 +705,16 @@ public final class ItemListRevealOptionsNode: ASDisplayNode { nodeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(circleLeft - metrics.slotShapeInset), y: 0.0), size: CGSize(width: metrics.slotWidth, height: size.height)) } } else { - let baseCircleRight = revealedDistance + self.sideInset - optionEdgeInset - CGFloat(self.optionNodes.count - 1 - i) * stride + let baseCircleRight = revealedDistance + self.sideInset - edgeInset - CGFloat(self.optionNodes.count - 1 - i) * stride baseCircleFrame = CGRect(origin: CGPoint(x: baseCircleRight - metrics.shapeSize.width, y: 0.0), size: metrics.shapeSize) - revealProgress = clampToUnitInterval((baseCircleFrame.minX + optionRevealStartOverlap) / (optionRevealStartOverlap + optionRevealEndDistance)) + revealProgress = clampToUnitInterval((baseCircleFrame.minX + revealStartOverlap) / (revealStartOverlap + revealEndDistance)) if isStretched { - let primaryRight = revealedDistance + self.sideInset - optionEdgeInset + let primaryRight = revealedDistance + self.sideInset - edgeInset let primaryLeft: CGFloat if self.optionNodes.count > 1 { let neighborRight = primaryRight - stride - overswipeDistance - primaryLeft = min(primaryRight - metrics.shapeSize.width, neighborRight + optionSpacing) + primaryLeft = min(primaryRight - metrics.shapeSize.width, neighborRight + spacing) } else { primaryLeft = primaryRight - metrics.shapeSize.width - overswipeDistance } diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift index 4967801ddb..c4076f2345 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift @@ -760,7 +760,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s var canTransfer = false var canDismiss = false let canEditProcessJoinRequests: Bool - if case let .user(user) = admin, user.botInfo?.flags.contains(.isGuardBot) == true { + if !isChannel, case let .user(user) = admin, user.botInfo?.flags.contains(.isGuardBot) == true { canEditProcessJoinRequests = canEdit && user.id != accountPeerId } else { canEditProcessJoinRequests = false diff --git a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserDomainExceptionItem.swift b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserDomainExceptionItem.swift index 14d3d23236..09f072481a 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserDomainExceptionItem.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserDomainExceptionItem.swift @@ -13,6 +13,15 @@ private enum RevealOptionKey: Int32 { case delete } +private func webBrowserDomainExceptionPlaceholderLetter(_ title: String) -> String { + let trimmedTitle = title.trimmingCharacters(in: .whitespacesAndNewlines) + if let firstCharacter = trimmedTitle.first { + return String(firstCharacter).uppercased() + } else { + return "#" + } +} + final class WebBrowserDomainExceptionItem: ListViewItem, ItemListItem { let presentationData: ItemListPresentationData let systemStyle: ItemListSystemStyle @@ -92,6 +101,8 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It private let maskNode: ASImageNode let iconNode: TransformImageNode + private let iconPlaceholderNode: ASDisplayNode + private let iconPlaceholderTextNode: TextNode let titleNode: TextNode let labelNode: TextNode @@ -126,6 +137,15 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It self.iconNode.isLayerBacked = true self.iconNode.displaysAsynchronously = false + self.iconPlaceholderNode = ASDisplayNode() + self.iconPlaceholderNode.clipsToBounds = true + self.iconPlaceholderNode.cornerRadius = 7.0 + + self.iconPlaceholderTextNode = TextNode() + self.iconPlaceholderTextNode.isUserInteractionEnabled = false + self.iconPlaceholderTextNode.contentMode = .center + self.iconPlaceholderTextNode.contentsScale = UIScreen.main.scale + self.titleNode = TextNode() self.titleNode.isUserInteractionEnabled = false @@ -136,6 +156,8 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It super.init(layerBacked: false, rotated: false, seeThrough: false) + self.addSubnode(self.iconPlaceholderNode) + self.iconPlaceholderNode.addSubnode(self.iconPlaceholderTextNode) self.addSubnode(self.iconNode) self.addSubnode(self.titleNode) self.addSubnode(self.labelNode) @@ -150,6 +172,7 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It func asyncLayout() -> (_ item: WebBrowserDomainExceptionItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeLabelLayout = TextNode.asyncLayout(self.labelNode) + let makeIconPlaceholderTextLayout = TextNode.asyncLayout(self.iconPlaceholderTextNode) let currentItem = self.item @@ -163,6 +186,7 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It let insets: UIEdgeInsets let separatorHeight = UIScreenPixel let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 + let iconSize = CGSize(width: 30.0, height: 30.0) let itemBackgroundColor: UIColor let itemSeparatorColor: UIColor @@ -181,6 +205,10 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: labelFont, textColor: labelColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: maxTitleWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let iconPlaceholderText = webBrowserDomainExceptionPlaceholderLetter(item.title) + let iconPlaceholderFont = Font.with(size: 17.0, design: .round, weight: .semibold) + let (iconPlaceholderTextLayout, iconPlaceholderTextApply) = makeIconPlaceholderTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: iconPlaceholderText, font: iconPlaceholderFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: iconSize, alignment: .center, cutout: nil, insets: UIEdgeInsets())) + let verticalInset: CGFloat switch item.systemStyle { case .glass: @@ -220,12 +248,13 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It strongSelf.topStripeNode.backgroundColor = itemSeparatorColor strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor strongSelf.backgroundNode.backgroundColor = itemBackgroundColor + strongSelf.iconPlaceholderNode.backgroundColor = item.presentationData.theme.list.mediaPlaceholderColor } - let iconSize = CGSize(width: 30.0, height: 30.0) if currentItem?.favicon != item.favicon { strongSelf.currentIconFile = nil strongSelf.iconDisposable.set(nil) + strongSelf.iconNode.reset() if let favicon = item.favicon { strongSelf.iconDisposable.set((item.context.engine.stickers.resolveInlineStickers(fileIds: [favicon]) @@ -244,6 +273,8 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It strongSelf.iconNode.setSignal(chatMessageSticker(account: item.context.account, userLocation: .other, file: file, small: false, fetched: true)) } strongSelf.iconNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 8.0), imageSize: resolvedImageSize, boundingSize: iconSize, intrinsicInsets: .zero))() + strongSelf.iconPlaceholderNode.isHidden = true + strongSelf.iconNode.isHidden = false })) } } @@ -251,9 +282,11 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It if strongSelf.currentIconFile?.fileId.id == item.favicon, let dimensions = strongSelf.currentIconFile?.dimensions?.cgSize { imageSize = dimensions.aspectFilled(imageSize) } + let hasResolvedIcon = item.favicon != nil && strongSelf.currentIconFile?.fileId.id == item.favicon let _ = titleApply() let _ = labelApply() + let _ = iconPlaceholderTextApply() switch item.style { case .plain: @@ -324,6 +357,10 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It strongSelf.labelNode.frame = labelFrame let iconFrame = CGRect(origin: CGPoint(x: params.leftInset + 16.0 + strongSelf.revealOffset, y: floorToScreenPixels((contentSize.height - iconSize.height) / 2.0)), size: iconSize) + strongSelf.iconPlaceholderNode.frame = iconFrame + strongSelf.iconPlaceholderTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((iconSize.width - iconPlaceholderTextLayout.size.width) / 2.0) + UIScreenPixel, y: floorToScreenPixels((iconSize.height - iconPlaceholderTextLayout.size.height) / 2.0) + 1.0), size: iconPlaceholderTextLayout.size) + strongSelf.iconPlaceholderNode.isHidden = hasResolvedIcon + strongSelf.iconNode.isHidden = !hasResolvedIcon strongSelf.iconNode.frame = iconFrame strongSelf.iconNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 7.0), imageSize: imageSize, boundingSize: iconSize, intrinsicInsets: .zero))() @@ -360,6 +397,10 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It iconFrame.origin.x = params.leftInset + 16.0 + offset transition.updateFrame(node: self.iconNode, frame: iconFrame) + var iconPlaceholderFrame = self.iconPlaceholderNode.frame + iconPlaceholderFrame.origin.x = params.leftInset + 16.0 + offset + transition.updateFrame(node: self.iconPlaceholderNode, frame: iconPlaceholderFrame) + var titleFrame = self.titleNode.frame titleFrame.origin.x = leftInset + offset transition.updateFrame(node: self.titleNode, frame: titleFrame) From 11a4dbf0a0a285e88f53e6da635633e85ee71904 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 2 Jun 2026 13:31:23 +0200 Subject: [PATCH 2/2] Various improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 13 +++++ .../Sources/AttachmentPanel.swift | 6 +- .../BotCheckoutPaymentMethodScreen.swift | 25 ++++---- .../BotCheckoutShippingOptionScreen.swift | 25 ++++---- .../Sources/ChatListController.swift | 53 +---------------- .../MtProtoKit/Sources/MTApiEnvironment.m | 8 +++ .../Sources/ComposePollScreen.swift | 38 ++++++++++++ .../Sources/PollAttachmentScreen.swift | 58 ++++++++++--------- .../Sources/OpenUserGeneratedUrl.swift | 5 +- .../ChatControllerOpenLinkContextMenu.swift | 3 +- 10 files changed, 120 insertions(+), 114 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index a626678a02..43e6681e57 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -16342,3 +16342,16 @@ Error: %8$@"; "MediaPicker.SetNewGroupPhoto" = "Set new group photo"; "MediaPicker.SetNewChannelPhoto" = "Set new channel photo"; + +"Attachment.Link" = "Link"; + +"Checkout.PaymentMethod.Proceed" = "Proceed"; +"Checkout.ShippingMethod.Proceed" = "Proceed"; + +"OpenLinkConfirmation.Title" = "Open link?"; +"OpenLinkConfirmation.Open" = "Open"; + +"Chat.ContextMenu.OpenInBrowser" = "Open in Browser"; +"Chat.ContextMenu.OpenInApp" = "Open In-App"; + +"CreatePoll.Link.Description" = "Attach a link to this option."; diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index f60228bde9..340a97f14a 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -244,8 +244,7 @@ private final class AttachButtonComponent: CombinedComponent { name = strings.Attachment_Audio imageName = "Chat/Attach Menu/Audio" case .link: - //TODO:localize - name = "Link" + name = strings.Attachment_Link imageName = "Chat/Attach Menu/Link" case let .app(bot): botPeer = bot.peer @@ -2187,8 +2186,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog case .audio: accessibilityTitle = self.presentationData.strings.Attachment_Audio case .link: - //TODO:localize - accessibilityTitle = "Link" + accessibilityTitle = self.presentationData.strings.Attachment_Link case let .app(bot): accessibilityTitle = bot.shortName case .standalone: diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutPaymentMethodScreen.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutPaymentMethodScreen.swift index 1e85b5f263..e9aff0ba7c 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutPaymentMethodScreen.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutPaymentMethodScreen.swift @@ -328,9 +328,9 @@ private final class BotCheckoutPaymentMethodScreenComponent: Component { self.component = component self.state = state - let environmentValue = environment[ViewControllerComponentContainer.Environment.self].value - let controller = environmentValue.controller - let theme = environmentValue.theme.withModalBlocksBackground() + let environment = environment[ViewControllerComponentContainer.Environment.self].value + let controller = environment.controller + let theme = environment.theme.withModalBlocksBackground() let dismiss: (Bool, (() -> Void)?) -> Void = { [weak self] animated, completion in guard let self, !self.isDismissing else { @@ -379,7 +379,7 @@ private final class BotCheckoutPaymentMethodScreenComponent: Component { )), titleItem: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: environmentValue.strings.Checkout_PaymentMethod, + string: environment.strings.Checkout_PaymentMethod, font: Font.semibold(17.0), textColor: theme.list.itemPrimaryTextColor )), @@ -402,7 +402,6 @@ private final class BotCheckoutPaymentMethodScreenComponent: Component { } ) ), - //TODO:localize bottomItem: AnyComponent(ButtonComponent( background: ButtonComponent.Background( style: .glass, @@ -413,7 +412,7 @@ private final class BotCheckoutPaymentMethodScreenComponent: Component { content: AnyComponentWithIdentity( id: AnyHashable("proceed"), component: AnyComponent(ButtonTextContentComponent( - text: "Proceed", + text: environment.strings.Checkout_PaymentMethod_Proceed, badge: 0, textColor: theme.list.itemCheckColors.foregroundColor, badgeBackground: theme.list.itemCheckColors.foregroundColor, @@ -440,16 +439,16 @@ private final class BotCheckoutPaymentMethodScreenComponent: Component { animateOut: self.animateOut )), environment: { - environmentValue + environment ResizableSheetComponentEnvironment( theme: theme, - statusBarHeight: environmentValue.statusBarHeight, - safeInsets: environmentValue.safeInsets, + statusBarHeight: environment.statusBarHeight, + safeInsets: environment.safeInsets, inputHeight: 0.0, - metrics: environmentValue.metrics, - deviceMetrics: environmentValue.deviceMetrics, - isDisplaying: environmentValue.isVisible, - isCentered: environmentValue.metrics.widthClass == .regular, + metrics: environment.metrics, + deviceMetrics: environment.deviceMetrics, + isDisplaying: environment.isVisible, + isCentered: environment.metrics.widthClass == .regular, screenSize: availableSize, regularMetricsSize: nil, dismiss: { animated in diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutShippingOptionScreen.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutShippingOptionScreen.swift index aa848c41fc..f86a1496e5 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutShippingOptionScreen.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutShippingOptionScreen.swift @@ -225,9 +225,9 @@ private final class BotCheckoutShippingOptionScreenComponent: Component { self.component = component self.state = state - let environmentValue = environment[ViewControllerComponentContainer.Environment.self].value - let controller = environmentValue.controller - let theme = environmentValue.theme.withModalBlocksBackground() + let environment = environment[ViewControllerComponentContainer.Environment.self].value + let controller = environment.controller + let theme = environment.theme.withModalBlocksBackground() let dismiss: (Bool) -> Void = { [weak self] animated in guard let self, !self.isDismissing else { @@ -268,7 +268,7 @@ private final class BotCheckoutShippingOptionScreenComponent: Component { )), titleItem: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: environmentValue.strings.Checkout_ShippingMethod, + string: environment.strings.Checkout_ShippingMethod, font: Font.semibold(17.0), textColor: theme.list.itemPrimaryTextColor )), @@ -291,7 +291,6 @@ private final class BotCheckoutShippingOptionScreenComponent: Component { } ) ), - //TODO:localize bottomItem: AnyComponent(ButtonComponent( background: ButtonComponent.Background( style: .glass, @@ -302,7 +301,7 @@ private final class BotCheckoutShippingOptionScreenComponent: Component { content: AnyComponentWithIdentity( id: AnyHashable("proceed"), component: AnyComponent(ButtonTextContentComponent( - text: "Proceed", + text: environment.strings.Checkout_ShippingMethod_Proceed, badge: 0, textColor: theme.list.itemCheckColors.foregroundColor, badgeBackground: theme.list.itemCheckColors.foregroundColor, @@ -323,16 +322,16 @@ private final class BotCheckoutShippingOptionScreenComponent: Component { animateOut: self.animateOut )), environment: { - environmentValue + environment ResizableSheetComponentEnvironment( theme: theme, - statusBarHeight: environmentValue.statusBarHeight, - safeInsets: environmentValue.safeInsets, + statusBarHeight: environment.statusBarHeight, + safeInsets: environment.safeInsets, inputHeight: 0.0, - metrics: environmentValue.metrics, - deviceMetrics: environmentValue.deviceMetrics, - isDisplaying: environmentValue.isVisible, - isCentered: environmentValue.metrics.widthClass == .regular, + metrics: environment.metrics, + deviceMetrics: environment.deviceMetrics, + isDisplaying: environment.isVisible, + isCentered: environment.metrics.widthClass == .regular, screenSize: availableSize, regularMetricsSize: nil, dismiss: { animated in diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 79b806e43d..d02ebd71aa 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -5900,8 +5900,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } actions.append(.init(title: self.presentationData.strings.Common_Cancel)) - //TODO:localize - let title: String = "Delete Chat" + let title: String = self.presentationData.strings.ChatList_DeleteChat var text: String if mainPeer.id == self.context.account.peerId { text = self.presentationData.strings.ChatList_DeleteSavedMessagesConfirmation @@ -5948,56 +5947,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController ], actions: actions ) - - -// let actionSheet = ActionSheetController(presentationData: self.presentationData) -// var items: [ActionSheetItem] = [] -// -// items.append(DeleteChatPeerActionSheetItem(context: self.context, peer: mainPeer, chatPeer: chatPeer, action: .delete, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder)) -// -// if joined || mainPeer.isDeleted { -// items.append(ActionSheetButtonItem(title: self.presentationData.strings.Common_Delete, color: .destructive, action: { [weak self, weak actionSheet] in -// actionSheet?.dismissAnimated() -// self?.schedulePeerChatRemoval(peer: peer, type: .forEveryone, deleteGloballyIfPossible: deleteGloballyIfPossible, completion: { -// removed() -// }) -// completion(true) -// })) -// } else { -// items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForCurrentUser, color: .destructive, action: { [weak self, weak actionSheet] in -// actionSheet?.dismissAnimated() -// self?.schedulePeerChatRemoval(peer: peer, type: .forLocalPeer, deleteGloballyIfPossible: deleteGloballyIfPossible, completion: { -// removed() -// }) -// completion(true) -// })) -// items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForEveryone(mainPeer.compactDisplayTitle).string, color: .destructive, action: { [weak self, weak actionSheet] in -// actionSheet?.dismissAnimated() -// guard let strongSelf = self else { -// return -// } -// strongSelf.present(textAlertController(context: strongSelf.context, title: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationTitle, text: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationText, actions: [ -// TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { -// completion(false) -// }), -// TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationAction, action: { -// self?.schedulePeerChatRemoval(peer: peer, type: .forEveryone, deleteGloballyIfPossible: deleteGloballyIfPossible, completion: { -// removed() -// }) -// completion(true) -// }) -// ], parseMarkdown: true), in: .window(.root)) -// })) -// } -// actionSheet.setItemGroups([ -// ActionSheetItemGroup(items: items), -// ActionSheetItemGroup(items: [ -// ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in -// actionSheet?.dismissAnimated() -// completion(false) -// }) -// ]) -// ]) self.present(alertScreen, in: .window(.root)) } else if peer.peerId == self.context.account.peerId { self.present(textAlertController(context: self.context, title: self.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationTitle, text: self.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationText, actions: [ diff --git a/submodules/MtProtoKit/Sources/MTApiEnvironment.m b/submodules/MtProtoKit/Sources/MTApiEnvironment.m index 92b6ab69cf..36da88228b 100644 --- a/submodules/MtProtoKit/Sources/MTApiEnvironment.m +++ b/submodules/MtProtoKit/Sources/MTApiEnvironment.m @@ -737,6 +737,14 @@ NSString *suffix = @""; [platform isEqualToString:@"iPad16,6"]) return @"iPad Pro 12.9 inch (7th gen)"; + if ([platform isEqualToString:@"iPad16,8"] || + [platform isEqualToString:@"iPad16,9"]) + return @"iPad Air 11 inch (8th gen)"; + + if ([platform isEqualToString:@"iPad16,10"] || + [platform isEqualToString:@"iPad16,11"]) + return @"iPad Air 13 inch (8th gen)"; + if ([platform hasPrefix:@"iPhone"]) return @"Unknown iPhone"; if ([platform hasPrefix:@"iPod"]) diff --git a/submodules/TelegramUI/Components/ComposePollScreen/Sources/ComposePollScreen.swift b/submodules/TelegramUI/Components/ComposePollScreen/Sources/ComposePollScreen.swift index 8253c1768c..7753274151 100644 --- a/submodules/TelegramUI/Components/ComposePollScreen/Sources/ComposePollScreen.swift +++ b/submodules/TelegramUI/Components/ComposePollScreen/Sources/ComposePollScreen.swift @@ -37,6 +37,7 @@ import EdgeEffect import LocationUI import CountrySelectionUI import ShareWithPeersScreen +import ChatTextLinkEditUI public final class ComposedPoll { public struct Text { @@ -918,6 +919,11 @@ final class ComposePollScreenComponent: Component { self.deactivateInput() + if !replace, case .pollOption = subject, let webpage = self.attachedMedia(for: subject)?.media.media as? TelegramMediaWebpage, let link = webpage.content.url { + self.openAttachedLinkMedia(subject: subject, link: link) + return + } + guard replace || !self.openAttachMediaContextMenu(subject: subject) else { return } @@ -967,6 +973,38 @@ final class ComposePollScreenComponent: Component { }) } + private func openAttachedLinkMedia(subject: MediaAttachSubject, link: String) { + guard let component = self.component else { + return + } + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let controller = chatTextLinkEditController( + context: component.context, + updatedPresentationData: (presentationData, .never()), + text: presentationData.strings.CreatePoll_Link_Description, + link: link, + preview: true, + apply: { [weak self] link, webpage in + guard let self, let link else { + return + } + + if link.isEmpty { + self.setAttachedMedia(nil, for: subject) + self.state?.updated(transition: .easeInOut(duration: 0.2)) + return + } + + let attachedMedia = AttachedMedia(media: .standalone(media: webpage ?? makePollAttachmentLinkWebpage(link: link))) + self.setAttachedMedia(attachedMedia, for: subject) + self.uploadAttachedMediaIfNeeded(attachedMedia) + self.state?.updated(transition: .easeInOut(duration: 0.2)) + } + ) + (self.environment?.controller() as? ComposePollScreen)?.parentController()?.present(controller, in: .window(.root)) + } + private func uploadAttachedMediaIfNeeded(_ media: AttachedMedia) { guard let component = self.component, media.requiresUpload, media.uploadDisposable == nil else { return diff --git a/submodules/TelegramUI/Components/ComposePollScreen/Sources/PollAttachmentScreen.swift b/submodules/TelegramUI/Components/ComposePollScreen/Sources/PollAttachmentScreen.swift index 9ad77ecf2c..3945de4fc1 100644 --- a/submodules/TelegramUI/Components/ComposePollScreen/Sources/PollAttachmentScreen.swift +++ b/submodules/TelegramUI/Components/ComposePollScreen/Sources/PollAttachmentScreen.swift @@ -23,6 +23,34 @@ public enum PollAttachmentSubject { case option } +func makePollAttachmentLinkWebpage(link: String) -> TelegramMediaWebpage { + let mediaId = Int64.random(in: Int64.min ... Int64.max) + return TelegramMediaWebpage( + webpageId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: mediaId), + content: .Loaded(TelegramMediaWebpageLoadedContent( + url: link, + displayUrl: link, + hash: 0, + type: nil, + websiteName: nil, + title: nil, + text: nil, + embedUrl: nil, + embedType: nil, + embedSize: nil, + duration: nil, + author: nil, + isMediaLargeByDefault: nil, + imageIsVideoCover: false, + image: nil, + file: nil, + story: nil, + attributes: [], + instantPage: nil + )) + ) +} + public func presentPollAttachmentScreen( context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, @@ -236,11 +264,11 @@ public func presentPollAttachmentScreen( case .link: attachmentController?.dismiss(animated: true) - //TODO:localize + let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } let controller = chatTextLinkEditController( context: context, updatedPresentationData: updatedPresentationData, - text: "Attach a link to this option.", + text: presentationData.strings.CreatePoll_Link_Description, link: nil, preview: true, apply: { link, webpage in @@ -251,31 +279,7 @@ public func presentPollAttachmentScreen( completion(.standalone(media: webpage)) return } - let mediaId = Int64.random(in: Int64.min ... Int64.max) - let webPage = TelegramMediaWebpage( - webpageId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: mediaId), - content: .Loaded(TelegramMediaWebpageLoadedContent( - url: link, - displayUrl: link, - hash: 0, - type: nil, - websiteName: nil, - title: nil, - text: nil, - embedUrl: nil, - embedType: nil, - embedSize: nil, - duration: nil, - author: nil, - isMediaLargeByDefault: nil, - imageIsVideoCover: false, - image: nil, - file: nil, - story: nil, - attributes: [], - instantPage: nil - ))) - completion(.standalone(media: webPage)) + completion(.standalone(media: makePollAttachmentLinkWebpage(link: link))) } ) present(controller, false) diff --git a/submodules/TelegramUI/Components/OpenUserGeneratedUrl/Sources/OpenUserGeneratedUrl.swift b/submodules/TelegramUI/Components/OpenUserGeneratedUrl/Sources/OpenUserGeneratedUrl.swift index d17edb70c6..51d69bcbb9 100644 --- a/submodules/TelegramUI/Components/OpenUserGeneratedUrl/Sources/OpenUserGeneratedUrl.swift +++ b/submodules/TelegramUI/Components/OpenUserGeneratedUrl/Sources/OpenUserGeneratedUrl.swift @@ -125,9 +125,8 @@ public func openUserGeneratedUrl( let disposable = MetaDisposable() let checkState = concealedAlertOption.map { _ in AlertCheckComponent.ExternalState() } - //TODO:localize var content: [AnyComponentWithIdentity] = [] - content.append(AnyComponentWithIdentity(id: "title", component: AnyComponent(AlertTitleComponent(title: "Open link?")))) + content.append(AnyComponentWithIdentity(id: "title", component: AnyComponent(AlertTitleComponent(title: presentationData.strings.OpenLinkConfirmation_Title)))) content.append(AnyComponentWithIdentity(id: "text", component: AnyComponent(AlertTextComponent( content: .plain(displayUrl), alignment: .center, @@ -158,7 +157,7 @@ public func openUserGeneratedUrl( content: content, actions: [ .init(title: presentationData.strings.Common_Cancel), - .init(title: "Open", type: .default, action: { + .init(title: presentationData.strings.OpenLinkConfirmation_Open, type: .default, action: { if checkState?.value == true { concealedAlertOption?.action() } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenLinkContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenLinkContextMenu.swift index 532a7b7719..63ae79c6ea 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenLinkContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenLinkContextMenu.swift @@ -250,8 +250,7 @@ extension ChatControllerImpl { })) if let openMode { - //TODO:localize - let reverseText = openMode.shouldOpenInApp ? "Open in Browser" : "Open In-App" + let reverseText = openMode.shouldOpenInApp ? self.presentationData.strings.Chat_ContextMenu_OpenInBrowser : self.presentationData.strings.Chat_ContextMenu_OpenInApp items.append(ActionSheetButtonItem(title: reverseText, color: .accent, action: { [weak self, weak actionSheet] in actionSheet?.dismissAnimated() guard let self else {