Various fixes

This commit is contained in:
Ilya Laktyushin 2026-05-31 17:35:32 +02:00
parent 98d148e08d
commit 622f9d970a
48 changed files with 499 additions and 1876 deletions

View file

@ -1331,7 +1331,7 @@ public final class TextProcessingScreenSendContextActions {
public enum TextProcessingScreenMode {
case edit(saveRestoreStateId: EnginePeer.Id?, completion: (TextWithEntities) -> Void, send: ((TextWithEntities) -> Void)?, sendContextActions: TextProcessingScreenSendContextActions?)
case translate(fromLanguage: String?)
case translate(fromLanguage: String?, applyResult: ((TextWithEntities) -> Void)?)
case preview(style: TelegramComposeAIMessageMode.CloudStyle.Custom, authorPeer: EnginePeer?, initialPreview: AIMessageStylePreview?, isAlreadyAdded: Bool, added: () -> Void)
}

View file

@ -23,6 +23,7 @@ swift_library(
"//submodules/ContextUI",
"//submodules/UndoUI",
"//submodules/TranslateUI",
"//submodules/TelegramUI/Components/TextProcessingScreen",
"//submodules/ComponentFlow:ComponentFlow",
"//submodules/Components/ViewControllerComponent:ViewControllerComponent",
"//submodules/Components/MultilineTextComponent:MultilineTextComponent",

View file

@ -13,6 +13,7 @@ import AppBundle
import InstantPageUI
import UndoUI
import TranslateUI
import TextProcessingScreen
import ContextUI
import Pasteboard
import SaveToCameraRoll
@ -1821,15 +1822,22 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
let (canTranslate, language) = canTranslateText(context: context, text: text, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: false, ignoredLanguages: translationSettings.ignoredLanguages)
if canTranslate {
actions.append(ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuTranslate, accessibilityLabel: strings.Conversation_ContextMenuTranslate), action: { [weak self] in
let controller = TranslateScreen(context: context, text: text, canCopy: true, fromLanguage: language)
controller.pushController = { [weak self] c in
self?.getNavigationController()?._keepModalDismissProgress = true
self?.push(c)
Task { @MainActor [weak self] in
guard let self else {
return
}
let controller = await TextProcessingScreen(
context: context,
mode: .translate(fromLanguage: language, applyResult: nil),
inputText: TextWithEntities(text: text, entities: []),
copyResult: { [weak self] text in
storeMessageTextInPasteboard(text.text, entities: text.entities)
self?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), nil)
},
translateChat: nil
)
self.present(controller, nil)
}
controller.presentController = { [weak self] c in
self?.present(c, nil)
}
self?.present(controller, nil)
}))
}

View file

@ -1150,7 +1150,7 @@ public final class Camera {
public static func isDualCameraSupported(forRoundVideo: Bool = false) -> Bool {
if #available(iOS 13.0, *), AVCaptureMultiCamSession.isMultiCamSupported && !DeviceModel.current.isIpad {
if forRoundVideo && DeviceModel.current == .iPhoneXR {
if forRoundVideo && (ProcessInfo.processInfo.isLowPowerModeEnabled || DeviceModel.current == .iPhoneXR) {
return false
}
return true

View file

@ -3134,9 +3134,10 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
default:
break
}
let messageTypeIconScale = min(1.0, item.presentationData.fontSize.itemListBaseFontSize / 17.0)
if let currentMessageTypeIcon {
textLeftCutout += currentMessageTypeIcon.size.width
textLeftCutout += currentMessageTypeIcon.size.width * messageTypeIconScale
if !contentImageSpecs.isEmpty {
textLeftCutout += forwardedIconSpacing
} else {
@ -3654,6 +3655,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
textMaxWidth -= 18.0
}
let textLineSpacing: CGFloat = min(0.2, item.presentationData.fontSize.itemListBaseFontSize * 0.2 / 17.0)
let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(
attributedString: textAttributedString,
backgroundColor: nil,
@ -3661,7 +3663,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
truncationType: .end,
constrainedSize: CGSize(width: textMaxWidth, height: .greatestFiniteMagnitude),
alignment: .natural,
lineSpacing: 0.2,
lineSpacing: textLineSpacing,
cutout: textCutout,
insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)
))
@ -4380,17 +4382,17 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
strongSelf.statusNode.fontSize = item.presentationData.fontSize.itemListBaseFontSize
let _ = strongSelf.statusNode.transitionToState(statusState, animated: animateContent)
let rightAccessoryVerticalOffset: CGFloat = -2.0
let rightAccessoryVerticalOffset: CGFloat = floorToScreenPixels(-4.0 * min(1.0, item.presentationData.fontSize.itemListBaseFontSize / 17.0))
var nextBadgeX: CGFloat = contentRect.maxX
if let _ = currentBadgeBackgroundImage {
let badgeFrame = CGRect(x: nextBadgeX - badgeLayout.width, y: contentRect.maxY - badgeLayout.height - 2.0 + rightAccessoryVerticalOffset, width: badgeLayout.width, height: badgeLayout.height)
let badgeFrame = CGRect(x: nextBadgeX - badgeLayout.width, y: contentRect.maxY - badgeLayout.height + rightAccessoryVerticalOffset, width: badgeLayout.width, height: badgeLayout.height)
transition.updateFrame(node: strongSelf.badgeNode, frame: badgeFrame)
nextBadgeX -= badgeLayout.width + 6.0
}
if currentMentionBadgeImage != nil || currentBadgeBackgroundImage != nil {
let badgeFrame = CGRect(x: nextBadgeX - mentionBadgeLayout.width, y: contentRect.maxY - mentionBadgeLayout.height - 2.0 + rightAccessoryVerticalOffset, width: mentionBadgeLayout.width, height: mentionBadgeLayout.height)
let badgeFrame = CGRect(x: nextBadgeX - mentionBadgeLayout.width, y: contentRect.maxY - mentionBadgeLayout.height + rightAccessoryVerticalOffset, width: mentionBadgeLayout.width, height: mentionBadgeLayout.height)
transition.updateFrame(node: strongSelf.mentionBadgeNode, frame: badgeFrame)
nextBadgeX -= mentionBadgeLayout.width + 6.0
@ -4401,7 +4403,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
strongSelf.pinnedIconNode.isHidden = false
let pinnedIconSize = currentPinnedIconImage.size
let pinnedIconFrame = CGRect(x: nextBadgeX - pinnedIconSize.width, y: contentRect.maxY - pinnedIconSize.height - 2.0 + rightAccessoryVerticalOffset, width: pinnedIconSize.width, height: pinnedIconSize.height)
let pinnedIconFrame = CGRect(x: nextBadgeX - pinnedIconSize.width, y: contentRect.maxY - pinnedIconSize.height + rightAccessoryVerticalOffset, width: pinnedIconSize.width, height: pinnedIconSize.height)
strongSelf.pinnedIconNode.frame = pinnedIconFrame
nextBadgeX -= pinnedIconSize.width + 6.0
@ -4836,8 +4838,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
if strongSelf.forwardedIconNode.supernode == nil {
strongSelf.mainContentContainerNode.addSubnode(strongSelf.forwardedIconNode)
}
transition.updateFrame(node: strongSelf.forwardedIconNode, frame: CGRect(origin: messageTypeIconOffset, size: messageTypeIconImage.size))
mediaPreviewOffset.x += messageTypeIconImage.size.width + forwardedIconSpacing
let iconSize = CGSize(width: messageTypeIconImage.size.width * messageTypeIconScale, height: messageTypeIconImage.size.height * messageTypeIconScale)
transition.updateFrame(node: strongSelf.forwardedIconNode, frame: CGRect(origin: messageTypeIconOffset, size: iconSize))
mediaPreviewOffset.x += messageTypeIconImage.size.width * messageTypeIconScale + forwardedIconSpacing
} else if strongSelf.forwardedIconNode.supernode != nil {
strongSelf.forwardedIconNode.removeFromSupernode()
}

View file

@ -2070,7 +2070,13 @@ private final class DrawingScreenComponent: CombinedComponent {
})
.opacity(controlsAreVisible ? 1.0 : 0.0)
)
let modeConstrainedWidth: CGFloat
if component.sourceHint == .storyEditor {
modeConstrainedWidth = context.availableSize.width - 66.0 * 2.0
} else {
modeConstrainedWidth = context.availableSize.width - 76.0 * 2.0
}
let mode = mode.update(
component: ModeComponent(
isTablet: false,
@ -2093,7 +2099,7 @@ private final class DrawingScreenComponent: CombinedComponent {
},
tag: modeTag
),
availableSize: CGSize(width: context.availableSize.width - 66.0 * 2.0, height: 44.0),
availableSize: CGSize(width: modeConstrainedWidth, height: 44.0),
transition: context.transition
)
var modePosition = CGPoint(x: context.availableSize.width / 2.0 - (modeRightInset - 57.0) / 2.0, y: context.availableSize.height - environment.safeInsets.bottom - mode.size.height / 2.0 - 9.0)

View file

@ -332,9 +332,9 @@ final class ModeComponent: Component {
if isTablet {
spacing = 9.0
} else {
if availableSize.width < 200.0 {
inset = 20.0
spacing = 24.0
if availableSize.width < 270.0 {
inset = 16.0
spacing = 20.0
} else {
spacing = 30.0
}

View file

@ -40,6 +40,7 @@ swift_library(
"//submodules/UndoUI:UndoUI",
"//submodules/InvisibleInkDustNode:InvisibleInkDustNode",
"//submodules/TranslateUI:TranslateUI",
"//submodules/TelegramUI/Components/TextProcessingScreen",
"//submodules/ShimmerEffect:ShimmerEffect",
"//submodules/Utils/RangeSet:RangeSet",
"//submodules/TelegramUI/Components/TextNodeWithEntities:TextNodeWithEntities",

View file

@ -26,6 +26,7 @@ import MultiAnimationRenderer
import Pasteboard
import Speak
import TranslateUI
import TextProcessingScreen
import TelegramNotices
import SolidRoundedButtonNode
import UrlHandling
@ -546,34 +547,42 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
let (_, language) = canTranslateText(context: self.context, text: text.string, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: showTranslateIfTopical, ignoredLanguages: translationSettings.ignoredLanguages)
let _ = ApplicationSpecificNotice.incrementTranslationSuggestion(accountManager: self.context.sharedContext.accountManager, timestamp: Int32(Date().timeIntervalSince1970)).start()
let translateController = TranslateScreen(context: self.context, forceTheme: defaultDarkPresentationTheme, text: text.string, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages)
translateController.pushController = { [weak self] c in
Task { @MainActor [weak self] in
guard let self else {
return
}
self.controllerInteraction?.pushController(c)
let translateController = await TextProcessingScreen(
context: self.context,
theme: defaultDarkPresentationTheme,
mode: .translate(fromLanguage: language, applyResult: nil),
inputText: TextWithEntities(text: text.string, entities: []),
copyResult: { [weak self] text in
guard let self else {
return
}
storeMessageTextInPasteboard(text.text, entities: text.entities)
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let undoController = UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, appearance: UndoOverlayController.Appearance(isBlurred: true), action: { _ in true })
self.controllerInteraction?.presentController(undoController, nil)
},
translateChat: nil
)
//self.actionSheet = translateController
//view.updateIsProgressPaused()
/*translateController.wasDismissed = { [weak self, weak view] in
guard let self, let view else {
return
}
self.actionSheet = nil
view.updateIsProgressPaused()
}*/
//component.controller()?.present(translateController, in: .window(.root))
self.controllerInteraction?.presentController(translateController, nil)
}
translateController.presentController = { [weak self] c in
guard let self else {
return
}
self.controllerInteraction?.presentController(c, nil)
}
//self.actionSheet = translateController
//view.updateIsProgressPaused()
/*translateController.wasDismissed = { [weak self, weak view] in
guard let self, let view else {
return
}
self.actionSheet = nil
view.updateIsProgressPaused()
}*/
//component.controller()?.present(translateController, in: .window(.root))
self.controllerInteraction?.presentController(translateController, nil)
})
case .quote:
break

View file

@ -17,6 +17,7 @@ import ImageContentAnalysis
import TextSelectionNode
import Speak
import TranslateUI
import TextProcessingScreen
import UndoUI
import ContextUI
import SaveToCameraRoll
@ -408,15 +409,24 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
}
case .translate:
if let parentController = strongSelf.baseNavigationController()?.topViewController as? ViewController {
let controller = TranslateScreen(context: strongSelf.context, text: string, canCopy: true, fromLanguage: nil)
controller.pushController = { [weak parentController] c in
(parentController?.navigationController as? NavigationController)?._keepModalDismissProgress = true
parentController?.push(c)
Task { @MainActor [weak parentController, weak strongSelf] in
guard let strongSelf else {
return
}
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with({ $0 })
let controller = await TextProcessingScreen(
context: strongSelf.context,
mode: .translate(fromLanguage: nil, applyResult: nil),
inputText: TextWithEntities(text: string, entities: []),
copyResult: { [weak parentController] text in
storeMessageTextInPasteboard(text.text, entities: text.entities)
let tooltipController = UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false })
parentController?.present(tooltipController, in: .window(.root))
},
translateChat: nil
)
parentController?.present(controller, in: .window(.root))
}
controller.presentController = { [weak parentController] c in
parentController?.present(c, in: .window(.root))
}
parentController.present(controller, in: .window(.root))
}
}
})

View file

@ -32,6 +32,8 @@ swift_library(
"//submodules/LocationResources:LocationResources",
"//submodules/UndoUI:UndoUI",
"//submodules/TranslateUI:TranslateUI",
"//submodules/TelegramUI/Components/TextProcessingScreen",
"//submodules/Pasteboard",
"//submodules/Tuples:Tuples",
"//third-party/SwiftMath:SwiftMath",
"//submodules/ComponentFlow:ComponentFlow",

View file

@ -16,6 +16,8 @@ import LocationUI
import UndoUI
import ContextUI
import TranslateUI
import TextProcessingScreen
import Pasteboard
final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
private weak var controller: InstantPageController?
@ -1528,11 +1530,11 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
translationSettings = TranslationSettings.defaultSettings
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var actions: [ContextMenuAction] = [ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuCopy, accessibilityLabel: strings.Conversation_ContextMenuCopy), action: { [weak self] in
UIPasteboard.general.string = text
if let strongSelf = self {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
}
}), ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuShare, accessibilityLabel: strings.Conversation_ContextMenuShare), action: { [weak self] in
@ -1544,15 +1546,22 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
let (canTranslate, language) = canTranslateText(context: context, text: text, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: false, ignoredLanguages: translationSettings.ignoredLanguages)
if canTranslate {
actions.append(ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuTranslate, accessibilityLabel: strings.Conversation_ContextMenuTranslate), action: { [weak self] in
let controller = TranslateScreen(context: context, text: text, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages)
controller.pushController = { [weak self] c in
(self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true
self?.controller?.push(c)
Task { @MainActor [weak self] in
guard let self else {
return
}
let controller = await TextProcessingScreen(
context: context,
mode: .translate(fromLanguage: language, applyResult: nil),
inputText: TextWithEntities(text: text, entities: []),
copyResult: { [weak self] text in
storeMessageTextInPasteboard(text.text, entities: text.entities)
self?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), nil)
},
translateChat: nil
)
self.present(controller, nil)
}
controller.presentController = { [weak self] c in
self?.controller?.present(c, in: .window(.root))
}
self?.present(controller, nil)
}))
}

View file

@ -112,7 +112,7 @@ private struct ItemListRevealOptionLayoutMetrics {
}
var slotShapeInset: CGFloat {
return floor((self.slotWidth - self.shapeSize.width) / 2.0)
return floorToScreenPixels((self.slotWidth - self.shapeSize.width) / 2.0)
}
static func metrics(for height: CGFloat, hasVisualIcons: Bool) -> ItemListRevealOptionLayoutMetrics {
@ -120,7 +120,7 @@ private struct ItemListRevealOptionLayoutMetrics {
let compactShapeSize = CGSize(width: 60.0, height: 32.0)
let regularContentHeight = regularShapeSize.height + optionTitleSpacing + ceil(titleFont.lineHeight)
if height < regularContentHeight || !hasVisualIcons {
return ItemListRevealOptionLayoutMetrics(shapeSize: compactShapeSize, slotWidth: 70.0, titleWidth: 70.0, iconMaxSide: 20.0, cornerRadius: 16.0, expandedIconInset: 16.0)
return ItemListRevealOptionLayoutMetrics(shapeSize: compactShapeSize, slotWidth: 70.0, titleWidth: 70.0, iconMaxSide: 24.0, cornerRadius: 16.0, expandedIconInset: 16.0)
} else {
return ItemListRevealOptionLayoutMetrics(shapeSize: regularShapeSize, slotWidth: 60.0, titleWidth: 60.0, iconMaxSide: 40.0, cornerRadius: 25.0, expandedIconInset: 20.0)
}
@ -163,7 +163,7 @@ private final class ItemListRevealOptionNode: ASDisplayNode {
private let contentContainerNode: ASDisplayNode
private let backgroundNode: ASDisplayNode
private let highlightNode: ASDisplayNode
private let titleNode: ASTextNode
private let titleNode: ImmediateTextNode
private let iconNode: ASImageNode?
private let animationNode: SimpleAnimationNode?
@ -187,7 +187,7 @@ private final class ItemListRevealOptionNode: ASDisplayNode {
}
var titleWidthForGroupPillSizing: CGFloat {
var titleWidth = self.titleNode.measure(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)).width
var titleWidth = self.titleNode.updateLayout(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)).width
if self.displaysTitleInsidePill {
titleWidth += optionIconlessTitleAdditionalHorizontalPadding
}
@ -199,9 +199,11 @@ private final class ItemListRevealOptionNode: ASDisplayNode {
self.backgroundNode = ASDisplayNode()
self.highlightNode = ASDisplayNode()
self.titleNode = ASTextNode()
self.titleNode = ImmediateTextNode()
self.titleNode.displaysAsynchronously = false
self.titleNode.maximumNumberOfLines = 1
self.titleNode.truncationMode = .byTruncatingTail
self.titleNode.textAlignment = .center
let displaysTitleInsidePill: Bool
if case .none = icon {
@ -361,20 +363,27 @@ private final class ItemListRevealOptionNode: ASDisplayNode {
let bounds = CGRect(origin: CGPoint(), size: self.bounds.size)
transition.updateFrame(node: self.contentContainerNode, frame: bounds)
let titleSize = self.titleNode.measure(CGSize(width: metrics.titleWidth, height: CGFloat.greatestFiniteMagnitude))
let titleSize: CGSize
if self.titleNode.frame.isEmpty {
titleSize = self.titleNode.updateLayout(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude))
} else {
titleSize = self.titleNode.cachedLayout?.size ?? CGSize()
}
let pillSize = metrics.shapeSize
let shapeY: CGFloat
if self.displaysTitleInsidePill {
shapeY = floor((bounds.height - pillSize.height) / 2.0)
shapeY = floorToScreenPixels((bounds.height - pillSize.height) / 2.0)
} else {
shapeY = floor((bounds.height - metrics.contentHeight) / 2.0)
let contentHeight = pillSize.height + optionTitleSpacing + titleSize.height
shapeY = floorToScreenPixels((bounds.height - contentHeight) / 2.0)
}
let shapeFrameX: CGFloat
if isStretched {
shapeFrameX = isLeft ? 0.0 : bounds.width - pillSize.width
} else {
shapeFrameX = floor((metrics.slotWidth - pillSize.width) / 2.0)
shapeFrameX = floorToScreenPixels((metrics.slotWidth - pillSize.width) / 2.0)
}
let shapeFrame = CGRect(origin: CGPoint(x: shapeFrameX, y: shapeY), size: pillSize)
let backgroundFrame: CGRect
@ -424,10 +433,11 @@ private final class ItemListRevealOptionNode: ASDisplayNode {
var imageSize = CGSize(width: animationNode.size.width * self.animationScale, height: animationNode.size.height * self.animationScale)
let imageMaxSide = max(imageSize.width, imageSize.height)
if imageMaxSide > metrics.iconMaxSide {
let imageScale = metrics.iconMaxSide / imageMaxSide * 1.4
let imageScale = metrics.iconMaxSide / imageMaxSide * 1.5
imageSize = CGSize(width: floorToScreenPixels(imageSize.width * imageScale), height: floorToScreenPixels(imageSize.height * imageScale))
}
let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(iconCenterX - imageSize.width / 2.0), y: floorToScreenPixels(iconCenterY - imageSize.height / 2.0) + 6.0 + self.animationNodeOffset), size: imageSize)
let scaleFraction = imageSize.height / 56.0
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
@ -493,7 +503,6 @@ private final class ItemListRevealOptionNode: ASDisplayNode {
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
let metrics = ItemListRevealOptionLayoutMetrics.metrics(for: constrainedSize.height, hasVisualIcons: !self.displaysTitleInsidePill)
let _ = self.titleNode.measure(CGSize(width: metrics.titleWidth, height: CGFloat.greatestFiniteMagnitude))
return CGSize(width: metrics.slotWidth, height: constrainedSize.height)
}
}
@ -510,6 +519,7 @@ public final class ItemListRevealOptionsNode: ASDisplayNode {
private var optionNodes: [ItemListRevealOptionNode] = []
private var revealOffset: CGFloat = 0.0
private var sideInset: CGFloat = 0.0
private var currentMetrics: (containerSize: CGSize, metrics: ItemListRevealOptionLayoutMetrics)?
public init(optionSelected: @escaping (ItemListRevealOption) -> Void, tapticAction: @escaping () -> Void) {
self.optionSelected = optionSelected
@ -564,20 +574,27 @@ public final class ItemListRevealOptionsNode: ASDisplayNode {
self.optionsContainerNode.addSubnode(node)
}
}
self.currentMetrics = nil
self.invalidateCalculatedLayout()
}
}
private func layoutMetrics(for height: CGFloat) -> ItemListRevealOptionLayoutMetrics {
let metrics = ItemListRevealOptionLayoutMetrics.metrics(for: height, hasVisualIcons: self.options.contains(where: { $0.icon.hasVisualIcon }))
private func layoutMetrics(for containerSize: CGSize) -> ItemListRevealOptionLayoutMetrics {
if let currentMetrics = self.currentMetrics, currentMetrics.containerSize == containerSize {
return currentMetrics.metrics
}
let metrics = ItemListRevealOptionLayoutMetrics.metrics(for: containerSize.height, hasVisualIcons: self.options.contains(where: { $0.icon.hasVisualIcon }))
let maxTitleWidth = self.optionNodes.reduce(0.0) { result, node in
return max(result, node.titleWidthForGroupPillSizing)
}
return metrics.withGroupTitleWidth(maxTitleWidth)
let updatedMetrics = metrics.withGroupTitleWidth(maxTitleWidth)
self.currentMetrics = (containerSize, updatedMetrics)
return updatedMetrics
}
override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
let metrics = self.layoutMetrics(for: constrainedSize.height)
let metrics = self.layoutMetrics(for: constrainedSize)
for node in self.optionNodes {
let _ = node.measure(constrainedSize)
}
@ -595,7 +612,7 @@ public final class ItemListRevealOptionsNode: ASDisplayNode {
if size.width.isLessThanOrEqualTo(0.0) || self.optionNodes.isEmpty {
return
}
let metrics = self.layoutMetrics(for: size.height)
let metrics = self.layoutMetrics(for: size)
let revealedDistance = abs(self.revealOffset)
let boundedRevealedDistance = min(revealedDistance, size.width)
let overswipeDistance = max(0.0, revealedDistance - size.width)

View file

@ -506,7 +506,11 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: layoutSize.width - params.rightInset - bottomStripeInset - separatorRightInset, height: separatorHeight)))
}
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: topInset + 1.0), size: titleLayout.size)
var titleOriginY = floorToScreenPixels((contentSize.height - titleLayout.size.height) / 2.0) + 1.0
if textLayoutAndApply != nil {
titleOriginY = topInset + 1.0
}
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: titleOriginY), size: titleLayout.size)
transition.updatePosition(node: strongSelf.titleNode, position: titleFrame.origin)
strongSelf.titleNode.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)

View file

@ -56,6 +56,7 @@
- (void)editorTransitionIn;
- (void)editorTransitionOut;
- (bool)canBeginEditingCaption;
- (void)beginEditingCaption;
- (void)setupGifEditing;

View file

@ -39,4 +39,7 @@
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
- (bool)canBeginEditingCaption;
- (void)beginEditingCaption;
@end

View file

@ -593,6 +593,10 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber *
}
- (bool)canBeginEditingCaption {
return _hasCaptions && _captionMixin != nil && _captionMixin.inputPanel != nil && !_captionMixin.inputPanelView.hidden && !_captionMixin.editing;
}
- (void)beginEditingCaption {
[_captionMixin activateInput];
}

View file

@ -44,6 +44,20 @@ static void adjustFrameRate(CAAnimation *animation) {
}
}
static bool TGModernGalleryViewHasFirstResponder(UIView *view) {
if (view.isFirstResponder) {
return true;
}
for (UIView *subview in view.subviews) {
if (TGModernGalleryViewHasFirstResponder(subview)) {
return true;
}
}
return false;
}
@interface TGModernGalleryController () <UIScrollViewDelegate, TGModernGalleryScrollViewDelegate, TGModernGalleryItemViewDelegate, TGKeyCommandResponder>
{
NSMutableDictionary *_reusableItemViewsByIdentifier;
@ -1759,6 +1773,28 @@ static CGFloat transformRotation(CGAffineTransform transform)
}
}
- (bool)_canBeginEditingCaptionFromKeyCommand
{
UIView<TGModernGalleryInterfaceView> *interfaceView = _view.interfaceView;
if (interfaceView == nil) {
return false;
}
if (![interfaceView respondsToSelector:@selector(beginEditingCaption)]) {
return false;
}
if ([interfaceView respondsToSelector:@selector(canBeginEditingCaption)] && ![interfaceView canBeginEditingCaption]) {
return false;
}
if (TGModernGalleryViewHasFirstResponder(_view)) {
return false;
}
return true;
}
- (void)processKeyCommand:(UIKeyCommand *)keyCommand
{
if ([keyCommand.input isEqualToString:UIKeyInputLeftArrow])
@ -1777,17 +1813,27 @@ static CGFloat transformRotation(CGAffineTransform transform)
{
_view.transitionOut(0.0f);
}
else if ([keyCommand.input isEqualToString:@"\r"])
{
if ([self _canBeginEditingCaptionFromKeyCommand])
[_view.interfaceView beginEditingCaption];
}
}
- (NSArray *)availableKeyCommands
{
return @
NSMutableArray *commands = [[NSMutableArray alloc] initWithArray:@
[
[TGKeyCommand keyCommandWithTitle:nil input:UIKeyInputLeftArrow modifierFlags:0],
[TGKeyCommand keyCommandWithTitle:nil input:UIKeyInputRightArrow modifierFlags:0],
[TGKeyCommand keyCommandWithTitle:nil input:UIKeyInputEscape modifierFlags:0],
[TGKeyCommand keyCommandWithTitle:nil input:@"\t" modifierFlags:0]
];
]];
if ([self _canBeginEditingCaptionFromKeyCommand])
[commands addObject:[TGKeyCommand keyCommandWithTitle:nil input:@"\r" modifierFlags:0]];
return commands;
}
- (bool)isExclusive

View file

@ -762,3 +762,50 @@ open class LegacyController: ViewController, PresentableController {
}
}
}
extension LegacyController: KeyShortcutResponder {
public var keyShortcuts: [KeyShortcut] {
guard TGKeyCommandController.keyCommandsSupported(), let legacyController = self.legacyController as? TGViewController else {
return []
}
var shortcuts: [KeyShortcut] = []
var hasExclusiveResponder = false
legacyController.enumerateChildViewControllersRecursively { viewController in
if hasExclusiveResponder {
return
}
guard let viewController = viewController, let responder = viewController as? TGKeyCommandResponder else {
return
}
if responder.isExclusive?() == true {
hasExclusiveResponder = true
shortcuts.removeAll()
}
guard let commands = responder.availableKeyCommands() as? [TGKeyCommand] else {
return
}
for command in commands {
let title = command.title ?? ""
let input = command.input ?? ""
let modifiers = command.modifierFlags
let shortcut = KeyShortcut(title: title, input: input, modifiers: modifiers, action: { [weak viewController] in
guard let responder = viewController as? TGKeyCommandResponder else {
return
}
let keyCommand = UIKeyCommand(input: input, modifierFlags: modifiers, action: #selector(TGKeyCommandResponder.processKeyCommand(_:)))
responder.processKeyCommand(keyCommand)
})
shortcuts.removeAll(where: { $0 == shortcut })
shortcuts.append(shortcut)
}
}
return shortcuts
}
}

View file

@ -471,7 +471,7 @@ private final class MediaPickerPhotoToolbarComponent: Component {
var doneWidth: CGFloat = toolbarButtonSide
switch component.doneButtonType {
case TGPhotoEditorDoneButtonSend:
doneIconName = "Chat/Input/Text/SendIcon"
doneIconName = "Media Editor/Send"
doneWidth = 46.0
case TGPhotoEditorDoneButtonSchedule:
doneIconName = "Chat/Input/ScheduleIcon"

View file

@ -1179,9 +1179,12 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
guardBotId = currentGuardBotId
approveMembersInfo += " " + presentationData.strings.Group_Setup_ApproveNewMembersManagedBy("[@\(guardBotUsername)](guardbot)").string
}
entries.append(.approveMembers(presentationData.theme, approveMembersTitle, approveMembers))
entries.append(.approveMembersInfo(presentationData.theme, approveMembersInfo, guardBotId))
if !isGroup && selectedType == .publicChannel {
} else {
entries.append(.approveMembers(presentationData.theme, approveMembersTitle, approveMembers))
entries.append(.approveMembersInfo(presentationData.theme, approveMembersInfo, guardBotId))
}
entries.append(.forwardingHeader(presentationData.theme, isGroup ? presentationData.strings.Group_Setup_ForwardingGroupTitle.uppercased() : presentationData.strings.Group_Setup_ForwardingChannelTitle.uppercased()))
entries.append(.forwardingDisabled(presentationData.theme, presentationData.strings.Group_Setup_ForwardingDisabled, !forwardingEnabled))

View file

@ -178,6 +178,11 @@ public func archiveSettingsController(context: AccountContext) -> ViewController
)
|> deliverOnMainQueue
|> map { presentationData, settings, appConfiguration, accountPeer -> (ItemListControllerState, (ItemListNodeState, Any)) in
var presentationData = presentationData
let updatedTheme = presentationData.theme.withModalBlocksBackground()
presentationData = presentationData.withUpdated(theme: updatedTheme)
let isPremium = accountPeer?.isPremium ?? false
let isPremiumDisabled = PremiumConfiguration.with(appConfiguration: appConfiguration).isPremiumDisabled

View file

@ -292,7 +292,7 @@ public func logoutOptionsController(context: AccountContext, navigationControlle
navigationController?.pushViewController(value, animated: false)
}
presentControllerImpl = { [weak controller] value, arguments in
controller?.present(value, in: .window(.root), with: arguments ?? ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
controller?.present(value, in: .window(.root), with: arguments)
}
replaceTopControllerImpl = { [weak navigationController] c in
navigationController?.replaceTopController(c, animated: true)

View file

@ -29,6 +29,7 @@ swift_library(
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
"//submodules/TelegramUI/Components/Ads/AdsReportScreen",
"//submodules/TelegramUI/Components/LottieComponent",
],
visibility = [
"//visibility:public",

View file

@ -19,6 +19,7 @@ import UndoUI
import GlassBarButtonComponent
import ButtonComponent
import AdsReportScreen
import LottieComponent
private let moreTag = GenericComponentViewTag()
@ -135,7 +136,7 @@ private final class SheetContent: CombinedComponent {
context.add(iconBackground.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + iconBackground.size.height / 2.0)))
let icon = icon.update(
component: BundleIconComponent(name: "Ads/AdsLogo", tintColor: theme.list.itemCheckColors.foregroundColor),
component: BundleIconComponent(name: "Ads/AdsLogo", tintColor: .white),
availableSize: CGSize(width: 90.0, height: 90.0),
transition: .immediate
)
@ -523,6 +524,8 @@ private final class AdsInfoSheetComponent: CombinedComponent {
let sheet = Child(ResizableSheetComponent<(EnvironmentType)>.self)
let animateOut = StoredActionSlot(Action<Void>.self)
let moreButtonPlayOnce = ActionSlot<Void>()
return { context in
let environment = context.environment[EnvironmentType.self]
let controller = environment.controller
@ -569,14 +572,19 @@ private final class AdsInfoSheetComponent: CombinedComponent {
component: AnyComponentWithIdentity(
id: "more",
component: AnyComponent(
BundleIconComponent(
name: "Chat/Context Menu/More",
tintColor: theme.chat.inputPanel.panelControlColor
LottieComponent(
content: LottieComponent.AppBundleContent(
name: "anim_morewide"
),
color: theme.chat.inputPanel.panelControlColor,
size: CGSize(width: 34.0, height: 34.0),
playOnce: moreButtonPlayOnce
)
)
),
action: { _ in
(controller() as? AdsInfoScreen)?.infoPressed()
moreButtonPlayOnce.invoke(Void())
},
tag: moreTag
)

View file

@ -116,7 +116,11 @@ public final class NavigationButtonComponent: Component {
switch component.content {
case let .text(title, isBold):
textString = NSAttributedString(string: title, font: isBold ? Font.bold(17.0) : Font.medium(17.0), textColor: theme.chat.inputPanel.panelControlColor)
if title == "___done" {
imageName = "Navigation/Done"
} else {
textString = NSAttributedString(string: title, font: isBold ? Font.bold(17.0) : Font.medium(17.0), textColor: theme.chat.inputPanel.panelControlColor)
}
case .more:
isMore = true
case let .icon(imageNameValue):

View file

@ -1764,8 +1764,7 @@ private final class ChatThemeSheetContentComponent: Component {
style: .glass,
color: destructiveColor.withMultipliedAlpha(0.1),
foreground: destructiveColor,
pressedColor: destructiveColor.withMultipliedAlpha(0.2),
cornerRadius: 22.0
pressedColor: destructiveColor.withMultipliedAlpha(0.2)
),
content: AnyComponentWithIdentity(
id: AnyHashable("resetWallpaper"),

View file

@ -632,7 +632,7 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
}
self.buttonsContainerNode = SparseNode()
self.buttonsContainerNode.clipsToBounds = true
//self.buttonsContainerNode.clipsToBounds = true
self.backButtonNodeImpl.color = self.presentationData.theme.buttonColor
self.backButtonNodeImpl.disabledColor = self.presentationData.theme.disabledButtonColor

View file

@ -19,6 +19,7 @@ import ContextUI
import AvatarNode
import PlainButtonComponent
import ToastComponent
import TextFormat
private final class JoinAffiliateProgramScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -1220,13 +1221,28 @@ private final class JoinAffiliateProgramScreenComponent: Component {
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemSecondaryTextColor),
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.itemSecondaryTextColor),
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemAccentColor),
linkAttribute: { url in
return ("URL", url)
linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
}
)
),
horizontalAlignment: .center,
maximumNumberOfLines: 0
maximumNumberOfLines: 0,
highlightColor: environment.theme.list.itemAccentColor.withAlphaComponent(0.2),
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
} else {
return nil
}
},
tapAction: { [weak self] attributes, _ in
guard let environment = self?.environment, let controller = environment.controller(), let navigationController = controller.navigationController as? NavigationController else {
return
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: environment.strings.Stars_Purchase_Terms_URL, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {})
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0)

View file

@ -142,6 +142,7 @@ swift_library(
"//submodules/TelegramUI/Components/Settings/PeerNameColorItem",
"//submodules/TelegramUI/Components/PlainButtonComponent",
"//submodules/TelegramUI/Components/TextLoadingEffect",
"//submodules/TelegramUI/Components/TextProcessingScreen",
"//submodules/TelegramUI/Components/Settings/BirthdayPickerScreen",
"//submodules/TelegramUI/Components/Settings/PeerSelectionScreen",
"//submodules/TelegramUI/Components/ButtonComponent",

View file

@ -8,6 +8,8 @@ import AsyncDisplayKit
import TelegramUIPreferences
import ContextUI
import TranslateUI
import TextProcessingScreen
import Pasteboard
import UndoUI
extension PeerInfoScreenNode {
@ -105,16 +107,24 @@ extension PeerInfoScreenNode {
guard let self else {
return
}
let controller = TranslateScreen(context: self.context, text: bioText, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages)
controller.pushController = { [weak self] c in
(self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true
self?.controller?.push(c)
Task { @MainActor [weak self] in
guard let self, let parentController = self.controller else {
return
}
let presentationData = self.presentationData
let controller = await TextProcessingScreen(
context: self.context,
mode: .translate(fromLanguage: language, applyResult: nil),
inputText: TextWithEntities(text: bioText, entities: []),
copyResult: { [weak parentController] text in
storeMessageTextInPasteboard(text.text, entities: text.entities)
parentController?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
},
translateChat: nil
)
parentController.present(controller, in: .window(.root))
}
controller.presentController = { [weak self] c in
self?.controller?.present(c, in: .window(.root))
}
self.controller?.present(controller, in: .window(.root))
}
})))
}

View file

@ -7,6 +7,8 @@ import TelegramCore
import AsyncDisplayKit
import UndoUI
import TranslateUI
import TextProcessingScreen
import Pasteboard
import TelegramStringFormatting
import TelegramUIPreferences
@ -70,16 +72,22 @@ extension PeerInfoScreenNode {
let (canTranslate, language) = canTranslateText(context: context, text: text, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: false, ignoredLanguages: translationSettings.ignoredLanguages)
if canTranslate {
actions.append(ContextMenuAction(content: .text(title: presentationData.strings.Conversation_ContextMenuTranslate, accessibilityLabel: presentationData.strings.Conversation_ContextMenuTranslate), action: { [weak self] in
let controller = TranslateScreen(context: context, text: text, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages)
controller.pushController = { [weak self] c in
(self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true
self?.controller?.push(c)
Task { @MainActor [weak self] in
guard let self, let parentController = self.controller else {
return
}
let controller = await TextProcessingScreen(
context: context,
mode: .translate(fromLanguage: language, applyResult: nil),
inputText: TextWithEntities(text: text, entities: []),
copyResult: { [weak parentController] text in
storeMessageTextInPasteboard(text.text, entities: text.entities)
parentController?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
},
translateChat: nil
)
parentController.present(controller, in: .window(.root))
}
controller.presentController = { [weak self] c in
self?.controller?.present(c, in: .window(.root))
}
self?.controller?.present(controller, in: .window(.root))
}))
}

View file

@ -186,6 +186,7 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent {
let component = context.component
let scrollEnvironment = context.environment[ScrollChildEnvironment.self].value
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
let controller = environment.controller
let state = context.state
state.products = component.products
@ -475,7 +476,10 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent {
}
},
tapAction: { attributes, _ in
component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: strings.Stars_Purchase_Terms_URL, forceExternal: false, presentationData: presentationData, navigationController: nil, dismissInput: {})
guard let controller = controller(), let navigationController = controller.navigationController as? NavigationController else {
return
}
component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: strings.Stars_Purchase_Terms_URL, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {})
}
),
environment: {},

View file

@ -79,6 +79,7 @@ swift_library(
"//submodules/TextSelectionNode",
"//submodules/Pasteboard",
"//submodules/Speak",
"//submodules/TelegramUI/Components/TextProcessingScreen",
"//submodules/TranslateUI",
"//submodules/TelegramNotices",
"//submodules/MediaPlayer:UniversalMediaPlayer",

View file

@ -39,6 +39,8 @@ import ChatScheduleTimeController
import StoryStealthModeSheetScreen
import Speak
import TranslateUI
import TextProcessingScreen
import Pasteboard
import TelegramNotices
import ObjectiveC
import LocationUI
@ -1543,32 +1545,39 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) {
let _ = ApplicationSpecificNotice.incrementTranslationSuggestion(accountManager: component.context.sharedContext.accountManager, timestamp: Int32(Date().timeIntervalSince1970)).start()
let translateController = TranslateScreen(context: component.context, forceTheme: defaultDarkPresentationTheme, text: text, entities: entities, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages)
translateController.pushController = { [weak view] c in
guard let view, let component = view.component else {
Task { @MainActor [weak self, weak view] in
guard let self, let view, let component = view.component else {
return
}
component.controller()?.push(c)
}
translateController.presentController = { [weak view] c in
guard let view, let component = view.component else {
return
}
component.controller()?.present(c, in: .window(.root))
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let translateController = await TextProcessingScreen(
context: component.context,
theme: defaultDarkPresentationTheme,
mode: .translate(fromLanguage: language, applyResult: nil),
inputText: TextWithEntities(text: text, entities: entities),
copyResult: { [weak view] text in
guard let component = view?.component else {
return
}
storeMessageTextInPasteboard(text.text, entities: text.entities)
component.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
},
translateChat: nil
)
self.actionSheet = translateController
view.updateIsProgressPaused()
translateController.wasDismissed = { [weak self, weak view] in
guard let self, let view else {
return
}
self.actionSheet = nil
self.actionSheet = translateController
view.updateIsProgressPaused()
}
component.controller()?.present(translateController, in: .window(.root))
translateController.wasDismissed = { [weak self, weak view] in
guard let self, let view else {
return
}
self.actionSheet = nil
view.updateIsProgressPaused()
}
component.controller()?.present(translateController, in: .window(.root))
}
})
}

View file

@ -791,7 +791,11 @@ public final class TabBarComponent: Component {
var itemFrame = CGRect(origin: CGPoint(x: nextItemX, y: floor((tabsSize.height - itemSize.height) * 0.5)), size: itemSize)
nextItemX += itemSize.width
if isItemSelected {
selectionFrame = itemFrame
if itemFrame.size.width < itemFrame.size.height {
selectionFrame = itemFrame.insetBy(dx: floor((itemFrame.size.height * 1.2 - itemFrame.size.width) * -0.5), dy: 0.0)
} else {
selectionFrame = itemFrame
}
}
if let itemComponentView = itemView.view as? ItemComponent.View, let selectedItemComponentView = selectedItemView.view as? ItemComponent.View {

View file

@ -471,6 +471,7 @@ final class TextProcessingContentComponent: Component {
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.2)
let environment = environment[ViewControllerComponentContainer.Environment.self].value
let theme = environment.theme.withModalBlocksBackground()
let isFirstTime = self.component == nil
@ -516,8 +517,8 @@ final class TextProcessingContentComponent: Component {
content: .animation(
content: .file(file: previewIconFile),
size: previewIconSize,
placeholderColor: environment.theme.list.mediaPlaceholderColor,
themeColor: environment.theme.list.itemPrimaryTextColor,
placeholderColor: theme.list.mediaPlaceholderColor,
themeColor: theme.list.itemPrimaryTextColor,
loopMode: .count(1)
),
isVisibleForAnimations: true,
@ -557,7 +558,7 @@ final class TextProcessingContentComponent: Component {
let titleSize = previewTitle.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: style.title, font: Font.bold(30.0), textColor: environment.theme.list.itemPrimaryTextColor))
text: .plain(NSAttributedString(string: style.title, font: Font.bold(30.0), textColor: theme.list.itemPrimaryTextColor))
)),
environment: {},
containerSize: CGSize(width: availableSize.width, height: 100.0)
@ -576,7 +577,7 @@ final class TextProcessingContentComponent: Component {
let descriptionSize = previewDescription.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: environment.strings.TextProcessing_StylePreview_Subtitle, font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor)),
text: .plain(NSAttributedString(string: environment.strings.TextProcessing_StylePreview_Subtitle, font: Font.regular(15.0), textColor: theme.list.itemPrimaryTextColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 0,
lineSpacing: 0.12
@ -683,7 +684,7 @@ final class TextProcessingContentComponent: Component {
let modeTabsSize = self.modeTabs.update(
transition: transition,
component: AnyComponent(TabBarComponent(
theme: environment.theme,
theme: theme,
tintSelectedItem: false,
isLiftedStateEnabled: false,
strings: environment.strings,
@ -725,7 +726,7 @@ final class TextProcessingContentComponent: Component {
contentExternalState = self.translateState
contentComponent = AnyComponent(TextProcessingTranslateContentComponent(
context: component.context,
theme: environment.theme,
theme: theme,
strings: environment.strings,
styles: component.styles,
externalState: self.translateState,
@ -771,7 +772,7 @@ final class TextProcessingContentComponent: Component {
contentExternalState = self.stylizeState
contentComponent = AnyComponent(TextProcessingTranslateContentComponent(
context: component.context,
theme: environment.theme,
theme: theme,
strings: environment.strings,
styles: component.styles,
externalState: self.stylizeState,
@ -892,7 +893,7 @@ final class TextProcessingContentComponent: Component {
contentExternalState = self.fixState
contentComponent = AnyComponent(TextProcessingTranslateContentComponent(
context: component.context,
theme: environment.theme,
theme: theme,
strings: environment.strings,
styles: component.styles,
externalState: self.fixState,
@ -997,7 +998,7 @@ final class TextProcessingContentComponent: Component {
if self.currentContentBackground.image == nil {
self.currentContentBackground.image = generateStretchableFilledCircleImage(diameter: 60.0, color: .white)?.withRenderingMode(.alwaysTemplate)
}
self.currentContentBackground.tintColor = environment.theme.list.itemBlocksBackgroundColor
self.currentContentBackground.tintColor = theme.list.itemBlocksBackgroundColor
transition.setFrame(view: self.currentContentBackground, frame: contentFrame)
contentHeight += contentSize.height
@ -1057,19 +1058,19 @@ final class TextProcessingContentComponent: Component {
if case .translate = component.mode {
if let copyTranslation = component.copyCurrentResult {
actionsSectionItems.append(AnyComponentWithIdentity(id: "copy", component: AnyComponent(ListActionItemComponent(
theme: environment.theme,
theme: theme,
style: .glass,
title: AnyComponent(
MultilineTextComponent(
text: .plain(NSAttributedString(
string: environment.strings.Translate_CopyTranslation,
font: Font.regular(17.0),
textColor: environment.theme.list.itemAccentColor
textColor: theme.list.itemAccentColor
)),
maximumNumberOfLines: 1
)
),
leftIcon: .custom(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Copy", tintColor: environment.theme.list.itemAccentColor))), false),
leftIcon: .custom(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Copy", tintColor: theme.list.itemAccentColor))), false),
action: { _ in
copyTranslation()
}
@ -1077,19 +1078,19 @@ final class TextProcessingContentComponent: Component {
}
if let translateChat = component.translateChat {
actionsSectionItems.append(AnyComponentWithIdentity(id: "translate", component: AnyComponent(ListActionItemComponent(
theme: environment.theme,
theme: theme,
style: .glass,
title: AnyComponent(
MultilineTextComponent(
text: .plain(NSAttributedString(
string: environment.strings.Localization_TranslateEntireChat,
font: Font.regular(17.0),
textColor: environment.theme.list.itemAccentColor
textColor: theme.list.itemAccentColor
)),
maximumNumberOfLines: 1
)
),
leftIcon: .custom(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Translate", tintColor: environment.theme.list.itemAccentColor))), false),
leftIcon: .custom(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Translate", tintColor: theme.list.itemAccentColor))), false),
action: { [weak self] _ in
guard let self, let language = self.translateState.result?.language else {
return
@ -1105,7 +1106,7 @@ final class TextProcessingContentComponent: Component {
let actionsSectionSize = self.actionsSection.update(
transition: transition,
component: AnyComponent(ListSectionComponent(
theme: environment.theme,
theme: theme,
style: .glass,
header: nil,
footer: nil,
@ -1368,7 +1369,7 @@ private final class TextProcessingSheetComponent: Component {
let environmentValue = environment[ViewControllerComponentContainer.Environment.self].value
self.environment = environmentValue
let controller = environmentValue.controller
let theme = environmentValue.theme
let theme = environmentValue.theme.withModalBlocksBackground()
let dismiss: (Bool) -> Void = { [weak self] animated in
if animated {
@ -1427,11 +1428,23 @@ private final class TextProcessingSheetComponent: Component {
}
dismiss(true)
}
case .translate:
actionButtonTitle = environmentValue.strings.TextProcessing_ActionClose
isMainActionEnabled = true
performMainAction = {
dismiss(true)
case let .translate(_, applyResult):
if let applyResult {
actionButtonTitle = environmentValue.strings.TextProcessing_ActionApply
isMainActionEnabled = !self.contentExternalState.isProcessing && self.contentExternalState.result != nil
performMainAction = { [weak self] in
guard let self, let result = self.contentExternalState.result else {
return
}
applyResult(result)
dismiss(true)
}
} else {
actionButtonTitle = environmentValue.strings.TextProcessing_ActionClose
isMainActionEnabled = true
performMainAction = {
dismiss(true)
}
}
case let .preview(style, _, _, isAlreadyAdded, added):
actionButtonTitle = isAlreadyAdded ? environmentValue.strings.TextProcessing_StyleMenu_ButtonClose : environmentValue.strings.TextProcessing_StyleMenu_ButtonAdd

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "attachlogo.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -59,6 +59,7 @@ import Markdown
import TelegramPermissionsUI
import Speak
import TranslateUI
import TextProcessingScreen
import UniversalMediaPlayer
import WallpaperBackgroundNode
import ChatListUI
@ -4625,27 +4626,56 @@ extension ChatControllerImpl {
return
}
let (_, language) = canTranslateText(context: context, text: text.string, showTranslate: true, ignoredLanguages: nil)
let entities = generateChatInputTextEntities(text)
presentTranslateScreen(
context: self.context,
text: text.string,
entities: entities,
canCopy: true,
fromLanguage: language,
replaceText: { text, entities in
replace(chatInputStateStringWithAppliedEntities(text, entities: entities))
},
pushController: { [weak self] c in
self?.push(c)
},
presentController: { [weak self] c in
self?.present(c, in: .window(.root))
},
display: { [weak self] c in
self?.push(c)
let translationConfiguration = TranslationConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
var useSystemTranslation = false
switch translationConfiguration.manual {
case .system:
if #available(iOS 18.0, *) {
useSystemTranslation = true
}
)
default:
break
}
if useSystemTranslation {
presentTranslateScreen(
context: self.context,
text: text.string,
entities: entities,
canCopy: true,
fromLanguage: language,
replaceText: { text, entities in
replace(chatInputStateStringWithAppliedEntities(text, entities: entities))
},
pushController: { [weak self] c in
self?.push(c)
},
presentController: { [weak self] c in
self?.present(c, in: .window(.root))
},
display: { [weak self] c in
self?.push(c)
}
)
} else {
Task { @MainActor [weak self] in
guard let self else {
return
}
self.push(await TextProcessingScreen(
context: self.context,
mode: .translate(fromLanguage: language, applyResult: { text in
replace(chatInputStateStringWithAppliedEntities(text.text, entities: text.entities))
}),
inputText: TextWithEntities(text: text.string, entities: entities),
copyResult: nil,
translateChat: nil
))
}
}
}, sendEmoji: { [weak self] text, attribute, immediately in
guard let self else {
return

View file

@ -4328,7 +4328,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} else {
self.push(await TextProcessingScreen(
context: self.context,
mode: .translate(fromLanguage: language),
mode: .translate(fromLanguage: language, applyResult: nil),
inputText: TextWithEntities(text: text.string, entities: entities ?? []),
copyResult: canCopy ? { [weak self] text in
guard let self else {

View file

@ -87,8 +87,14 @@ final class ChatMessageContextExtractedContentSource: ContextExtractedContentSou
return
}
if item.content.contains(where: { $0.0.stableId == self.message.stableId }), let contentNode = itemNode.getMessageContextSourceNode(stableId: self.selectAll ? nil : self.message.stableId) {
result = ContextControllerTakeViewInfo(containingItem: .node(contentNode), contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil), sourceTransitionSurface: chatNode.ensureContextTransitionContainer())
let sourceTransitionSurface: UIView?
if self.snapshot {
sourceTransitionSurface = nil
} else {
sourceTransitionSurface = chatNode.ensureContextTransitionContainer()
}
result = ContextControllerTakeViewInfo(containingItem: .node(contentNode), contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil), sourceTransitionSurface: sourceTransitionSurface)
if self.snapshot, let snapshotView = contentNode.contentNode.view.snapshotContentTree(unhide: false, keepPortals: true, keepTransform: true) {
contentNode.view.superview?.addSubview(snapshotView)
self.snapshotView = snapshotView
@ -112,7 +118,13 @@ final class ChatMessageContextExtractedContentSource: ContextExtractedContentSou
return
}
if item.content.contains(where: { $0.0.stableId == self.message.stableId }) {
result = ContextControllerPutBackViewInfo(contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil), sourceTransitionSurface: chatNode.ensureContextTransitionContainer())
let sourceTransitionSurface: UIView?
if self.snapshot {
sourceTransitionSurface = nil
} else {
sourceTransitionSurface = chatNode.ensureContextTransitionContainer()
}
result = ContextControllerPutBackViewInfo(contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil), sourceTransitionSurface: sourceTransitionSurface)
}
}

View file

@ -1,197 +0,0 @@
import Foundation
import UIKit
import Display
import SwiftSignalKit
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import ItemListUI
import PresentationDataUtils
import TelegramStringFormatting
import AccountContext
private final class LanguageSelectionControllerArguments {
let context: AccountContext
let updateLanguageSelected: (String) -> Void
init(context: AccountContext, updateLanguageSelected: @escaping (String) -> Void) {
self.context = context
self.updateLanguageSelected = updateLanguageSelected
}
}
private enum LanguageSelectionControllerSection: Int32 {
case languages
}
private enum LanguageSelectionControllerEntry: ItemListNodeEntry {
case language(Int32, PresentationTheme, String, String, Bool, String)
var section: ItemListSectionId {
switch self {
case .language:
return LanguageSelectionControllerSection.languages.rawValue
}
}
var stableId: Int32 {
switch self {
case let .language(index, _, _, _, _, _):
return index
}
}
static func ==(lhs: LanguageSelectionControllerEntry, rhs: LanguageSelectionControllerEntry) -> Bool {
switch lhs {
case let .language(lhsIndex, lhsTheme, lhsTitle, lhsSubtitle, lhsValue, lhsCode):
if case let .language(rhsIndex, rhsTheme, rhsTitle, rhsSubtitle, rhsValue, rhsCode) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsValue == rhsValue, lhsCode == rhsCode {
return true
} else {
return false
}
}
}
static func <(lhs: LanguageSelectionControllerEntry, rhs: LanguageSelectionControllerEntry) -> Bool {
return lhs.stableId < rhs.stableId
}
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
let arguments = arguments as! LanguageSelectionControllerArguments
switch self {
case let .language(_, _, title, subtitle, value, code):
return LocalizationListItem(presentationData: presentationData, id: code, title: title, subtitle: subtitle, checked: value, activity: false, loading: false, editing: LocalizationListItemEditing(editable: false, editing: false, revealed: false, reorderable: false), sectionId: self.section, alwaysPlain: false, action: {
arguments.updateLanguageSelected(code)
}, setItemWithRevealedOptions: { _, _ in }, removeItem: { _ in })
}
}
}
private func languageSelectionControllerEntries(theme: PresentationTheme, strings: PresentationStrings, selectedLanguage: String, languages: [(String, String, String)]) -> [LanguageSelectionControllerEntry] {
var entries: [LanguageSelectionControllerEntry] = []
var index: Int32 = 0
for (code, title, subtitle) in languages {
entries.append(.language(index, theme, title, subtitle, code == selectedLanguage, code))
index += 1
}
return entries
}
private struct LanguageSelectionControllerState: Equatable {
enum Section {
case original
case translation
}
var section: Section
var fromLanguage: String
var toLanguage: String
}
public func languageSelectionController(context: AccountContext, forceTheme: PresentationTheme? = nil, fromLanguage: String, toLanguage: String, completion: @escaping (String, String) -> Void) -> ViewController {
let statePromise = ValuePromise(LanguageSelectionControllerState(section: .translation, fromLanguage: fromLanguage, toLanguage: toLanguage), ignoreRepeated: true)
let stateValue = Atomic(value: LanguageSelectionControllerState(section: .translation, fromLanguage: fromLanguage, toLanguage: toLanguage))
let updateState: ((LanguageSelectionControllerState) -> LanguageSelectionControllerState) -> Void = { f in
statePromise.set(stateValue.modify { f($0) })
}
let actionsDisposable = DisposableSet()
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let interfaceLanguageCode = presentationData.strings.baseLanguageCode
var dismissImpl: (() -> Void)?
let arguments = LanguageSelectionControllerArguments(context: context, updateLanguageSelected: { code in
updateState { current in
var updated = current
switch updated.section {
case .original:
updated.fromLanguage = code
case .translation:
updated.toLanguage = code
}
return updated
}
})
let enLocale = Locale(identifier: "en")
var languages: [(String, String, String)] = []
var addedLanguages = Set<String>()
for code in popularTranslationLanguages {
if let title = enLocale.localizedString(forLanguageCode: code) {
let languageLocale = Locale(identifier: code)
let subtitle = languageLocale.localizedString(forLanguageCode: code) ?? title
let value = (code, title.capitalized, subtitle.capitalized)
if code == interfaceLanguageCode {
languages.insert(value, at: 0)
} else {
languages.append(value)
}
addedLanguages.insert(code)
}
}
for code in supportedTranslationLanguages {
if !addedLanguages.contains(code), let title = enLocale.localizedString(forLanguageCode: code) {
let languageLocale = Locale(identifier: code)
let subtitle = languageLocale.localizedString(forLanguageCode: code) ?? title
let value = (code, title.capitalized, subtitle.capitalized)
if code == interfaceLanguageCode {
languages.insert(value, at: 0)
} else {
languages.append(value)
}
}
}
let signal = combineLatest(queue: Queue.mainQueue(), context.sharedContext.presentationData, statePromise.get())
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
var presentationData = presentationData
if let forceTheme {
presentationData = presentationData.withUpdated(theme: forceTheme)
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .sectionControl([presentationData.strings.Translate_Languages_Original, presentationData.strings.Translate_Languages_Translation], 1), leftNavigationButton: ItemListNavigationButton(content: .none, style: .regular, enabled: false, action: {}), rightNavigationButton: ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: {
completion(state.fromLanguage, state.toLanguage)
dismissImpl?()
}), backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let selectedLanguage: String
switch state.section {
case.original:
selectedLanguage = state.fromLanguage
case .translation:
selectedLanguage = state.toLanguage
}
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: languageSelectionControllerEntries(theme: presentationData.theme, strings: presentationData.strings, selectedLanguage: selectedLanguage, languages: languages), style: .blocks, animateChanges: false)
return (controllerState, (listState, arguments))
}
|> afterDisposed {
actionsDisposable.dispose()
}
let controller = ItemListController(context: context, state: signal)
controller.titleControlValueChanged = { value in
updateState { current in
var updated = current
if value == 0 {
updated.section = .original
} else {
updated.section = .translation
}
return updated
}
}
controller.alwaysSynchronous = true
controller.navigationPresentation = .modal
dismissImpl = { [weak controller] in
controller?.dismiss(animated: true, completion: nil)
}
return controller
}

View file

@ -1,120 +0,0 @@
import Foundation
import UIKit
import ComponentFlow
import ManagedAnimationNode
enum PlayPauseIconNodeState: Equatable {
case play
case pause
}
private final class PlayPauseIconNode: ManagedAnimationNode {
private let duration: Double = 0.35
private var iconState: PlayPauseIconNodeState = .play
init() {
super.init(size: CGSize(width: 40.0, height: 40.0))
self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.01))
}
func enqueueState(_ state: PlayPauseIconNodeState, animated: Bool) {
guard self.iconState != state else {
return
}
let previousState = self.iconState
self.iconState = state
switch previousState {
case .pause:
switch state {
case .play:
if animated {
self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 41, endFrame: 83), duration: self.duration))
} else {
self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.01))
}
case .pause:
break
}
case .play:
switch state {
case .pause:
if animated {
self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 0, endFrame: 41), duration: self.duration))
} else {
self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 41, endFrame: 41), duration: 0.01))
}
case .play:
break
}
}
}
}
final class PlayPauseIconComponent: Component {
let state: PlayPauseIconNodeState
let tintColor: UIColor?
let size: CGSize
init(state: PlayPauseIconNodeState, tintColor: UIColor?, size: CGSize) {
self.state = state
self.tintColor = tintColor
self.size = size
}
static func ==(lhs: PlayPauseIconComponent, rhs: PlayPauseIconComponent) -> Bool {
if lhs.state != rhs.state {
return false
}
if lhs.tintColor != rhs.tintColor {
return false
}
if lhs.size != rhs.size {
return false
}
return true
}
final class View: UIView {
private var component: PlayPauseIconComponent?
private var animationNode: PlayPauseIconNode
override init(frame: CGRect) {
self.animationNode = PlayPauseIconNode()
super.init(frame: frame)
self.addSubview(self.animationNode.view)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: PlayPauseIconComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
if self.component?.state != component.state {
self.component = component
self.animationNode.enqueueState(component.state, animated: true)
}
self.animationNode.customColor = component.tintColor
let animationSize = component.size
let size = CGSize(width: min(animationSize.width, availableSize.width), height: min(animationSize.height, availableSize.height))
self.animationNode.view.frame = CGRect(origin: CGPoint(x: floor((size.width - animationSize.width) / 2.0), y: floor((size.height - animationSize.height) / 2.0)), size: animationSize)
return size
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition)
}
}

View file

@ -86,6 +86,7 @@ public var supportedTranslationLanguages = [
"fa",
"pl",
"pt",
"pt-BR",
"pa",
"ro",
"ru",
@ -132,7 +133,7 @@ public var popularTranslationLanguages = [
"it",
"ja",
"ko",
"pt",
"pt-BR",
"ru",
"es",
"uk"

View file

@ -1,184 +0,0 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import TelegramPresentationData
import BundleIconComponent
private final class TranslateButtonContentComponent: CombinedComponent {
let theme: PresentationTheme
let title: String
let icon: String
init(
theme: PresentationTheme,
title: String,
icon: String
) {
self.theme = theme
self.title = title
self.icon = icon
}
static func ==(lhs: TranslateButtonContentComponent, rhs: TranslateButtonContentComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.icon != rhs.icon {
return false
}
return true
}
static var body: Body {
let title = Child(Text.self)
let icon = Child(BundleIconComponent.self)
return { context in
let component = context.component
let icon = icon.update(
component: BundleIconComponent(
name: component.icon,
tintColor: component.theme.list.itemPrimaryTextColor
),
availableSize: CGSize(width: 30.0, height: 30.0),
transition: context.transition
)
let title = title.update(
component: Text(
text: component.title,
font: Font.regular(17.0),
color: component.theme.list.itemPrimaryTextColor
),
availableSize: context.availableSize,
transition: .immediate
)
let sideInset: CGFloat = 16.0
let textSideInset: CGFloat = 60.0
context.add(title
.position(CGPoint(x: textSideInset + title.size.width / 2.0, y: context.availableSize.height / 2.0))
)
context.add(icon
.position(CGPoint(x: sideInset + icon.size.width / 2.0, y: context.availableSize.height / 2.0))
)
return context.availableSize
}
}
}
final class TranslateButtonComponent: Component {
private let content: TranslateButtonContentComponent
private let theme: PresentationTheme
private let isEnabled: Bool
private let action: () -> Void
init(
theme: PresentationTheme,
title: String,
icon: String,
isEnabled: Bool,
action: @escaping () -> Void
) {
self.content = TranslateButtonContentComponent(theme: theme, title: title, icon: icon)
self.isEnabled = isEnabled
self.theme = theme
self.action = action
}
static func ==(lhs: TranslateButtonComponent, rhs: TranslateButtonComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.content !== rhs.content {
return false
}
if lhs.isEnabled != rhs.isEnabled {
return false
}
return true
}
final class View: HighlightTrackingButton {
private let backgroundView: UIView
private let centralContentView: ComponentHostView<Empty>
private var component: TranslateButtonComponent?
override init(frame: CGRect) {
self.backgroundView = UIView()
self.backgroundView.isUserInteractionEnabled = false
self.centralContentView = ComponentHostView()
self.centralContentView.isUserInteractionEnabled = false
super.init(frame: frame)
self.backgroundView.clipsToBounds = true
self.addSubview(self.backgroundView)
self.addSubview(self.centralContentView)
self.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self, let component = strongSelf.component {
if highlighted {
strongSelf.backgroundView.backgroundColor = component.theme.list.itemHighlightedBackgroundColor
} else {
UIView.animate(withDuration: 0.3, animations: {
strongSelf.backgroundView.backgroundColor = component.theme.list.itemBlocksBackgroundColor
})
}
}
}
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func pressed() {
if let component = self.component {
component.action()
}
}
public func update(component: TranslateButtonComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
self.component = component
self.backgroundView.backgroundColor = component.theme.list.itemBlocksBackgroundColor
self.backgroundView.layer.cornerRadius = 26.0
let _ = self.centralContentView.update(
transition: transition,
component: AnyComponent(component.content),
environment: {},
containerSize: availableSize
)
transition.setFrame(view: self.centralContentView, frame: CGRect(origin: CGPoint(), size: availableSize), completion: nil)
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: availableSize), completion: nil)
self.centralContentView.alpha = component.isEnabled ? 1.0 : 0.4
self.isUserInteractionEnabled = component.isEnabled
return availableSize
}
}
func makeView() -> View {
return View()
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition)
}
}

File diff suppressed because it is too large Load diff

View file

@ -3785,8 +3785,6 @@ public final class WebAppController: ViewController, AttachmentContainable {
if let ageBotUsername = self.context.currentAppConfiguration.with({ $0 }).data?["verify_age_bot_username"] as? String {
if self.botAddress == ageBotUsername {
return true
} else if self.botAddress == "mod_bot" {
return true
}
}
return false