diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index bd5b3a8537..860d6c897a 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -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) } diff --git a/submodules/BrowserUI/BUILD b/submodules/BrowserUI/BUILD index 8b9315a112..a240332122 100644 --- a/submodules/BrowserUI/BUILD +++ b/submodules/BrowserUI/BUILD @@ -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", diff --git a/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift b/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift index af5e88e55b..9cdb96f6be 100644 --- a/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift +++ b/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift @@ -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) })) } diff --git a/submodules/Camera/Sources/Camera.swift b/submodules/Camera/Sources/Camera.swift index e2353ce62b..b2fccc4bf1 100644 --- a/submodules/Camera/Sources/Camera.swift +++ b/submodules/Camera/Sources/Camera.swift @@ -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 diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 62ec39b3c2..4ca41f47df 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -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() } diff --git a/submodules/DrawingUI/Sources/DrawingScreen.swift b/submodules/DrawingUI/Sources/DrawingScreen.swift index aa578ffa70..419309c549 100644 --- a/submodules/DrawingUI/Sources/DrawingScreen.swift +++ b/submodules/DrawingUI/Sources/DrawingScreen.swift @@ -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) diff --git a/submodules/DrawingUI/Sources/ModeAndSizeComponent.swift b/submodules/DrawingUI/Sources/ModeAndSizeComponent.swift index 0872e00b73..eabf7ba305 100644 --- a/submodules/DrawingUI/Sources/ModeAndSizeComponent.swift +++ b/submodules/DrawingUI/Sources/ModeAndSizeComponent.swift @@ -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 } diff --git a/submodules/GalleryUI/BUILD b/submodules/GalleryUI/BUILD index 50b974a980..bb4781735b 100644 --- a/submodules/GalleryUI/BUILD +++ b/submodules/GalleryUI/BUILD @@ -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", diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index ccf2c4a8dd..021fbd21c0 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -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 diff --git a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift index 6e0913d2c5..337480c990 100644 --- a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift @@ -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)) } } }) diff --git a/submodules/InstantPageUI/BUILD b/submodules/InstantPageUI/BUILD index 0671d69c9a..186ab72b1c 100644 --- a/submodules/InstantPageUI/BUILD +++ b/submodules/InstantPageUI/BUILD @@ -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", diff --git a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift index 69f1f2b0ba..c003a10967 100644 --- a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift @@ -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) })) } diff --git a/submodules/ItemListUI/Sources/ItemListRevealOptionsNode.swift b/submodules/ItemListUI/Sources/ItemListRevealOptionsNode.swift index 545d22b3b1..c76abf5b89 100644 --- a/submodules/ItemListUI/Sources/ItemListRevealOptionsNode.swift +++ b/submodules/ItemListUI/Sources/ItemListRevealOptionsNode.swift @@ -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) diff --git a/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift b/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift index e9f93e82c2..3a05467b15 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift @@ -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) diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryInterfaceView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryInterfaceView.h index d0d054545a..da6e7831eb 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryInterfaceView.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryInterfaceView.h @@ -56,6 +56,7 @@ - (void)editorTransitionIn; - (void)editorTransitionOut; +- (bool)canBeginEditingCaption; - (void)beginEditingCaption; - (void)setupGifEditing; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernGalleryInterfaceView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernGalleryInterfaceView.h index a21f36027d..f2ae886fe8 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernGalleryInterfaceView.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernGalleryInterfaceView.h @@ -39,4 +39,7 @@ - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration; +- (bool)canBeginEditingCaption; +- (void)beginEditingCaption; + @end diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m index 84a2531334..b10092651a 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m @@ -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]; } diff --git a/submodules/LegacyComponents/Sources/TGModernGalleryController.m b/submodules/LegacyComponents/Sources/TGModernGalleryController.m index 6b6f83d8da..5f05c2a3cd 100644 --- a/submodules/LegacyComponents/Sources/TGModernGalleryController.m +++ b/submodules/LegacyComponents/Sources/TGModernGalleryController.m @@ -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 () { NSMutableDictionary *_reusableItemViewsByIdentifier; @@ -1759,6 +1773,28 @@ static CGFloat transformRotation(CGAffineTransform transform) } } +- (bool)_canBeginEditingCaptionFromKeyCommand +{ + UIView *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 diff --git a/submodules/LegacyUI/Sources/LegacyController.swift b/submodules/LegacyUI/Sources/LegacyController.swift index 7b276de96e..dae16ea446 100644 --- a/submodules/LegacyUI/Sources/LegacyController.swift +++ b/submodules/LegacyUI/Sources/LegacyController.swift @@ -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 + } +} diff --git a/submodules/MediaPickerUI/Sources/MediaPickerPhotoToolbarView.swift b/submodules/MediaPickerUI/Sources/MediaPickerPhotoToolbarView.swift index b7ae2d96a4..cf16d90804 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerPhotoToolbarView.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerPhotoToolbarView.swift @@ -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" diff --git a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift index 236c5c511b..4cf3f97bc9 100644 --- a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift @@ -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)) diff --git a/submodules/SettingsUI/Sources/ArchiveSettingsController.swift b/submodules/SettingsUI/Sources/ArchiveSettingsController.swift index f92f453a1e..95b321126e 100644 --- a/submodules/SettingsUI/Sources/ArchiveSettingsController.swift +++ b/submodules/SettingsUI/Sources/ArchiveSettingsController.swift @@ -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 diff --git a/submodules/SettingsUI/Sources/LogoutOptionsController.swift b/submodules/SettingsUI/Sources/LogoutOptionsController.swift index 9dc6416674..bc029a99ae 100644 --- a/submodules/SettingsUI/Sources/LogoutOptionsController.swift +++ b/submodules/SettingsUI/Sources/LogoutOptionsController.swift @@ -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) diff --git a/submodules/TelegramUI/Components/Ads/AdsInfoScreen/BUILD b/submodules/TelegramUI/Components/Ads/AdsInfoScreen/BUILD index db32ff429f..7a00f00071 100644 --- a/submodules/TelegramUI/Components/Ads/AdsInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/Ads/AdsInfoScreen/BUILD @@ -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", diff --git a/submodules/TelegramUI/Components/Ads/AdsInfoScreen/Sources/AdsInfoScreen.swift b/submodules/TelegramUI/Components/Ads/AdsInfoScreen/Sources/AdsInfoScreen.swift index 065e9057e2..de4d0710fe 100644 --- a/submodules/TelegramUI/Components/Ads/AdsInfoScreen/Sources/AdsInfoScreen.swift +++ b/submodules/TelegramUI/Components/Ads/AdsInfoScreen/Sources/AdsInfoScreen.swift @@ -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.self) + let moreButtonPlayOnce = ActionSlot() + 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 ) diff --git a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/NavigationButtonComponent.swift b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/NavigationButtonComponent.swift index 5ebc820020..55759caee5 100644 --- a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/NavigationButtonComponent.swift +++ b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/NavigationButtonComponent.swift @@ -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): diff --git a/submodules/TelegramUI/Components/ChatThemeScreen/Sources/ChatThemeScreen.swift b/submodules/TelegramUI/Components/ChatThemeScreen/Sources/ChatThemeScreen.swift index 6bbd3dad85..ede779c011 100644 --- a/submodules/TelegramUI/Components/ChatThemeScreen/Sources/ChatThemeScreen.swift +++ b/submodules/TelegramUI/Components/ChatThemeScreen/Sources/ChatThemeScreen.swift @@ -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"), diff --git a/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationBarImpl.swift b/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationBarImpl.swift index e48b154102..bb653def06 100644 --- a/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationBarImpl.swift +++ b/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationBarImpl.swift @@ -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 diff --git a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/JoinAffiliateProgramScreen.swift b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/JoinAffiliateProgramScreen.swift index 1adf1eae10..10d595552b 100644 --- a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/JoinAffiliateProgramScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/JoinAffiliateProgramScreen.swift @@ -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) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD index 79005d5964..9bc36133b3 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD @@ -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", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenBio.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenBio.swift index c6921aea19..909dba2e7a 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenBio.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenBio.swift @@ -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)) } }))) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenPeerInfoContextMenu.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenPeerInfoContextMenu.swift index b70068452d..11135c8597 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenPeerInfoContextMenu.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenPeerInfoContextMenu.swift @@ -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)) })) } diff --git a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift index b494ea0425..9e0f9fb370 100644 --- a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift @@ -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: {}, diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD index b73ff9df5a..f9476bb580 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD @@ -79,6 +79,7 @@ swift_library( "//submodules/TextSelectionNode", "//submodules/Pasteboard", "//submodules/Speak", + "//submodules/TelegramUI/Components/TextProcessingScreen", "//submodules/TranslateUI", "//submodules/TelegramNotices", "//submodules/MediaPlayer:UniversalMediaPlayer", diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index 7b342f3c00..f9b11322c8 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -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)) + } }) } diff --git a/submodules/TelegramUI/Components/TabBarComponent/Sources/TabBarComponent.swift b/submodules/TelegramUI/Components/TabBarComponent/Sources/TabBarComponent.swift index 0ebf550a13..d2ad61f9a6 100644 --- a/submodules/TelegramUI/Components/TabBarComponent/Sources/TabBarComponent.swift +++ b/submodules/TelegramUI/Components/TabBarComponent/Sources/TabBarComponent.swift @@ -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 { diff --git a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift index dc9d2a985c..9b0017373f 100644 --- a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift +++ b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift @@ -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 diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/Send.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Editor/Send.imageset/Contents.json new file mode 100644 index 0000000000..bbc3fe0aeb --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Media Editor/Send.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "attachlogo.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/Send.imageset/attachlogo.pdf b/submodules/TelegramUI/Images.xcassets/Media Editor/Send.imageset/attachlogo.pdf new file mode 100644 index 0000000000..b222371180 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Media Editor/Send.imageset/attachlogo.pdf differ diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index 3be681aab8..2687e1c212 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -59,6 +59,7 @@ import Markdown import TelegramPermissionsUI import Speak import TranslateUI +import TextProcessingScreen import UniversalMediaPlayer import WallpaperBackgroundNode import ChatListUI @@ -4624,27 +4625,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 diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index c5018fafd8..70f4ac306d 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -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 { diff --git a/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift b/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift index 560273d070..71d1ba707b 100644 --- a/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift +++ b/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift @@ -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) } } diff --git a/submodules/TranslateUI/Sources/LanguageSelectionController.swift b/submodules/TranslateUI/Sources/LanguageSelectionController.swift deleted file mode 100644 index dcea89c7b1..0000000000 --- a/submodules/TranslateUI/Sources/LanguageSelectionController.swift +++ /dev/null @@ -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() - 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 -} diff --git a/submodules/TranslateUI/Sources/PlayPauseIconComponent.swift b/submodules/TranslateUI/Sources/PlayPauseIconComponent.swift deleted file mode 100644 index 561a0feda1..0000000000 --- a/submodules/TranslateUI/Sources/PlayPauseIconComponent.swift +++ /dev/null @@ -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, transition: ComponentTransition) -> CGSize { - return view.update(component: self, availableSize: availableSize, transition: transition) - } -} diff --git a/submodules/TranslateUI/Sources/Translate.swift b/submodules/TranslateUI/Sources/Translate.swift index f587da8b5d..38152a40bc 100644 --- a/submodules/TranslateUI/Sources/Translate.swift +++ b/submodules/TranslateUI/Sources/Translate.swift @@ -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" diff --git a/submodules/TranslateUI/Sources/TranslateButtonComponent.swift b/submodules/TranslateUI/Sources/TranslateButtonComponent.swift deleted file mode 100644 index d00837baae..0000000000 --- a/submodules/TranslateUI/Sources/TranslateButtonComponent.swift +++ /dev/null @@ -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 - - 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, transition: ComponentTransition) -> CGSize { - return view.update(component: self, availableSize: availableSize, transition: transition) - } -} diff --git a/submodules/TranslateUI/Sources/TranslateScreen.swift b/submodules/TranslateUI/Sources/TranslateScreen.swift index b3f965462e..8cbde2fedb 100644 --- a/submodules/TranslateUI/Sources/TranslateScreen.swift +++ b/submodules/TranslateUI/Sources/TranslateScreen.swift @@ -5,1175 +5,8 @@ import AsyncDisplayKit import TelegramCore import SwiftSignalKit import AccountContext -import TelegramPresentationData -import PresentationDataUtils -import Speak -import ComponentFlow -import ViewControllerComponent -import MultilineTextComponent -import MultilineTextWithEntitiesComponent -import BundleIconComponent -import UndoUI import SwiftUI -import ResizableSheetComponent -import GlassBarButtonComponent -import ListSectionComponent -import ListActionItemComponent -import PlainButtonComponent -import ButtonComponent -import TextFormat -import Pasteboard -import ContextUI -import TranslationLanguagesContextMenuContent import TelegramUIPreferences -import Markdown - -private let translateToTag = GenericComponentViewTag() - -private func generateExpandBackground(size: CGSize, color: UIColor) -> UIImage { - return generateImage(size, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - var locations: [CGFloat] = [0.0, 1.0] - let colors: [CGColor] = [color.withAlphaComponent(0.0).cgColor, color.cgColor] - - let colorSpace = CGColorSpaceCreateDeviceRGB() - let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)! - - context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 40.0, y: size.height), options: CGGradientDrawingOptions()) - context.setFillColor(color.cgColor) - context.fill(CGRect(origin: CGPoint(x: 40.0, y: 0.0), size: CGSize(width: size.width - 40.0, height: size.height))) - })! -} - -private final class SheetContent: CombinedComponent { - typealias EnvironmentType = ViewControllerComponentContainer.Environment - - let context: AccountContext - let text: String - let entities: [MessageTextEntity] - let fromLanguage: String? - let toLanguage: String - let copyTranslation: ((String, [MessageTextEntity]) -> Void)? - let replaceText: ((String, [MessageTextEntity]) -> Void)? - let translateChat: ((String, String) -> Void)? - let changeLanguage: (String, String, @escaping (String, String) -> Void) -> Void - let expand: () -> Void - let dismiss: () -> Void - - init( - context: AccountContext, - text: String, - entities: [MessageTextEntity], - fromLanguage: String?, - toLanguage: String, - copyTranslation: ((String, [MessageTextEntity]) -> Void)?, - replaceText: ((String, [MessageTextEntity]) -> Void)?, - translateChat: ((String, String) -> Void)?, - changeLanguage: @escaping (String, String, @escaping (String, String) -> Void) -> Void, - expand: @escaping () -> Void, - dismiss: @escaping () -> Void - ) { - self.context = context - self.text = text - self.entities = entities - self.fromLanguage = fromLanguage - self.toLanguage = toLanguage - self.copyTranslation = copyTranslation - self.replaceText = replaceText - self.translateChat = translateChat - self.changeLanguage = changeLanguage - self.expand = expand - self.dismiss = dismiss - } - - static func ==(lhs: SheetContent, rhs: SheetContent) -> Bool { - if lhs.context !== rhs.context { - return false - } - if lhs.text != rhs.text { - return false - } - if lhs.entities != rhs.entities { - return false - } - if lhs.fromLanguage != rhs.fromLanguage { - return false - } - if lhs.toLanguage != rhs.toLanguage { - return false - } - return true - } - - final class State: ComponentState { - private let context: AccountContext - - var fromLanguage: String? - let text: String - let entities: [MessageTextEntity] - var textExpanded: Bool = false - - var toLanguage: String - var tone: TranslationTone = .neutral - - var translatedText: (String, [MessageTextEntity])? - - private let expand: () -> Void - - private var translationDisposable = MetaDisposable() - - fileprivate var isSpeakingOriginalText: Bool = false - fileprivate var isSpeakingTranslatedText: Bool = false - private var speechHolder: SpeechSynthesizerHolder? - fileprivate var availableSpeakLanguages: Set - - fileprivate var moreBackgroundImage: (CGSize, UIImage, UIColor)? - - private let useAlternativeTranslation: Bool - - weak var controller: TranslateScreen? - - init(context: AccountContext, fromLanguage: String?, text: String, entities: [MessageTextEntity], toLanguage: String, expand: @escaping () -> Void) { - self.context = context - self.text = text - self.entities = entities - self.fromLanguage = fromLanguage - self.toLanguage = toLanguage - self.expand = expand - self.availableSpeakLanguages = supportedSpeakLanguages() - - let translationConfiguration = TranslationConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) - var useAlternativeTranslation = false - switch translationConfiguration.manual { - case .alternative: - useAlternativeTranslation = true - default: - break - } - self.useAlternativeTranslation = useAlternativeTranslation - - super.init() - - self.translationDisposable.set((self.translate(text: text, entities: entities, fromLang: fromLanguage, toLang: toLanguage) |> deliverOnMainQueue).start(next: { [weak self] text in - guard let strongSelf = self else { - return - } - strongSelf.translatedText = text - strongSelf.updated(transition: .immediate) - }, error: { error in - - })) - } - - deinit { - self.speechHolder?.stop() - self.translationDisposable.dispose() - } - - func translate(text: String, entities: [MessageTextEntity], fromLang: String?, toLang: String) -> Signal<(String, [MessageTextEntity])?, TranslationError> { - if self.useAlternativeTranslation { - return alternativeTranslateText(text: text, fromLang: fromLang, toLang: toLang) - } else { - return self.context.engine.messages.translate(text: text, toLang: toLang, entities: entities, tone: self.tone) - } - } - - func changeTone(_ tone: TranslationTone) { - guard self.tone != tone else { - return - } - self.tone = tone - self.translatedText = nil - self.updated(transition: .immediate) - - self.translationDisposable.set((self.translate(text: self.text, entities: self.entities, fromLang: self.fromLanguage, toLang: self.toLanguage) |> deliverOnMainQueue).start(next: { [weak self] text in - guard let strongSelf = self else { - return - } - strongSelf.translatedText = text - strongSelf.updated(transition: .immediate) - }, error: { error in - - })) - } - - func changeLanguage(fromLanguage: String, toLanguage: String) { - guard self.fromLanguage != fromLanguage || self.toLanguage != toLanguage else { - return - } - self.fromLanguage = fromLanguage - self.toLanguage = toLanguage - self.translatedText = nil - self.updated(transition: .immediate) - - self.translationDisposable.set((self.translate(text: self.text, entities: self.entities, fromLang: fromLanguage, toLang: toLanguage) |> deliverOnMainQueue).start(next: { [weak self] text in - guard let strongSelf = self else { - return - } - strongSelf.translatedText = text - strongSelf.updated(transition: .immediate) - }, error: { error in - - })) - } - - func expandText() { - self.textExpanded = true - self.updated(transition: .immediate) - - self.expand() - } - - func speakOriginalText() { - if let speechHolder = self.speechHolder { - self.speechHolder = nil - speechHolder.stop() - } - - if self.isSpeakingOriginalText { - self.isSpeakingOriginalText = false - } else { - self.isSpeakingTranslatedText = false - - self.isSpeakingOriginalText = true - self.speechHolder = speakText(context: self.context, text: self.text) - self.speechHolder?.completion = { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.isSpeakingOriginalText = false - strongSelf.updated(transition: .immediate) - } - } - self.updated(transition: .immediate) - } - - func speakTranslatedText() { - guard let translatedText = self.translatedText else { - return - } - - if let speechHolder = self.speechHolder { - self.speechHolder = nil - speechHolder.stop() - } - - if self.isSpeakingTranslatedText { - self.isSpeakingTranslatedText = false - } else { - self.isSpeakingOriginalText = false - - self.isSpeakingTranslatedText = true - self.speechHolder = speakText(context: self.context, text: translatedText.0) - self.speechHolder?.completion = { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.isSpeakingTranslatedText = false - strongSelf.updated(transition: .immediate) - } - } - self.updated(transition: .immediate) - } - - func presentLanguageSelection() { - guard let controller = self.controller else { - return - } - - guard let sourceView = controller.node.hostView.findTaggedView(tag: translateToTag) else { - return - } - - let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - var languageCode = presentationData.strings.baseLanguageCode - let rawSuffix = "-raw" - if languageCode.hasSuffix(rawSuffix) { - languageCode = String(languageCode.dropLast(rawSuffix.count)) - } - - let _ = (self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings]) - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] sharedData in - guard let self else { - return - } - let settings: TranslationSettings - if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) { - settings = current - } else { - settings = TranslationSettings.defaultSettings - } - - var addedLanguages = Set() - - var topLanguages: [String] = [] - let langCode = normalizeTranslationLanguage(languageCode) - - topLanguages.append("tone_formal") - topLanguages.append("tone_neutral") - topLanguages.append("tone_casual") - - topLanguages.append("") - - var ignoredLanguages: Set - if let current = settings.ignoredLanguages { - ignoredLanguages = Set(current) - } else { - ignoredLanguages = Set([langCode]) - for language in systemLanguageCodes() { - ignoredLanguages.insert(language) - } - } - for code in supportedTranslationLanguages { - if ignoredLanguages.contains(code) { - topLanguages.append(code) - } - } - - topLanguages.append(" ") - - var languages: [(String, String)] = [] - let languageLocale = Locale(identifier: langCode) - - for code in topLanguages { - if !addedLanguages.contains(code) { - let displayTitle: String - if code.hasPrefix("tone_") { - switch code { - case "tone_formal": - displayTitle = "Formal" - case "tone_neutral": - displayTitle = "Neutral" - case "tone_casual": - displayTitle = "Casual" - default: - displayTitle = "" - } - } else { - displayTitle = languageLocale.localizedString(forLanguageCode: code) ?? "" - } - - let value = (code, displayTitle) - if code == languageCode { - languages.insert(value, at: 4) - } else { - languages.append(value) - } - addedLanguages.insert(code) - } - } - - for code in supportedTranslationLanguages { - if !addedLanguages.contains(code) { - let displayTitle = languageLocale.localizedString(forLanguageCode: code) ?? "" - let value = (code, displayTitle) - if code == languageCode { - languages.insert(value, at: 4) - } else { - languages.append(value) - } - addedLanguages.insert(code) - } - } - - var selectedLanguages = Set() - selectedLanguages.insert("tone_\(self.tone.rawValue)") - selectedLanguages.insert(self.toLanguage) - - var dismissImpl: (() -> Void)? - let items = ContextController.Items( - content: .custom( - TranslationLanguagesContextMenuContent( - context: self.context, - languages: languages, - selectedLanguages: selectedLanguages, - back: nil, - selectLanguage: { [weak self] language in - guard let self else { - return - } - if language.hasPrefix("tone_") { - let tone = String(language.dropFirst(5)) - self.changeTone(TranslationTone(rawValue: tone)!) - } else { - self.changeLanguage(fromLanguage: self.fromLanguage ?? "", toLanguage: language) - } - dismissImpl?() - } - ) - ) - ) - - let contextController = makeContextController(presentationData: presentationData, source: .reference(GiftViewContextReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(items), gesture: nil) - controller.presentInGlobalOverlay(contextController) - - dismissImpl = { [weak contextController] in - contextController?.dismiss() - } - }) - } - } - - func makeState() -> State { - return State(context: self.context, fromLanguage: self.fromLanguage, text: self.text, entities: self.entities, toLanguage: self.toLanguage, expand: self.expand) - } - - static var body: Body { - let textBackground = Child(RoundedRectangle.self) - - let originalTitle = Child(PlainButtonComponent.self) - let originalText = Child(MultilineTextComponent.self) - - let originalMoreBackground = Child(Image.self) - let originalMoreButton = Child(Button.self) - - let originalSpeakButton = Child(Button.self) - - let translationTitle = Child(PlainButtonComponent.self) - let translationText = Child(MultilineTextComponent.self) - let translationPlaceholder = Child(RoundedRectangle.self) - let translationSpeakButton = Child(Button.self) - - let textStripe = Child(Rectangle.self) -// let textSection = Child(ListSectionComponent.self) - let actionsSection = Child(ListSectionComponent.self) - - return { context in - let environment = context.environment[ViewControllerComponentContainer.Environment.self].value - let state = context.state - let theme = environment.theme.withModalBlocksBackground() - let strings = environment.strings - let presentationData = context.component.context.sharedContext.currentPresentationData.with { $0 } - let component = context.component - - if state.controller == nil { - state.controller = environment.controller() as? TranslateScreen - } - - let sideInset: CGFloat = 16.0 - - - let textTopInset: CGFloat = 16.0 - let textSpacing: CGFloat = 5.0 - let itemSpacing: CGFloat = 20.0 - let textSideInset: CGFloat = 16.0 - - - var contentHeight: CGFloat = 82.0 - - let textFont = Font.regular(20.0) - let boldTextFont = Font.semibold(20.0) - let italicTextFont = Font.with(size: 20.0, weight: .regular, traits: .italic) - let boldItalicTextFont = Font.semiboldItalic(20.0) - let fixedTextFont = Font.with(size: 20.0, design: .monospace) - - var languageCode = environment.strings.baseLanguageCode - let rawSuffix = "-raw" - if languageCode.hasSuffix(rawSuffix) { - languageCode = String(languageCode.dropLast(rawSuffix.count)) - } - let locale = Locale(identifier: languageCode) - let fromLanguage: String - if let languageCode = state.fromLanguage { - fromLanguage = locale.localizedString(forLanguageCode: languageCode) ?? "" - } else { - fromLanguage = "" - } - - let _ = sideInset - let _ = fromLanguage - - let originalTitleString = parseMarkdownIntoAttributedString("Detected as **\(fromLanguage)**", attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.list.itemSecondaryTextColor), bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.list.itemAccentColor), link: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.list.itemPrimaryTextColor), linkAttribute: { _ in return nil })) - - let originalTitle = originalTitle.update( - component: PlainButtonComponent( - content: AnyComponent( - HStack([ - AnyComponentWithIdentity(id: "label", component: AnyComponent( - MultilineTextComponent( - text: .plain(originalTitleString), - horizontalAlignment: .natural, - maximumNumberOfLines: 1 - ) - )), - AnyComponentWithIdentity(id: "icon", component: AnyComponent( - BundleIconComponent(name: "Item List/ContextDisclosureArrow", tintColor: theme.list.itemAccentColor, maxSize: CGSize(width: 8.0, height: 11.0)) - )) - ], spacing: 3.0) - ), - action: { - component.changeLanguage(state.fromLanguage ?? "", state.toLanguage, { fromLang, toLang in - state.changeLanguage(fromLanguage: fromLang, toLanguage: toLang) - }) - }, - animateScale: false - ), - availableSize: CGSize(width: context.availableSize.width - (sideInset + textSideInset) * 2.0, height: CGFloat.greatestFiniteMagnitude), - transition: .immediate - ) - - let originalAttributedText = stringWithAppliedEntities(state.text, entities: state.entities, baseColor: theme.list.itemPrimaryTextColor, linkColor: theme.list.itemPrimaryTextColor, baseFont: textFont, linkFont: textFont, boldFont: boldTextFont, italicFont: italicTextFont, boldItalicFont: boldItalicTextFont, fixedFont: fixedTextFont, blockQuoteFont: textFont, message: nil) - - let originalText = originalText.update( - component: MultilineTextComponent( - text: .plain(originalAttributedText), - horizontalAlignment: .natural, - maximumNumberOfLines: state.textExpanded ? 0 : 1, - lineSpacing: 0.1 - ), - availableSize: CGSize(width: context.availableSize.width - (sideInset + textSideInset) * 2.0 - (state.textExpanded ? 30.0 : 0.0), height: context.availableSize.height), - transition: .immediate - ) - - var toLanguage = locale.localizedString(forLanguageCode: state.toLanguage) ?? "" - if state.tone != .neutral { - toLanguage += " (\(state.tone.rawValue.capitalized))" - } - let translationTitle = translationTitle.update( - component: PlainButtonComponent( - content: AnyComponent( - HStack([ - AnyComponentWithIdentity(id: "label", component: AnyComponent( - MultilineTextComponent( - text: .plain(NSAttributedString(string: toLanguage, font: Font.semibold(13.0), textColor: theme.list.itemAccentColor, paragraphAlignment: .natural)), - horizontalAlignment: .natural, - maximumNumberOfLines: 1 - ) - )), - AnyComponentWithIdentity(id: "icon", component: AnyComponent( - BundleIconComponent(name: "Item List/ContextDisclosureArrow", tintColor: theme.list.itemAccentColor, maxSize: CGSize(width: 8.0, height: 11.0)) - )) - ], spacing: 3.0) - ), - action: { [weak state] in - state?.presentLanguageSelection() -// component.changeLanguage(state.fromLanguage ?? "", state.toLanguage, { fromLang, toLang in -// state.changeLanguage(fromLanguage: fromLang, toLanguage: toLang) -// }) - }, - animateScale: false, - tag: translateToTag - ), - availableSize: CGSize(width: context.availableSize.width - (sideInset + textSideInset) * 2.0, height: CGFloat.greatestFiniteMagnitude), - transition: .immediate - ) - - let translationTextHeight: CGFloat - - var maybeTranslationText: _UpdatedChildComponent? = nil - var maybeTranslationPlaceholder: _UpdatedChildComponent? = nil - if let translatedText = state.translatedText { - let attributedText = stringWithAppliedEntities(translatedText.0, entities: translatedText.1, baseColor: theme.list.itemAccentColor, linkColor: theme.list.itemAccentColor, baseFont: textFont, linkFont: textFont, boldFont: boldTextFont, italicFont: italicTextFont, boldItalicFont: boldItalicTextFont, fixedFont: fixedTextFont, blockQuoteFont: textFont, message: nil) - - maybeTranslationText = translationText.update( - component: MultilineTextComponent( - text: .plain(attributedText), - horizontalAlignment: .natural, - maximumNumberOfLines: 0, - lineSpacing: 0.1 - ), - availableSize: CGSize(width: context.availableSize.width - (sideInset + textSideInset) * 2.0 - 30.0, height: context.availableSize.height), - transition: .immediate - ) - translationTextHeight = maybeTranslationText?.size.height ?? 0.0 - } else { - maybeTranslationPlaceholder = translationPlaceholder.update( - component: RoundedRectangle(color: theme.list.itemAccentColor.withAlphaComponent(0.17), cornerRadius: 6.0), - availableSize: CGSize(width: context.availableSize.width - (sideInset + textSideInset) * 2.0 - 42.0, height: 12.0), - transition: .immediate - ) - translationTextHeight = 22.0 - } - - let topInset = contentHeight - let textBackgroundOrigin = CGPoint(x: sideInset, y: topInset) - - let textStripe = textStripe.update( - component: Rectangle(color: theme.list.itemPlainSeparatorColor), - availableSize: CGSize(width: context.availableSize.width - (sideInset + textSideInset) * 2.0, height: UIScreenPixel), - transition: .immediate - ) - - let textBackgroundSize = CGSize(width: context.availableSize.width - sideInset * 2.0, height: textTopInset + originalTitle.size.height + textSpacing + originalText.size.height + itemSpacing + textTopInset + translationTitle.size.height + textSpacing + translationTextHeight + itemSpacing) - - let textBackground = textBackground.update( - component: RoundedRectangle(color: theme.list.itemBlocksBackgroundColor, cornerRadius: 26.0), - availableSize: textBackgroundSize, - transition: context.transition - ) - - context.add(textBackground - .position(CGPoint(x: textBackgroundOrigin.x + textBackgroundSize.width / 2.0, y: topInset + textBackgroundSize.height / 2.0)) - ) - - context.add(textStripe - .position(CGPoint(x: textBackgroundOrigin.x + textSideInset + textStripe.size.width / 2.0, y: textBackgroundOrigin.y + textTopInset + originalTitle.size.height + textSpacing + originalText.size.height + itemSpacing)) - ) - - context.add(originalTitle - .position(CGPoint(x: textBackgroundOrigin.x + textSideInset + originalTitle.size.width / 2.0, y: textBackgroundOrigin.y + textTopInset + originalTitle.size.height / 2.0)) - ) - context.add(originalText - .position(CGPoint(x: textBackgroundOrigin.x + textSideInset + originalText.size.width / 2.0, y: textBackgroundOrigin.y + textTopInset + originalTitle.size.height + textSpacing + originalText.size.height / 2.0)) - ) - - if state.textExpanded { - if let fromLanguage = state.fromLanguage, state.availableSpeakLanguages.contains(fromLanguage) { - var checkColor = theme.list.itemCheckColors.foregroundColor - if checkColor.rgb == theme.list.itemPrimaryTextColor.rgb { - checkColor = theme.list.plainBackgroundColor - } - - let originalSpeakButton = originalSpeakButton.update( - component: Button( - content: AnyComponent(ZStack([ - AnyComponentWithIdentity(id: "b", component: AnyComponent(Circle( - fillColor: theme.list.itemPrimaryTextColor, - size: CGSize(width: 26.0, height: 26.0) - ))), - AnyComponentWithIdentity(id: "a", component: AnyComponent(PlayPauseIconComponent( - state: state.isSpeakingOriginalText ? .pause : .play, - tintColor: checkColor, - size: CGSize(width: 20.0, height: 20.0) - ))), - ])), - action: { [weak state] in - guard let state = state else { - return - } - state.speakOriginalText() - } - ).minSize(CGSize(width: 44.0, height: 44.0)), - availableSize: CGSize(width: 26.0, height: 26.0), - transition: .immediate - ) - - context.add(originalSpeakButton - .position(CGPoint(x: context.availableSize.width - sideInset - textSideInset - originalSpeakButton.size.width / 2.0 + 9.0, y: textBackgroundOrigin.y + textTopInset + originalTitle.size.height + textSpacing + originalText.size.height - originalSpeakButton.size.height / 2.0 - 2.0 + 12.0)) - ) - } - } else { - let originalMoreButton = originalMoreButton.update( - component: Button( - content: AnyComponent(Text(text: strings.PeerInfo_BioExpand, font: Font.regular(17.0), color: theme.list.itemAccentColor)), - action: { [weak state] in - guard let state = state else { - return - } - state.expandText() - } - ), - availableSize: context.availableSize, - transition: .immediate - ) - - let originalMoreBackgroundSize = CGSize(width: originalMoreButton.size.width + 50.0, height: originalMoreButton.size.height) - let originalMoreBackgroundImage: UIImage - let backgroundColor = theme.list.itemBlocksBackgroundColor - if let (size, image, color) = state.moreBackgroundImage, size == originalMoreBackgroundSize && color == backgroundColor { - originalMoreBackgroundImage = image - } else { - originalMoreBackgroundImage = generateExpandBackground(size: originalMoreBackgroundSize, color: backgroundColor) - state.moreBackgroundImage = (originalMoreBackgroundSize, originalMoreBackgroundImage, backgroundColor) - } - let originalMoreBackground = originalMoreBackground.update( - component: Image(image: originalMoreBackgroundImage, tintColor: backgroundColor), - availableSize: originalMoreBackgroundSize, - transition: .immediate - ) - - context.add(originalMoreBackground - .position(CGPoint(x: context.availableSize.width - sideInset - textSideInset - originalMoreBackground.size.width / 2.0, y: textBackgroundOrigin.y + textTopInset + originalTitle.size.height + textSpacing + originalMoreBackground.size.height / 2.0 + 2.0)) - ) - - context.add(originalMoreButton - .position(CGPoint(x: context.availableSize.width - sideInset - textSideInset - originalMoreButton.size.width / 2.0, y: textBackgroundOrigin.y + textTopInset + originalTitle.size.height + textSpacing + originalText.size.height / 2.0 + 1.0 - UIScreenPixel)) - ) - } - - context.add(translationTitle - .position(CGPoint(x: textBackgroundOrigin.x + textSideInset + translationTitle.size.width / 2.0, y: textBackgroundOrigin.y + textTopInset + originalTitle.size.height + textSpacing + originalText.size.height + itemSpacing + textTopInset + translationTitle.size.height / 2.0)) - ) - - if let translationText = maybeTranslationText { - context.add(translationText - .position(CGPoint(x: textBackgroundOrigin.x + textSideInset + translationText.size.width / 2.0, y: textBackgroundOrigin.y + textTopInset + originalTitle.size.height + textSpacing + originalText.size.height + itemSpacing + textTopInset + translationTitle.size.height + textSpacing + translationText.size.height / 2.0)) - ) - - if state.availableSpeakLanguages.contains(state.toLanguage) { - let translationSpeakButton = translationSpeakButton.update( - component: Button( - content: AnyComponent(ZStack([ - AnyComponentWithIdentity(id: "b", component: AnyComponent(Circle( - fillColor: theme.list.itemAccentColor, - size: CGSize(width: 26.0, height: 26.0) - ))), - AnyComponentWithIdentity(id: "a", component: AnyComponent(PlayPauseIconComponent( - state: state.isSpeakingTranslatedText ? .pause : .play, - tintColor: theme.list.itemCheckColors.foregroundColor, - size: CGSize(width: 20.0, height: 20.0) - ))), - ])), - action: { [weak state] in - guard let state = state else { - return - } - state.speakTranslatedText() - } - ).minSize(CGSize(width: 44.0, height: 44.0)), - availableSize: CGSize(width: 26.0, height: 26.0), - transition: .immediate - ) - - context.add(translationSpeakButton - .position(CGPoint(x: context.availableSize.width - sideInset - textSideInset - translationSpeakButton.size.width / 2.0 + 9.0, y: textBackgroundOrigin.y + textTopInset + originalTitle.size.height + textSpacing + originalText.size.height + itemSpacing + textTopInset + translationTitle.size.height + textSpacing + translationTextHeight - translationSpeakButton.size.height / 2.0 - 2.0 + 12.0)) - .appear(.default()) - .disappear(.default()) - ) - } - } else if let translationPlaceholder = maybeTranslationPlaceholder { - context.add(translationPlaceholder - .position(CGPoint(x: textBackgroundOrigin.x + textSideInset + translationPlaceholder.size.width / 2.0, y: textBackgroundOrigin.y + textTopInset + originalTitle.size.height + textSpacing + originalText.size.height + itemSpacing + textTopInset + translationTitle.size.height + textSpacing + translationPlaceholder.size.height / 2.0 + 4.0)) - ) - } - - contentHeight += textBackgroundSize.height - contentHeight += 24.0 - -// let textSectionItems: [AnyComponentWithIdentity] = [] -// let textSection = textSection.update( -// component: ListSectionComponent( -// theme: theme, -// style: .glass, -// header: nil, -// footer: nil, -// items: textSectionItems -// ), -// availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), -// transition: context.transition -// ) -// context.add(textSection -// .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + textSection.size.height / 2.0)) -// ) -// contentHeight += textSection.size.height -// contentHeight += 24.0 - - var actionsSectionItems: [AnyComponentWithIdentity] = [] - if let replaceText = component.replaceText { - actionsSectionItems.append(AnyComponentWithIdentity(id: "replace", component: AnyComponent(ListActionItemComponent( - theme: theme, - style: .glass, - title: AnyComponent( - MultilineTextComponent( - text: .plain(NSAttributedString( - string: "Replace with Translation", - font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize), - textColor: theme.list.itemAccentColor - )), - maximumNumberOfLines: 1 - ) - ), - leftIcon: .custom(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Replace", tintColor: theme.list.itemAccentColor))), false), - action: { [weak state] _ in - guard let state else { - return - } - replaceText(state.translatedText?.0 ?? state.text, state.translatedText?.1 ?? state.entities) - component.dismiss() - } - )))) - } - if let copyTranslation = component.copyTranslation { - actionsSectionItems.append(AnyComponentWithIdentity(id: "copy", component: AnyComponent(ListActionItemComponent( - theme: theme, - style: .glass, - title: AnyComponent( - MultilineTextComponent( - text: .plain(NSAttributedString( - string: strings.Translate_CopyTranslation, - font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize), - textColor: theme.list.itemAccentColor - )), - maximumNumberOfLines: 1 - ) - ), - leftIcon: .custom(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Copy", tintColor: theme.list.itemAccentColor))), false), - action: { [weak state] _ in - guard let state else { - return - } - copyTranslation(state.translatedText?.0 ?? "", state.translatedText?.1 ?? []) - } - )))) - } - if let translateChat = component.translateChat { - actionsSectionItems.append(AnyComponentWithIdentity(id: "translate", component: AnyComponent(ListActionItemComponent( - theme: theme, - style: .glass, - title: AnyComponent( - MultilineTextComponent( - text: .plain(NSAttributedString( - string: "Translate Entire Chat", - font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize), - textColor: theme.list.itemAccentColor - )), - maximumNumberOfLines: 1 - ) - ), - leftIcon: .custom(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Translate", tintColor: theme.list.itemAccentColor))), false), - action: { [weak state] _ in - guard let state else { - return - } - translateChat(state.fromLanguage ?? "", state.toLanguage) - component.dismiss() - } - )))) - } - - if !actionsSectionItems.isEmpty { - let actionsSection = actionsSection.update( - component: ListSectionComponent( - theme: theme, - style: .glass, - header: nil, - footer: nil, - items: actionsSectionItems - ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), - transition: context.transition - ) - context.add(actionsSection - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + actionsSection.size.height / 2.0)) - ) - contentHeight += actionsSection.size.height - } - - contentHeight += 32.0 - contentHeight += 52.0 + 30.0 - - return CGSize(width: context.availableSize.width, height: contentHeight) - } - } -} - -private final class TranslateSheetComponent: CombinedComponent { - typealias EnvironmentType = ViewControllerComponentContainer.Environment - - private let context: AccountContext - private let text: String - private let entities: [MessageTextEntity] - private let fromLanguage: String? - private let toLanguage: String - private let copyTranslation: ((String, [MessageTextEntity]) -> Void)? - private let replaceText: ((String, [MessageTextEntity]) -> Void)? - private let translateChat: ((String, String) -> Void)? - private let changeLanguage: (String, String, @escaping (String, String) -> Void) -> Void - private let openCocoonInfo: () -> Void - - init( - context: AccountContext, - text: String, - entities: [MessageTextEntity], - fromLanguage: String?, - toLanguage: String, - copyTranslation: ((String, [MessageTextEntity]) -> Void)?, - replaceText: ((String, [MessageTextEntity]) -> Void)?, - translateChat: ((String, String) -> Void)? = nil, - changeLanguage: @escaping (String, String, @escaping (String, String) -> Void) -> Void, - openCocoonInfo: @escaping () -> Void - ) { - self.context = context - self.text = text - self.entities = entities - self.fromLanguage = fromLanguage - self.toLanguage = toLanguage - self.copyTranslation = copyTranslation - self.replaceText = replaceText - self.translateChat = translateChat - self.changeLanguage = changeLanguage - self.openCocoonInfo = openCocoonInfo - } - - static func ==(lhs: TranslateSheetComponent, rhs: TranslateSheetComponent) -> Bool { - return true - } - - static var body: Body { - let sheet = Child(ResizableSheetComponent<(EnvironmentType)>.self) - let animateOut = StoredActionSlot(Action.self) - - return { context in - let environment = context.environment[EnvironmentType.self] - - let controller = environment.controller - - let dismiss: (Bool) -> Void = { animated in - if animated { - animateOut.invoke(Action { _ in - if let controller = controller() { - controller.dismiss(completion: nil) - } - }) - } else { - if let controller = controller() { - controller.dismiss(completion: nil) - } - } - } - - let theme = environment.theme.withModalBlocksBackground() - let strings = environment.strings - - let openCocoonInfo = context.component.openCocoonInfo - - let sheet = sheet.update( - component: ResizableSheetComponent( - content: AnyComponent(SheetContent( - context: context.component.context, - text: context.component.text, - entities: context.component.entities, - fromLanguage: context.component.fromLanguage, - toLanguage: context.component.toLanguage, - copyTranslation: context.component.copyTranslation, - replaceText: context.component.replaceText, - translateChat: context.component.translateChat, - changeLanguage: context.component.changeLanguage, - expand: {}, - dismiss: { - dismiss(true) - } - )), - titleItem: AnyComponent( - VStack([ - AnyComponentWithIdentity(id: "title", component: AnyComponent( - MultilineTextComponent(text: .plain(NSAttributedString(string: "Translation", font: Font.semibold(17.0), textColor: theme.list.itemPrimaryTextColor))) - )), - AnyComponentWithIdentity(id: "subtitle", component: AnyComponent( - MultilineTextComponent(text: .plain(NSAttributedString(string: "powered by Cocoon", font: Font.regular(13.0), textColor: theme.list.itemSecondaryTextColor))) - )) - ], spacing: 1.0) - ), - leftItem: AnyComponent( - GlassBarButtonComponent( - size: CGSize(width: 44.0, height: 44.0), - backgroundColor: nil, - isDark: theme.overallDarkAppearance, - state: .glass, - component: AnyComponentWithIdentity(id: "close", component: AnyComponent( - BundleIconComponent( - name: "Navigation/Close", - tintColor: theme.chat.inputPanel.panelControlColor - ) - )), - action: { _ in - dismiss(true) - } - ) - ), - rightItem: AnyComponent( - GlassBarButtonComponent( - size: CGSize(width: 44.0, height: 44.0), - backgroundColor: nil, - isDark: theme.overallDarkAppearance, - state: .glass, - component: AnyComponentWithIdentity(id: "info", component: AnyComponent( - BundleIconComponent( - name: "Navigation/Question", - tintColor: theme.chat.inputPanel.panelControlColor - ) - )), - action: { _ in - openCocoonInfo() - } - ) - ), - bottomItem: AnyComponent( - ButtonComponent( - background: ButtonComponent.Background( - style: .glass, - color: theme.list.itemCheckColors.fillColor, - foreground: theme.list.itemCheckColors.foregroundColor, - pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) - ), - content: AnyComponentWithIdentity( - id: AnyHashable(0), - component: AnyComponent( - ButtonTextContentComponent( - text: strings.Common_OK, - badge: 0, - textColor: theme.list.itemCheckColors.foregroundColor, - badgeBackground: theme.list.itemCheckColors.foregroundColor, - badgeForeground: theme.list.itemCheckColors.fillColor - ) - ) - ), - action: { - dismiss(true) - } - ) - ), - backgroundColor: .color(theme.list.modalBlocksBackgroundColor), - animateOut: animateOut - ), - environment: { - environment - ResizableSheetComponentEnvironment( - theme: theme, - statusBarHeight: environment.statusBarHeight, - safeInsets: environment.safeInsets, - inputHeight: 0.0, - metrics: environment.metrics, - deviceMetrics: environment.deviceMetrics, - isDisplaying: environment.value.isVisible, - isCentered: environment.metrics.widthClass == .regular, - screenSize: context.availableSize, - regularMetricsSize: CGSize(width: 430.0, height: 900.0), - dismiss: { animated in - dismiss(animated) - } - ) - }, - availableSize: context.availableSize, - transition: context.transition - ) - - context.add(sheet - .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) - ) - - return context.availableSize - } - } -} - -public final class TranslateScreen: ViewControllerComponentContainer { - private let context: AccountContext - - public var pushController: (ViewController) -> Void = { _ in } - public var presentController: (ViewController) -> Void = { _ in } - - public init( - context: AccountContext, - forceTheme: PresentationTheme? = nil, - text: String, - entities: [MessageTextEntity] = [], - canCopy: Bool, - fromLanguage: String?, - toLanguage: String? = nil, - ignoredLanguages: [String]? = nil, - replaceText: ((String, [MessageTextEntity]) -> Void)? = nil, - translateChat: ((String, String) -> Void)? = nil - ) { - self.context = context - - let theme: ViewControllerComponentContainer.Theme - if let forceTheme { - theme = .custom(forceTheme) - } else { - theme = .default - } - - let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - var baseLanguageCode = presentationData.strings.baseLanguageCode - let rawSuffix = "-raw" - if baseLanguageCode.hasSuffix(rawSuffix) { - baseLanguageCode = String(baseLanguageCode.dropLast(rawSuffix.count)) - } - - let dontTranslateLanguages = effectiveIgnoredTranslationLanguages(context: context, ignoredLanguages: ignoredLanguages) - - var toLanguage = toLanguage ?? baseLanguageCode - if toLanguage == fromLanguage { - if fromLanguage == "en" { - toLanguage = dontTranslateLanguages.first(where: { $0 != "en" }) ?? "en" - } else { - toLanguage = "en" - } - if toLanguage == "en" && fromLanguage == "en" { - if let anyOtherLanguage = NSLocale.preferredLanguages.first(where: { !$0.hasPrefix("en-") }) { - toLanguage = anyOtherLanguage - } - } - } - - toLanguage = normalizeTranslationLanguage(toLanguage) - - var copyTranslationImpl: ((String, [MessageTextEntity]) -> Void)? - var changeLanguageImpl: ((String, String, @escaping (String, String) -> Void) -> Void)? - var openCocoonInfoImpl: (() -> Void)? - - super.init( - context: context, - component: TranslateSheetComponent( - context: context, - text: text, - entities: entities, - fromLanguage: fromLanguage, - toLanguage: toLanguage, - copyTranslation: !canCopy ? nil : { text, entities in - copyTranslationImpl?(text, entities) - }, - replaceText: replaceText, - translateChat: translateChat, - changeLanguage: { fromLang, toLang, completion in - changeLanguageImpl?(fromLang, toLang, completion) - }, - openCocoonInfo: { - openCocoonInfoImpl?() - } - ), - navigationBarAppearance: .none, - statusBarStyle: .ignore, - theme: theme - ) - - self.statusBar.statusBarStyle = .Ignore - self.navigationPresentation = .flatModal - self.blocksBackgroundWhenInOverlay = true - self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) - - copyTranslationImpl = { [weak self] text, entities in - storeMessageTextInPasteboard(text, entities: entities) - - let content = UndoOverlayContent.copy(text: presentationData.strings.Conversation_TextCopied) - self?.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) - self?.dismissAnimated() - } - - changeLanguageImpl = { [weak self] fromLang, toLang, completion in - let pushController = self?.pushController - let presentController = self?.presentController - let controller = languageSelectionController(context: context, forceTheme: forceTheme, fromLanguage: fromLang, toLanguage: toLang, completion: { fromLang, toLang in - let controller = TranslateScreen(context: context, forceTheme: forceTheme, text: text, entities: entities, canCopy: canCopy, fromLanguage: fromLang, toLanguage: toLang, ignoredLanguages: ignoredLanguages, replaceText: replaceText) - controller.pushController = pushController ?? { _ in } - controller.presentController = presentController ?? { _ in } - presentController?(controller) - }) - - self?.dismissAnimated() - - pushController?(controller) - } - - openCocoonInfoImpl = { [weak self] in - let controller = context.sharedContext.makeCocoonInfoScreen(context: context) - self?.pushController(controller) - } - } - - required public init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - public func dismissAnimated() { - if let view = self.node.hostView.findTaggedView(tag: ResizableSheetComponent.View.Tag()) as? ResizableSheetComponent.View { - view.dismissAnimated() - } - } -} public func presentTranslateScreen( context: AccountContext, @@ -1205,11 +38,6 @@ public func presentTranslateScreen( if useSystemTranslation { presentSystemTranslateScreen(context: context, text: text) } else { - let controller = TranslateScreen(context: context, text: text, entities: entities, canCopy: canCopy, fromLanguage: fromLanguage, toLanguage: toLanguage, ignoredLanguages: ignoredLanguages, replaceText: replaceText, translateChat: translateChat) - controller.pushController = pushController - controller.presentController = presentController - controller.wasDismissed = wasDismissed - display(controller) } } @@ -1258,17 +86,3 @@ struct TranslateScreenHostingView: View { } } } - -private final class GiftViewContextReferenceContentSource: ContextReferenceContentSource { - private let controller: ViewController - private let sourceView: UIView - - init(controller: ViewController, sourceView: UIView) { - self.controller = controller - self.sourceView = sourceView - } - - func transitionInfo() -> ContextControllerReferenceViewInfo? { - return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds) - } -} diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 47ee5b384d..e441db4930 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -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