Various fixes
This commit is contained in:
parent
98d148e08d
commit
622f9d970a
48 changed files with 499 additions and 1876 deletions
|
|
@ -1331,7 +1331,7 @@ public final class TextProcessingScreenSendContextActions {
|
||||||
|
|
||||||
public enum TextProcessingScreenMode {
|
public enum TextProcessingScreenMode {
|
||||||
case edit(saveRestoreStateId: EnginePeer.Id?, completion: (TextWithEntities) -> Void, send: ((TextWithEntities) -> Void)?, sendContextActions: TextProcessingScreenSendContextActions?)
|
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)
|
case preview(style: TelegramComposeAIMessageMode.CloudStyle.Custom, authorPeer: EnginePeer?, initialPreview: AIMessageStylePreview?, isAlreadyAdded: Bool, added: () -> Void)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ swift_library(
|
||||||
"//submodules/ContextUI",
|
"//submodules/ContextUI",
|
||||||
"//submodules/UndoUI",
|
"//submodules/UndoUI",
|
||||||
"//submodules/TranslateUI",
|
"//submodules/TranslateUI",
|
||||||
|
"//submodules/TelegramUI/Components/TextProcessingScreen",
|
||||||
"//submodules/ComponentFlow:ComponentFlow",
|
"//submodules/ComponentFlow:ComponentFlow",
|
||||||
"//submodules/Components/ViewControllerComponent:ViewControllerComponent",
|
"//submodules/Components/ViewControllerComponent:ViewControllerComponent",
|
||||||
"//submodules/Components/MultilineTextComponent:MultilineTextComponent",
|
"//submodules/Components/MultilineTextComponent:MultilineTextComponent",
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import AppBundle
|
||||||
import InstantPageUI
|
import InstantPageUI
|
||||||
import UndoUI
|
import UndoUI
|
||||||
import TranslateUI
|
import TranslateUI
|
||||||
|
import TextProcessingScreen
|
||||||
import ContextUI
|
import ContextUI
|
||||||
import Pasteboard
|
import Pasteboard
|
||||||
import SaveToCameraRoll
|
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)
|
let (canTranslate, language) = canTranslateText(context: context, text: text, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: false, ignoredLanguages: translationSettings.ignoredLanguages)
|
||||||
if canTranslate {
|
if canTranslate {
|
||||||
actions.append(ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuTranslate, accessibilityLabel: strings.Conversation_ContextMenuTranslate), action: { [weak self] in
|
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)
|
Task { @MainActor [weak self] in
|
||||||
controller.pushController = { [weak self] c in
|
guard let self else {
|
||||||
self?.getNavigationController()?._keepModalDismissProgress = true
|
return
|
||||||
self?.push(c)
|
}
|
||||||
|
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)
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1150,7 +1150,7 @@ public final class Camera {
|
||||||
|
|
||||||
public static func isDualCameraSupported(forRoundVideo: Bool = false) -> Bool {
|
public static func isDualCameraSupported(forRoundVideo: Bool = false) -> Bool {
|
||||||
if #available(iOS 13.0, *), AVCaptureMultiCamSession.isMultiCamSupported && !DeviceModel.current.isIpad {
|
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 false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|
|
||||||
|
|
@ -3134,9 +3134,10 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
let messageTypeIconScale = min(1.0, item.presentationData.fontSize.itemListBaseFontSize / 17.0)
|
||||||
|
|
||||||
if let currentMessageTypeIcon {
|
if let currentMessageTypeIcon {
|
||||||
textLeftCutout += currentMessageTypeIcon.size.width
|
textLeftCutout += currentMessageTypeIcon.size.width * messageTypeIconScale
|
||||||
if !contentImageSpecs.isEmpty {
|
if !contentImageSpecs.isEmpty {
|
||||||
textLeftCutout += forwardedIconSpacing
|
textLeftCutout += forwardedIconSpacing
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -3654,6 +3655,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||||
textMaxWidth -= 18.0
|
textMaxWidth -= 18.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let textLineSpacing: CGFloat = min(0.2, item.presentationData.fontSize.itemListBaseFontSize * 0.2 / 17.0)
|
||||||
let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(
|
let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(
|
||||||
attributedString: textAttributedString,
|
attributedString: textAttributedString,
|
||||||
backgroundColor: nil,
|
backgroundColor: nil,
|
||||||
|
|
@ -3661,7 +3663,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||||
truncationType: .end,
|
truncationType: .end,
|
||||||
constrainedSize: CGSize(width: textMaxWidth, height: .greatestFiniteMagnitude),
|
constrainedSize: CGSize(width: textMaxWidth, height: .greatestFiniteMagnitude),
|
||||||
alignment: .natural,
|
alignment: .natural,
|
||||||
lineSpacing: 0.2,
|
lineSpacing: textLineSpacing,
|
||||||
cutout: textCutout,
|
cutout: textCutout,
|
||||||
insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)
|
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
|
strongSelf.statusNode.fontSize = item.presentationData.fontSize.itemListBaseFontSize
|
||||||
let _ = strongSelf.statusNode.transitionToState(statusState, animated: animateContent)
|
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
|
var nextBadgeX: CGFloat = contentRect.maxX
|
||||||
if let _ = currentBadgeBackgroundImage {
|
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)
|
transition.updateFrame(node: strongSelf.badgeNode, frame: badgeFrame)
|
||||||
nextBadgeX -= badgeLayout.width + 6.0
|
nextBadgeX -= badgeLayout.width + 6.0
|
||||||
}
|
}
|
||||||
|
|
||||||
if currentMentionBadgeImage != nil || currentBadgeBackgroundImage != nil {
|
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)
|
transition.updateFrame(node: strongSelf.mentionBadgeNode, frame: badgeFrame)
|
||||||
nextBadgeX -= mentionBadgeLayout.width + 6.0
|
nextBadgeX -= mentionBadgeLayout.width + 6.0
|
||||||
|
|
@ -4401,7 +4403,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||||
strongSelf.pinnedIconNode.isHidden = false
|
strongSelf.pinnedIconNode.isHidden = false
|
||||||
|
|
||||||
let pinnedIconSize = currentPinnedIconImage.size
|
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
|
strongSelf.pinnedIconNode.frame = pinnedIconFrame
|
||||||
nextBadgeX -= pinnedIconSize.width + 6.0
|
nextBadgeX -= pinnedIconSize.width + 6.0
|
||||||
|
|
@ -4836,8 +4838,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||||
if strongSelf.forwardedIconNode.supernode == nil {
|
if strongSelf.forwardedIconNode.supernode == nil {
|
||||||
strongSelf.mainContentContainerNode.addSubnode(strongSelf.forwardedIconNode)
|
strongSelf.mainContentContainerNode.addSubnode(strongSelf.forwardedIconNode)
|
||||||
}
|
}
|
||||||
transition.updateFrame(node: strongSelf.forwardedIconNode, frame: CGRect(origin: messageTypeIconOffset, size: messageTypeIconImage.size))
|
let iconSize = CGSize(width: messageTypeIconImage.size.width * messageTypeIconScale, height: messageTypeIconImage.size.height * messageTypeIconScale)
|
||||||
mediaPreviewOffset.x += messageTypeIconImage.size.width + forwardedIconSpacing
|
transition.updateFrame(node: strongSelf.forwardedIconNode, frame: CGRect(origin: messageTypeIconOffset, size: iconSize))
|
||||||
|
mediaPreviewOffset.x += messageTypeIconImage.size.width * messageTypeIconScale + forwardedIconSpacing
|
||||||
} else if strongSelf.forwardedIconNode.supernode != nil {
|
} else if strongSelf.forwardedIconNode.supernode != nil {
|
||||||
strongSelf.forwardedIconNode.removeFromSupernode()
|
strongSelf.forwardedIconNode.removeFromSupernode()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2070,7 +2070,13 @@ private final class DrawingScreenComponent: CombinedComponent {
|
||||||
})
|
})
|
||||||
.opacity(controlsAreVisible ? 1.0 : 0.0)
|
.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(
|
let mode = mode.update(
|
||||||
component: ModeComponent(
|
component: ModeComponent(
|
||||||
isTablet: false,
|
isTablet: false,
|
||||||
|
|
@ -2093,7 +2099,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
||||||
},
|
},
|
||||||
tag: modeTag
|
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
|
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)
|
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)
|
||||||
|
|
|
||||||
|
|
@ -332,9 +332,9 @@ final class ModeComponent: Component {
|
||||||
if isTablet {
|
if isTablet {
|
||||||
spacing = 9.0
|
spacing = 9.0
|
||||||
} else {
|
} else {
|
||||||
if availableSize.width < 200.0 {
|
if availableSize.width < 270.0 {
|
||||||
inset = 20.0
|
inset = 16.0
|
||||||
spacing = 24.0
|
spacing = 20.0
|
||||||
} else {
|
} else {
|
||||||
spacing = 30.0
|
spacing = 30.0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ swift_library(
|
||||||
"//submodules/UndoUI:UndoUI",
|
"//submodules/UndoUI:UndoUI",
|
||||||
"//submodules/InvisibleInkDustNode:InvisibleInkDustNode",
|
"//submodules/InvisibleInkDustNode:InvisibleInkDustNode",
|
||||||
"//submodules/TranslateUI:TranslateUI",
|
"//submodules/TranslateUI:TranslateUI",
|
||||||
|
"//submodules/TelegramUI/Components/TextProcessingScreen",
|
||||||
"//submodules/ShimmerEffect:ShimmerEffect",
|
"//submodules/ShimmerEffect:ShimmerEffect",
|
||||||
"//submodules/Utils/RangeSet:RangeSet",
|
"//submodules/Utils/RangeSet:RangeSet",
|
||||||
"//submodules/TelegramUI/Components/TextNodeWithEntities:TextNodeWithEntities",
|
"//submodules/TelegramUI/Components/TextNodeWithEntities:TextNodeWithEntities",
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import MultiAnimationRenderer
|
||||||
import Pasteboard
|
import Pasteboard
|
||||||
import Speak
|
import Speak
|
||||||
import TranslateUI
|
import TranslateUI
|
||||||
|
import TextProcessingScreen
|
||||||
import TelegramNotices
|
import TelegramNotices
|
||||||
import SolidRoundedButtonNode
|
import SolidRoundedButtonNode
|
||||||
import UrlHandling
|
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 (_, 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 _ = 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)
|
Task { @MainActor [weak self] in
|
||||||
translateController.pushController = { [weak self] c in
|
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
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:
|
case .quote:
|
||||||
break
|
break
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import ImageContentAnalysis
|
||||||
import TextSelectionNode
|
import TextSelectionNode
|
||||||
import Speak
|
import Speak
|
||||||
import TranslateUI
|
import TranslateUI
|
||||||
|
import TextProcessingScreen
|
||||||
import UndoUI
|
import UndoUI
|
||||||
import ContextUI
|
import ContextUI
|
||||||
import SaveToCameraRoll
|
import SaveToCameraRoll
|
||||||
|
|
@ -408,15 +409,24 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||||
}
|
}
|
||||||
case .translate:
|
case .translate:
|
||||||
if let parentController = strongSelf.baseNavigationController()?.topViewController as? ViewController {
|
if let parentController = strongSelf.baseNavigationController()?.topViewController as? ViewController {
|
||||||
let controller = TranslateScreen(context: strongSelf.context, text: string, canCopy: true, fromLanguage: nil)
|
Task { @MainActor [weak parentController, weak strongSelf] in
|
||||||
controller.pushController = { [weak parentController] c in
|
guard let strongSelf else {
|
||||||
(parentController?.navigationController as? NavigationController)?._keepModalDismissProgress = true
|
return
|
||||||
parentController?.push(c)
|
}
|
||||||
|
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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ swift_library(
|
||||||
"//submodules/LocationResources:LocationResources",
|
"//submodules/LocationResources:LocationResources",
|
||||||
"//submodules/UndoUI:UndoUI",
|
"//submodules/UndoUI:UndoUI",
|
||||||
"//submodules/TranslateUI:TranslateUI",
|
"//submodules/TranslateUI:TranslateUI",
|
||||||
|
"//submodules/TelegramUI/Components/TextProcessingScreen",
|
||||||
|
"//submodules/Pasteboard",
|
||||||
"//submodules/Tuples:Tuples",
|
"//submodules/Tuples:Tuples",
|
||||||
"//third-party/SwiftMath:SwiftMath",
|
"//third-party/SwiftMath:SwiftMath",
|
||||||
"//submodules/ComponentFlow:ComponentFlow",
|
"//submodules/ComponentFlow:ComponentFlow",
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ import LocationUI
|
||||||
import UndoUI
|
import UndoUI
|
||||||
import ContextUI
|
import ContextUI
|
||||||
import TranslateUI
|
import TranslateUI
|
||||||
|
import TextProcessingScreen
|
||||||
|
import Pasteboard
|
||||||
|
|
||||||
final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
private weak var controller: InstantPageController?
|
private weak var controller: InstantPageController?
|
||||||
|
|
@ -1528,11 +1530,11 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
translationSettings = TranslationSettings.defaultSettings
|
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
|
var actions: [ContextMenuAction] = [ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuCopy, accessibilityLabel: strings.Conversation_ContextMenuCopy), action: { [weak self] in
|
||||||
UIPasteboard.general.string = text
|
UIPasteboard.general.string = text
|
||||||
|
|
||||||
if let strongSelf = self {
|
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)
|
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
|
}), 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)
|
let (canTranslate, language) = canTranslateText(context: context, text: text, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: false, ignoredLanguages: translationSettings.ignoredLanguages)
|
||||||
if canTranslate {
|
if canTranslate {
|
||||||
actions.append(ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuTranslate, accessibilityLabel: strings.Conversation_ContextMenuTranslate), action: { [weak self] in
|
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)
|
Task { @MainActor [weak self] in
|
||||||
controller.pushController = { [weak self] c in
|
guard let self else {
|
||||||
(self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true
|
return
|
||||||
self?.controller?.push(c)
|
}
|
||||||
|
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)
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ private struct ItemListRevealOptionLayoutMetrics {
|
||||||
}
|
}
|
||||||
|
|
||||||
var slotShapeInset: CGFloat {
|
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 {
|
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 compactShapeSize = CGSize(width: 60.0, height: 32.0)
|
||||||
let regularContentHeight = regularShapeSize.height + optionTitleSpacing + ceil(titleFont.lineHeight)
|
let regularContentHeight = regularShapeSize.height + optionTitleSpacing + ceil(titleFont.lineHeight)
|
||||||
if height < regularContentHeight || !hasVisualIcons {
|
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 {
|
} else {
|
||||||
return ItemListRevealOptionLayoutMetrics(shapeSize: regularShapeSize, slotWidth: 60.0, titleWidth: 60.0, iconMaxSide: 40.0, cornerRadius: 25.0, expandedIconInset: 20.0)
|
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 contentContainerNode: ASDisplayNode
|
||||||
private let backgroundNode: ASDisplayNode
|
private let backgroundNode: ASDisplayNode
|
||||||
private let highlightNode: ASDisplayNode
|
private let highlightNode: ASDisplayNode
|
||||||
private let titleNode: ASTextNode
|
private let titleNode: ImmediateTextNode
|
||||||
private let iconNode: ASImageNode?
|
private let iconNode: ASImageNode?
|
||||||
private let animationNode: SimpleAnimationNode?
|
private let animationNode: SimpleAnimationNode?
|
||||||
|
|
||||||
|
|
@ -187,7 +187,7 @@ private final class ItemListRevealOptionNode: ASDisplayNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
var titleWidthForGroupPillSizing: CGFloat {
|
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 {
|
if self.displaysTitleInsidePill {
|
||||||
titleWidth += optionIconlessTitleAdditionalHorizontalPadding
|
titleWidth += optionIconlessTitleAdditionalHorizontalPadding
|
||||||
}
|
}
|
||||||
|
|
@ -199,9 +199,11 @@ private final class ItemListRevealOptionNode: ASDisplayNode {
|
||||||
self.backgroundNode = ASDisplayNode()
|
self.backgroundNode = ASDisplayNode()
|
||||||
self.highlightNode = ASDisplayNode()
|
self.highlightNode = ASDisplayNode()
|
||||||
|
|
||||||
self.titleNode = ASTextNode()
|
self.titleNode = ImmediateTextNode()
|
||||||
|
self.titleNode.displaysAsynchronously = false
|
||||||
self.titleNode.maximumNumberOfLines = 1
|
self.titleNode.maximumNumberOfLines = 1
|
||||||
self.titleNode.truncationMode = .byTruncatingTail
|
self.titleNode.truncationMode = .byTruncatingTail
|
||||||
|
self.titleNode.textAlignment = .center
|
||||||
|
|
||||||
let displaysTitleInsidePill: Bool
|
let displaysTitleInsidePill: Bool
|
||||||
if case .none = icon {
|
if case .none = icon {
|
||||||
|
|
@ -361,20 +363,27 @@ private final class ItemListRevealOptionNode: ASDisplayNode {
|
||||||
let bounds = CGRect(origin: CGPoint(), size: self.bounds.size)
|
let bounds = CGRect(origin: CGPoint(), size: self.bounds.size)
|
||||||
transition.updateFrame(node: self.contentContainerNode, frame: bounds)
|
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 pillSize = metrics.shapeSize
|
||||||
let shapeY: CGFloat
|
let shapeY: CGFloat
|
||||||
if self.displaysTitleInsidePill {
|
if self.displaysTitleInsidePill {
|
||||||
shapeY = floor((bounds.height - pillSize.height) / 2.0)
|
shapeY = floorToScreenPixels((bounds.height - pillSize.height) / 2.0)
|
||||||
} else {
|
} 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
|
let shapeFrameX: CGFloat
|
||||||
if isStretched {
|
if isStretched {
|
||||||
shapeFrameX = isLeft ? 0.0 : bounds.width - pillSize.width
|
shapeFrameX = isLeft ? 0.0 : bounds.width - pillSize.width
|
||||||
} else {
|
} 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 shapeFrame = CGRect(origin: CGPoint(x: shapeFrameX, y: shapeY), size: pillSize)
|
||||||
let backgroundFrame: CGRect
|
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)
|
var imageSize = CGSize(width: animationNode.size.width * self.animationScale, height: animationNode.size.height * self.animationScale)
|
||||||
let imageMaxSide = max(imageSize.width, imageSize.height)
|
let imageMaxSide = max(imageSize.width, imageSize.height)
|
||||||
if imageMaxSide > metrics.iconMaxSide {
|
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))
|
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 {
|
if isPrimary {
|
||||||
didApplyManualIconCenter = true
|
didApplyManualIconCenter = true
|
||||||
|
|
@ -493,7 +503,6 @@ private final class ItemListRevealOptionNode: ASDisplayNode {
|
||||||
|
|
||||||
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||||
let metrics = ItemListRevealOptionLayoutMetrics.metrics(for: constrainedSize.height, hasVisualIcons: !self.displaysTitleInsidePill)
|
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)
|
return CGSize(width: metrics.slotWidth, height: constrainedSize.height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -510,6 +519,7 @@ public final class ItemListRevealOptionsNode: ASDisplayNode {
|
||||||
private var optionNodes: [ItemListRevealOptionNode] = []
|
private var optionNodes: [ItemListRevealOptionNode] = []
|
||||||
private var revealOffset: CGFloat = 0.0
|
private var revealOffset: CGFloat = 0.0
|
||||||
private var sideInset: 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) {
|
public init(optionSelected: @escaping (ItemListRevealOption) -> Void, tapticAction: @escaping () -> Void) {
|
||||||
self.optionSelected = optionSelected
|
self.optionSelected = optionSelected
|
||||||
|
|
@ -564,20 +574,27 @@ public final class ItemListRevealOptionsNode: ASDisplayNode {
|
||||||
self.optionsContainerNode.addSubnode(node)
|
self.optionsContainerNode.addSubnode(node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.currentMetrics = nil
|
||||||
self.invalidateCalculatedLayout()
|
self.invalidateCalculatedLayout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func layoutMetrics(for height: CGFloat) -> ItemListRevealOptionLayoutMetrics {
|
private func layoutMetrics(for containerSize: CGSize) -> ItemListRevealOptionLayoutMetrics {
|
||||||
let metrics = ItemListRevealOptionLayoutMetrics.metrics(for: height, hasVisualIcons: self.options.contains(where: { $0.icon.hasVisualIcon }))
|
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
|
let maxTitleWidth = self.optionNodes.reduce(0.0) { result, node in
|
||||||
return max(result, node.titleWidthForGroupPillSizing)
|
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 {
|
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 {
|
for node in self.optionNodes {
|
||||||
let _ = node.measure(constrainedSize)
|
let _ = node.measure(constrainedSize)
|
||||||
}
|
}
|
||||||
|
|
@ -595,7 +612,7 @@ public final class ItemListRevealOptionsNode: ASDisplayNode {
|
||||||
if size.width.isLessThanOrEqualTo(0.0) || self.optionNodes.isEmpty {
|
if size.width.isLessThanOrEqualTo(0.0) || self.optionNodes.isEmpty {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let metrics = self.layoutMetrics(for: size.height)
|
let metrics = self.layoutMetrics(for: size)
|
||||||
let revealedDistance = abs(self.revealOffset)
|
let revealedDistance = abs(self.revealOffset)
|
||||||
let boundedRevealedDistance = min(revealedDistance, size.width)
|
let boundedRevealedDistance = min(revealedDistance, size.width)
|
||||||
let overswipeDistance = max(0.0, revealedDistance - size.width)
|
let overswipeDistance = max(0.0, revealedDistance - size.width)
|
||||||
|
|
|
||||||
|
|
@ -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)))
|
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)
|
transition.updatePosition(node: strongSelf.titleNode, position: titleFrame.origin)
|
||||||
strongSelf.titleNode.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
|
strongSelf.titleNode.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@
|
||||||
- (void)editorTransitionIn;
|
- (void)editorTransitionIn;
|
||||||
- (void)editorTransitionOut;
|
- (void)editorTransitionOut;
|
||||||
|
|
||||||
|
- (bool)canBeginEditingCaption;
|
||||||
- (void)beginEditingCaption;
|
- (void)beginEditingCaption;
|
||||||
- (void)setupGifEditing;
|
- (void)setupGifEditing;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,4 +39,7 @@
|
||||||
|
|
||||||
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
|
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
|
||||||
|
|
||||||
|
- (bool)canBeginEditingCaption;
|
||||||
|
- (void)beginEditingCaption;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
||||||
|
|
@ -593,6 +593,10 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber *
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (bool)canBeginEditingCaption {
|
||||||
|
return _hasCaptions && _captionMixin != nil && _captionMixin.inputPanel != nil && !_captionMixin.inputPanelView.hidden && !_captionMixin.editing;
|
||||||
|
}
|
||||||
|
|
||||||
- (void)beginEditingCaption {
|
- (void)beginEditingCaption {
|
||||||
[_captionMixin activateInput];
|
[_captionMixin activateInput];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,20 @@ static void adjustFrameRate(CAAnimation *animation) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool TGModernGalleryViewHasFirstResponder(UIView *view) {
|
||||||
|
if (view.isFirstResponder) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (UIView *subview in view.subviews) {
|
||||||
|
if (TGModernGalleryViewHasFirstResponder(subview)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@interface TGModernGalleryController () <UIScrollViewDelegate, TGModernGalleryScrollViewDelegate, TGModernGalleryItemViewDelegate, TGKeyCommandResponder>
|
@interface TGModernGalleryController () <UIScrollViewDelegate, TGModernGalleryScrollViewDelegate, TGModernGalleryItemViewDelegate, TGKeyCommandResponder>
|
||||||
{
|
{
|
||||||
NSMutableDictionary *_reusableItemViewsByIdentifier;
|
NSMutableDictionary *_reusableItemViewsByIdentifier;
|
||||||
|
|
@ -1759,6 +1773,28 @@ static CGFloat transformRotation(CGAffineTransform transform)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (bool)_canBeginEditingCaptionFromKeyCommand
|
||||||
|
{
|
||||||
|
UIView<TGModernGalleryInterfaceView> *interfaceView = _view.interfaceView;
|
||||||
|
if (interfaceView == nil) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (![interfaceView respondsToSelector:@selector(beginEditingCaption)]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([interfaceView respondsToSelector:@selector(canBeginEditingCaption)] && ![interfaceView canBeginEditingCaption]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TGModernGalleryViewHasFirstResponder(_view)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
- (void)processKeyCommand:(UIKeyCommand *)keyCommand
|
- (void)processKeyCommand:(UIKeyCommand *)keyCommand
|
||||||
{
|
{
|
||||||
if ([keyCommand.input isEqualToString:UIKeyInputLeftArrow])
|
if ([keyCommand.input isEqualToString:UIKeyInputLeftArrow])
|
||||||
|
|
@ -1777,17 +1813,27 @@ static CGFloat transformRotation(CGAffineTransform transform)
|
||||||
{
|
{
|
||||||
_view.transitionOut(0.0f);
|
_view.transitionOut(0.0f);
|
||||||
}
|
}
|
||||||
|
else if ([keyCommand.input isEqualToString:@"\r"])
|
||||||
|
{
|
||||||
|
if ([self _canBeginEditingCaptionFromKeyCommand])
|
||||||
|
[_view.interfaceView beginEditingCaption];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray *)availableKeyCommands
|
- (NSArray *)availableKeyCommands
|
||||||
{
|
{
|
||||||
return @
|
NSMutableArray *commands = [[NSMutableArray alloc] initWithArray:@
|
||||||
[
|
[
|
||||||
[TGKeyCommand keyCommandWithTitle:nil input:UIKeyInputLeftArrow modifierFlags:0],
|
[TGKeyCommand keyCommandWithTitle:nil input:UIKeyInputLeftArrow modifierFlags:0],
|
||||||
[TGKeyCommand keyCommandWithTitle:nil input:UIKeyInputRightArrow modifierFlags:0],
|
[TGKeyCommand keyCommandWithTitle:nil input:UIKeyInputRightArrow modifierFlags:0],
|
||||||
[TGKeyCommand keyCommandWithTitle:nil input:UIKeyInputEscape modifierFlags:0],
|
[TGKeyCommand keyCommandWithTitle:nil input:UIKeyInputEscape modifierFlags:0],
|
||||||
[TGKeyCommand keyCommandWithTitle:nil input:@"\t" 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
|
- (bool)isExclusive
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -471,7 +471,7 @@ private final class MediaPickerPhotoToolbarComponent: Component {
|
||||||
var doneWidth: CGFloat = toolbarButtonSide
|
var doneWidth: CGFloat = toolbarButtonSide
|
||||||
switch component.doneButtonType {
|
switch component.doneButtonType {
|
||||||
case TGPhotoEditorDoneButtonSend:
|
case TGPhotoEditorDoneButtonSend:
|
||||||
doneIconName = "Chat/Input/Text/SendIcon"
|
doneIconName = "Media Editor/Send"
|
||||||
doneWidth = 46.0
|
doneWidth = 46.0
|
||||||
case TGPhotoEditorDoneButtonSchedule:
|
case TGPhotoEditorDoneButtonSchedule:
|
||||||
doneIconName = "Chat/Input/ScheduleIcon"
|
doneIconName = "Chat/Input/ScheduleIcon"
|
||||||
|
|
|
||||||
|
|
@ -1179,9 +1179,12 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
|
||||||
guardBotId = currentGuardBotId
|
guardBotId = currentGuardBotId
|
||||||
approveMembersInfo += " " + presentationData.strings.Group_Setup_ApproveNewMembersManagedBy("[@\(guardBotUsername)](guardbot)").string
|
approveMembersInfo += " " + presentationData.strings.Group_Setup_ApproveNewMembersManagedBy("[@\(guardBotUsername)](guardbot)").string
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.append(.approveMembers(presentationData.theme, approveMembersTitle, approveMembers))
|
if !isGroup && selectedType == .publicChannel {
|
||||||
entries.append(.approveMembersInfo(presentationData.theme, approveMembersInfo, guardBotId))
|
} 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(.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))
|
entries.append(.forwardingDisabled(presentationData.theme, presentationData.strings.Group_Setup_ForwardingDisabled, !forwardingEnabled))
|
||||||
|
|
|
||||||
|
|
@ -178,6 +178,11 @@ public func archiveSettingsController(context: AccountContext) -> ViewController
|
||||||
)
|
)
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
|> map { presentationData, settings, appConfiguration, accountPeer -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
|> 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 isPremium = accountPeer?.isPremium ?? false
|
||||||
let isPremiumDisabled = PremiumConfiguration.with(appConfiguration: appConfiguration).isPremiumDisabled
|
let isPremiumDisabled = PremiumConfiguration.with(appConfiguration: appConfiguration).isPremiumDisabled
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -292,7 +292,7 @@ public func logoutOptionsController(context: AccountContext, navigationControlle
|
||||||
navigationController?.pushViewController(value, animated: false)
|
navigationController?.pushViewController(value, animated: false)
|
||||||
}
|
}
|
||||||
presentControllerImpl = { [weak controller] value, arguments in
|
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
|
replaceTopControllerImpl = { [weak navigationController] c in
|
||||||
navigationController?.replaceTopController(c, animated: true)
|
navigationController?.replaceTopController(c, animated: true)
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ swift_library(
|
||||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||||
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
|
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
|
||||||
"//submodules/TelegramUI/Components/Ads/AdsReportScreen",
|
"//submodules/TelegramUI/Components/Ads/AdsReportScreen",
|
||||||
|
"//submodules/TelegramUI/Components/LottieComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import UndoUI
|
||||||
import GlassBarButtonComponent
|
import GlassBarButtonComponent
|
||||||
import ButtonComponent
|
import ButtonComponent
|
||||||
import AdsReportScreen
|
import AdsReportScreen
|
||||||
|
import LottieComponent
|
||||||
|
|
||||||
private let moreTag = GenericComponentViewTag()
|
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)))
|
context.add(iconBackground.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + iconBackground.size.height / 2.0)))
|
||||||
|
|
||||||
let icon = icon.update(
|
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),
|
availableSize: CGSize(width: 90.0, height: 90.0),
|
||||||
transition: .immediate
|
transition: .immediate
|
||||||
)
|
)
|
||||||
|
|
@ -523,6 +524,8 @@ private final class AdsInfoSheetComponent: CombinedComponent {
|
||||||
let sheet = Child(ResizableSheetComponent<(EnvironmentType)>.self)
|
let sheet = Child(ResizableSheetComponent<(EnvironmentType)>.self)
|
||||||
let animateOut = StoredActionSlot(Action<Void>.self)
|
let animateOut = StoredActionSlot(Action<Void>.self)
|
||||||
|
|
||||||
|
let moreButtonPlayOnce = ActionSlot<Void>()
|
||||||
|
|
||||||
return { context in
|
return { context in
|
||||||
let environment = context.environment[EnvironmentType.self]
|
let environment = context.environment[EnvironmentType.self]
|
||||||
let controller = environment.controller
|
let controller = environment.controller
|
||||||
|
|
@ -569,14 +572,19 @@ private final class AdsInfoSheetComponent: CombinedComponent {
|
||||||
component: AnyComponentWithIdentity(
|
component: AnyComponentWithIdentity(
|
||||||
id: "more",
|
id: "more",
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
BundleIconComponent(
|
LottieComponent(
|
||||||
name: "Chat/Context Menu/More",
|
content: LottieComponent.AppBundleContent(
|
||||||
tintColor: theme.chat.inputPanel.panelControlColor
|
name: "anim_morewide"
|
||||||
|
),
|
||||||
|
color: theme.chat.inputPanel.panelControlColor,
|
||||||
|
size: CGSize(width: 34.0, height: 34.0),
|
||||||
|
playOnce: moreButtonPlayOnce
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
action: { _ in
|
action: { _ in
|
||||||
(controller() as? AdsInfoScreen)?.infoPressed()
|
(controller() as? AdsInfoScreen)?.infoPressed()
|
||||||
|
moreButtonPlayOnce.invoke(Void())
|
||||||
},
|
},
|
||||||
tag: moreTag
|
tag: moreTag
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,11 @@ public final class NavigationButtonComponent: Component {
|
||||||
|
|
||||||
switch component.content {
|
switch component.content {
|
||||||
case let .text(title, isBold):
|
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:
|
case .more:
|
||||||
isMore = true
|
isMore = true
|
||||||
case let .icon(imageNameValue):
|
case let .icon(imageNameValue):
|
||||||
|
|
|
||||||
|
|
@ -1764,8 +1764,7 @@ private final class ChatThemeSheetContentComponent: Component {
|
||||||
style: .glass,
|
style: .glass,
|
||||||
color: destructiveColor.withMultipliedAlpha(0.1),
|
color: destructiveColor.withMultipliedAlpha(0.1),
|
||||||
foreground: destructiveColor,
|
foreground: destructiveColor,
|
||||||
pressedColor: destructiveColor.withMultipliedAlpha(0.2),
|
pressedColor: destructiveColor.withMultipliedAlpha(0.2)
|
||||||
cornerRadius: 22.0
|
|
||||||
),
|
),
|
||||||
content: AnyComponentWithIdentity(
|
content: AnyComponentWithIdentity(
|
||||||
id: AnyHashable("resetWallpaper"),
|
id: AnyHashable("resetWallpaper"),
|
||||||
|
|
|
||||||
|
|
@ -632,7 +632,7 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.buttonsContainerNode = SparseNode()
|
self.buttonsContainerNode = SparseNode()
|
||||||
self.buttonsContainerNode.clipsToBounds = true
|
//self.buttonsContainerNode.clipsToBounds = true
|
||||||
|
|
||||||
self.backButtonNodeImpl.color = self.presentationData.theme.buttonColor
|
self.backButtonNodeImpl.color = self.presentationData.theme.buttonColor
|
||||||
self.backButtonNodeImpl.disabledColor = self.presentationData.theme.disabledButtonColor
|
self.backButtonNodeImpl.disabledColor = self.presentationData.theme.disabledButtonColor
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import ContextUI
|
||||||
import AvatarNode
|
import AvatarNode
|
||||||
import PlainButtonComponent
|
import PlainButtonComponent
|
||||||
import ToastComponent
|
import ToastComponent
|
||||||
|
import TextFormat
|
||||||
|
|
||||||
private final class JoinAffiliateProgramScreenComponent: Component {
|
private final class JoinAffiliateProgramScreenComponent: Component {
|
||||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
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),
|
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemSecondaryTextColor),
|
||||||
bold: MarkdownAttributeSet(font: Font.semibold(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),
|
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemAccentColor),
|
||||||
linkAttribute: { url in
|
linkAttribute: { contents in
|
||||||
return ("URL", url)
|
return (TelegramTextAttributes.URL, contents)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
horizontalAlignment: .center,
|
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: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0)
|
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0)
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,7 @@ swift_library(
|
||||||
"//submodules/TelegramUI/Components/Settings/PeerNameColorItem",
|
"//submodules/TelegramUI/Components/Settings/PeerNameColorItem",
|
||||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||||
"//submodules/TelegramUI/Components/TextLoadingEffect",
|
"//submodules/TelegramUI/Components/TextLoadingEffect",
|
||||||
|
"//submodules/TelegramUI/Components/TextProcessingScreen",
|
||||||
"//submodules/TelegramUI/Components/Settings/BirthdayPickerScreen",
|
"//submodules/TelegramUI/Components/Settings/BirthdayPickerScreen",
|
||||||
"//submodules/TelegramUI/Components/Settings/PeerSelectionScreen",
|
"//submodules/TelegramUI/Components/Settings/PeerSelectionScreen",
|
||||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ import AsyncDisplayKit
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import ContextUI
|
import ContextUI
|
||||||
import TranslateUI
|
import TranslateUI
|
||||||
|
import TextProcessingScreen
|
||||||
|
import Pasteboard
|
||||||
import UndoUI
|
import UndoUI
|
||||||
|
|
||||||
extension PeerInfoScreenNode {
|
extension PeerInfoScreenNode {
|
||||||
|
|
@ -105,16 +107,24 @@ extension PeerInfoScreenNode {
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let controller = TranslateScreen(context: self.context, text: bioText, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages)
|
Task { @MainActor [weak self] in
|
||||||
controller.pushController = { [weak self] c in
|
guard let self, let parentController = self.controller else {
|
||||||
(self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true
|
return
|
||||||
self?.controller?.push(c)
|
}
|
||||||
|
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))
|
|
||||||
}
|
}
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ import TelegramCore
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import UndoUI
|
import UndoUI
|
||||||
import TranslateUI
|
import TranslateUI
|
||||||
|
import TextProcessingScreen
|
||||||
|
import Pasteboard
|
||||||
import TelegramStringFormatting
|
import TelegramStringFormatting
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
|
|
||||||
|
|
@ -70,16 +72,22 @@ extension PeerInfoScreenNode {
|
||||||
let (canTranslate, language) = canTranslateText(context: context, text: text, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: false, ignoredLanguages: translationSettings.ignoredLanguages)
|
let (canTranslate, language) = canTranslateText(context: context, text: text, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: false, ignoredLanguages: translationSettings.ignoredLanguages)
|
||||||
if canTranslate {
|
if canTranslate {
|
||||||
actions.append(ContextMenuAction(content: .text(title: presentationData.strings.Conversation_ContextMenuTranslate, accessibilityLabel: presentationData.strings.Conversation_ContextMenuTranslate), action: { [weak self] in
|
actions.append(ContextMenuAction(content: .text(title: presentationData.strings.Conversation_ContextMenuTranslate, accessibilityLabel: presentationData.strings.Conversation_ContextMenuTranslate), action: { [weak self] in
|
||||||
|
Task { @MainActor [weak self] in
|
||||||
let controller = TranslateScreen(context: context, text: text, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages)
|
guard let self, let parentController = self.controller else {
|
||||||
controller.pushController = { [weak self] c in
|
return
|
||||||
(self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true
|
}
|
||||||
self?.controller?.push(c)
|
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))
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -186,6 +186,7 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent {
|
||||||
let component = context.component
|
let component = context.component
|
||||||
let scrollEnvironment = context.environment[ScrollChildEnvironment.self].value
|
let scrollEnvironment = context.environment[ScrollChildEnvironment.self].value
|
||||||
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
|
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
|
||||||
|
let controller = environment.controller
|
||||||
let state = context.state
|
let state = context.state
|
||||||
|
|
||||||
state.products = component.products
|
state.products = component.products
|
||||||
|
|
@ -475,7 +476,10 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tapAction: { attributes, _ in
|
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: {},
|
environment: {},
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,7 @@ swift_library(
|
||||||
"//submodules/TextSelectionNode",
|
"//submodules/TextSelectionNode",
|
||||||
"//submodules/Pasteboard",
|
"//submodules/Pasteboard",
|
||||||
"//submodules/Speak",
|
"//submodules/Speak",
|
||||||
|
"//submodules/TelegramUI/Components/TextProcessingScreen",
|
||||||
"//submodules/TranslateUI",
|
"//submodules/TranslateUI",
|
||||||
"//submodules/TelegramNotices",
|
"//submodules/TelegramNotices",
|
||||||
"//submodules/MediaPlayer:UniversalMediaPlayer",
|
"//submodules/MediaPlayer:UniversalMediaPlayer",
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,8 @@ import ChatScheduleTimeController
|
||||||
import StoryStealthModeSheetScreen
|
import StoryStealthModeSheetScreen
|
||||||
import Speak
|
import Speak
|
||||||
import TranslateUI
|
import TranslateUI
|
||||||
|
import TextProcessingScreen
|
||||||
|
import Pasteboard
|
||||||
import TelegramNotices
|
import TelegramNotices
|
||||||
import ObjectiveC
|
import ObjectiveC
|
||||||
import LocationUI
|
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 _ = 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)
|
Task { @MainActor [weak self, weak view] in
|
||||||
translateController.pushController = { [weak view] c in
|
guard let self, let view, let component = view.component else {
|
||||||
guard let view, let component = view.component else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
component.controller()?.push(c)
|
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
}
|
let translateController = await TextProcessingScreen(
|
||||||
translateController.presentController = { [weak view] c in
|
context: component.context,
|
||||||
guard let view, let component = view.component else {
|
theme: defaultDarkPresentationTheme,
|
||||||
return
|
mode: .translate(fromLanguage: language, applyResult: nil),
|
||||||
}
|
inputText: TextWithEntities(text: text, entities: entities),
|
||||||
component.controller()?.present(c, in: .window(.root))
|
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
|
self.actionSheet = translateController
|
||||||
view.updateIsProgressPaused()
|
|
||||||
|
|
||||||
translateController.wasDismissed = { [weak self, weak view] in
|
|
||||||
guard let self, let view else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.actionSheet = nil
|
|
||||||
view.updateIsProgressPaused()
|
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))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
var itemFrame = CGRect(origin: CGPoint(x: nextItemX, y: floor((tabsSize.height - itemSize.height) * 0.5)), size: itemSize)
|
||||||
nextItemX += itemSize.width
|
nextItemX += itemSize.width
|
||||||
if isItemSelected {
|
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 {
|
if let itemComponentView = itemView.view as? ItemComponent.View, let selectedItemComponentView = selectedItemView.view as? ItemComponent.View {
|
||||||
|
|
|
||||||
|
|
@ -471,6 +471,7 @@ final class TextProcessingContentComponent: Component {
|
||||||
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.2)
|
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.2)
|
||||||
|
|
||||||
let environment = environment[ViewControllerComponentContainer.Environment.self].value
|
let environment = environment[ViewControllerComponentContainer.Environment.self].value
|
||||||
|
let theme = environment.theme.withModalBlocksBackground()
|
||||||
|
|
||||||
let isFirstTime = self.component == nil
|
let isFirstTime = self.component == nil
|
||||||
|
|
||||||
|
|
@ -516,8 +517,8 @@ final class TextProcessingContentComponent: Component {
|
||||||
content: .animation(
|
content: .animation(
|
||||||
content: .file(file: previewIconFile),
|
content: .file(file: previewIconFile),
|
||||||
size: previewIconSize,
|
size: previewIconSize,
|
||||||
placeholderColor: environment.theme.list.mediaPlaceholderColor,
|
placeholderColor: theme.list.mediaPlaceholderColor,
|
||||||
themeColor: environment.theme.list.itemPrimaryTextColor,
|
themeColor: theme.list.itemPrimaryTextColor,
|
||||||
loopMode: .count(1)
|
loopMode: .count(1)
|
||||||
),
|
),
|
||||||
isVisibleForAnimations: true,
|
isVisibleForAnimations: true,
|
||||||
|
|
@ -557,7 +558,7 @@ final class TextProcessingContentComponent: Component {
|
||||||
let titleSize = previewTitle.update(
|
let titleSize = previewTitle.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(MultilineTextComponent(
|
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: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: availableSize.width, height: 100.0)
|
containerSize: CGSize(width: availableSize.width, height: 100.0)
|
||||||
|
|
@ -576,7 +577,7 @@ final class TextProcessingContentComponent: Component {
|
||||||
let descriptionSize = previewDescription.update(
|
let descriptionSize = previewDescription.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(MultilineTextComponent(
|
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,
|
horizontalAlignment: .center,
|
||||||
maximumNumberOfLines: 0,
|
maximumNumberOfLines: 0,
|
||||||
lineSpacing: 0.12
|
lineSpacing: 0.12
|
||||||
|
|
@ -683,7 +684,7 @@ final class TextProcessingContentComponent: Component {
|
||||||
let modeTabsSize = self.modeTabs.update(
|
let modeTabsSize = self.modeTabs.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(TabBarComponent(
|
component: AnyComponent(TabBarComponent(
|
||||||
theme: environment.theme,
|
theme: theme,
|
||||||
tintSelectedItem: false,
|
tintSelectedItem: false,
|
||||||
isLiftedStateEnabled: false,
|
isLiftedStateEnabled: false,
|
||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
|
|
@ -725,7 +726,7 @@ final class TextProcessingContentComponent: Component {
|
||||||
contentExternalState = self.translateState
|
contentExternalState = self.translateState
|
||||||
contentComponent = AnyComponent(TextProcessingTranslateContentComponent(
|
contentComponent = AnyComponent(TextProcessingTranslateContentComponent(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
theme: environment.theme,
|
theme: theme,
|
||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
styles: component.styles,
|
styles: component.styles,
|
||||||
externalState: self.translateState,
|
externalState: self.translateState,
|
||||||
|
|
@ -771,7 +772,7 @@ final class TextProcessingContentComponent: Component {
|
||||||
contentExternalState = self.stylizeState
|
contentExternalState = self.stylizeState
|
||||||
contentComponent = AnyComponent(TextProcessingTranslateContentComponent(
|
contentComponent = AnyComponent(TextProcessingTranslateContentComponent(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
theme: environment.theme,
|
theme: theme,
|
||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
styles: component.styles,
|
styles: component.styles,
|
||||||
externalState: self.stylizeState,
|
externalState: self.stylizeState,
|
||||||
|
|
@ -892,7 +893,7 @@ final class TextProcessingContentComponent: Component {
|
||||||
contentExternalState = self.fixState
|
contentExternalState = self.fixState
|
||||||
contentComponent = AnyComponent(TextProcessingTranslateContentComponent(
|
contentComponent = AnyComponent(TextProcessingTranslateContentComponent(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
theme: environment.theme,
|
theme: theme,
|
||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
styles: component.styles,
|
styles: component.styles,
|
||||||
externalState: self.fixState,
|
externalState: self.fixState,
|
||||||
|
|
@ -997,7 +998,7 @@ final class TextProcessingContentComponent: Component {
|
||||||
if self.currentContentBackground.image == nil {
|
if self.currentContentBackground.image == nil {
|
||||||
self.currentContentBackground.image = generateStretchableFilledCircleImage(diameter: 60.0, color: .white)?.withRenderingMode(.alwaysTemplate)
|
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)
|
transition.setFrame(view: self.currentContentBackground, frame: contentFrame)
|
||||||
contentHeight += contentSize.height
|
contentHeight += contentSize.height
|
||||||
|
|
||||||
|
|
@ -1057,19 +1058,19 @@ final class TextProcessingContentComponent: Component {
|
||||||
if case .translate = component.mode {
|
if case .translate = component.mode {
|
||||||
if let copyTranslation = component.copyCurrentResult {
|
if let copyTranslation = component.copyCurrentResult {
|
||||||
actionsSectionItems.append(AnyComponentWithIdentity(id: "copy", component: AnyComponent(ListActionItemComponent(
|
actionsSectionItems.append(AnyComponentWithIdentity(id: "copy", component: AnyComponent(ListActionItemComponent(
|
||||||
theme: environment.theme,
|
theme: theme,
|
||||||
style: .glass,
|
style: .glass,
|
||||||
title: AnyComponent(
|
title: AnyComponent(
|
||||||
MultilineTextComponent(
|
MultilineTextComponent(
|
||||||
text: .plain(NSAttributedString(
|
text: .plain(NSAttributedString(
|
||||||
string: environment.strings.Translate_CopyTranslation,
|
string: environment.strings.Translate_CopyTranslation,
|
||||||
font: Font.regular(17.0),
|
font: Font.regular(17.0),
|
||||||
textColor: environment.theme.list.itemAccentColor
|
textColor: theme.list.itemAccentColor
|
||||||
)),
|
)),
|
||||||
maximumNumberOfLines: 1
|
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
|
action: { _ in
|
||||||
copyTranslation()
|
copyTranslation()
|
||||||
}
|
}
|
||||||
|
|
@ -1077,19 +1078,19 @@ final class TextProcessingContentComponent: Component {
|
||||||
}
|
}
|
||||||
if let translateChat = component.translateChat {
|
if let translateChat = component.translateChat {
|
||||||
actionsSectionItems.append(AnyComponentWithIdentity(id: "translate", component: AnyComponent(ListActionItemComponent(
|
actionsSectionItems.append(AnyComponentWithIdentity(id: "translate", component: AnyComponent(ListActionItemComponent(
|
||||||
theme: environment.theme,
|
theme: theme,
|
||||||
style: .glass,
|
style: .glass,
|
||||||
title: AnyComponent(
|
title: AnyComponent(
|
||||||
MultilineTextComponent(
|
MultilineTextComponent(
|
||||||
text: .plain(NSAttributedString(
|
text: .plain(NSAttributedString(
|
||||||
string: environment.strings.Localization_TranslateEntireChat,
|
string: environment.strings.Localization_TranslateEntireChat,
|
||||||
font: Font.regular(17.0),
|
font: Font.regular(17.0),
|
||||||
textColor: environment.theme.list.itemAccentColor
|
textColor: theme.list.itemAccentColor
|
||||||
)),
|
)),
|
||||||
maximumNumberOfLines: 1
|
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
|
action: { [weak self] _ in
|
||||||
guard let self, let language = self.translateState.result?.language else {
|
guard let self, let language = self.translateState.result?.language else {
|
||||||
return
|
return
|
||||||
|
|
@ -1105,7 +1106,7 @@ final class TextProcessingContentComponent: Component {
|
||||||
let actionsSectionSize = self.actionsSection.update(
|
let actionsSectionSize = self.actionsSection.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(ListSectionComponent(
|
component: AnyComponent(ListSectionComponent(
|
||||||
theme: environment.theme,
|
theme: theme,
|
||||||
style: .glass,
|
style: .glass,
|
||||||
header: nil,
|
header: nil,
|
||||||
footer: nil,
|
footer: nil,
|
||||||
|
|
@ -1368,7 +1369,7 @@ private final class TextProcessingSheetComponent: Component {
|
||||||
let environmentValue = environment[ViewControllerComponentContainer.Environment.self].value
|
let environmentValue = environment[ViewControllerComponentContainer.Environment.self].value
|
||||||
self.environment = environmentValue
|
self.environment = environmentValue
|
||||||
let controller = environmentValue.controller
|
let controller = environmentValue.controller
|
||||||
let theme = environmentValue.theme
|
let theme = environmentValue.theme.withModalBlocksBackground()
|
||||||
|
|
||||||
let dismiss: (Bool) -> Void = { [weak self] animated in
|
let dismiss: (Bool) -> Void = { [weak self] animated in
|
||||||
if animated {
|
if animated {
|
||||||
|
|
@ -1427,11 +1428,23 @@ private final class TextProcessingSheetComponent: Component {
|
||||||
}
|
}
|
||||||
dismiss(true)
|
dismiss(true)
|
||||||
}
|
}
|
||||||
case .translate:
|
case let .translate(_, applyResult):
|
||||||
actionButtonTitle = environmentValue.strings.TextProcessing_ActionClose
|
if let applyResult {
|
||||||
isMainActionEnabled = true
|
actionButtonTitle = environmentValue.strings.TextProcessing_ActionApply
|
||||||
performMainAction = {
|
isMainActionEnabled = !self.contentExternalState.isProcessing && self.contentExternalState.result != nil
|
||||||
dismiss(true)
|
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):
|
case let .preview(style, _, _, isAlreadyAdded, added):
|
||||||
actionButtonTitle = isAlreadyAdded ? environmentValue.strings.TextProcessing_StyleMenu_ButtonClose : environmentValue.strings.TextProcessing_StyleMenu_ButtonAdd
|
actionButtonTitle = isAlreadyAdded ? environmentValue.strings.TextProcessing_StyleMenu_ButtonClose : environmentValue.strings.TextProcessing_StyleMenu_ButtonAdd
|
||||||
|
|
|
||||||
12
submodules/TelegramUI/Images.xcassets/Media Editor/Send.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Media Editor/Send.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "attachlogo.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
submodules/TelegramUI/Images.xcassets/Media Editor/Send.imageset/attachlogo.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Media Editor/Send.imageset/attachlogo.pdf
vendored
Normal file
Binary file not shown.
|
|
@ -59,6 +59,7 @@ import Markdown
|
||||||
import TelegramPermissionsUI
|
import TelegramPermissionsUI
|
||||||
import Speak
|
import Speak
|
||||||
import TranslateUI
|
import TranslateUI
|
||||||
|
import TextProcessingScreen
|
||||||
import UniversalMediaPlayer
|
import UniversalMediaPlayer
|
||||||
import WallpaperBackgroundNode
|
import WallpaperBackgroundNode
|
||||||
import ChatListUI
|
import ChatListUI
|
||||||
|
|
@ -4625,27 +4626,56 @@ extension ChatControllerImpl {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let (_, language) = canTranslateText(context: context, text: text.string, showTranslate: true, ignoredLanguages: nil)
|
let (_, language) = canTranslateText(context: context, text: text.string, showTranslate: true, ignoredLanguages: nil)
|
||||||
|
|
||||||
let entities = generateChatInputTextEntities(text)
|
let entities = generateChatInputTextEntities(text)
|
||||||
presentTranslateScreen(
|
|
||||||
context: self.context,
|
let translationConfiguration = TranslationConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
|
||||||
text: text.string,
|
var useSystemTranslation = false
|
||||||
entities: entities,
|
switch translationConfiguration.manual {
|
||||||
canCopy: true,
|
case .system:
|
||||||
fromLanguage: language,
|
if #available(iOS 18.0, *) {
|
||||||
replaceText: { text, entities in
|
useSystemTranslation = true
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
)
|
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
|
}, sendEmoji: { [weak self] text, attribute, immediately in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -4328,7 +4328,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||||
} else {
|
} else {
|
||||||
self.push(await TextProcessingScreen(
|
self.push(await TextProcessingScreen(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
mode: .translate(fromLanguage: language),
|
mode: .translate(fromLanguage: language, applyResult: nil),
|
||||||
inputText: TextWithEntities(text: text.string, entities: entities ?? []),
|
inputText: TextWithEntities(text: text.string, entities: entities ?? []),
|
||||||
copyResult: canCopy ? { [weak self] text in
|
copyResult: canCopy ? { [weak self] text in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
|
|
|
||||||
|
|
@ -87,8 +87,14 @@ final class ChatMessageContextExtractedContentSource: ContextExtractedContentSou
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if item.content.contains(where: { $0.0.stableId == self.message.stableId }), let contentNode = itemNode.getMessageContextSourceNode(stableId: self.selectAll ? nil : self.message.stableId) {
|
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) {
|
if self.snapshot, let snapshotView = contentNode.contentNode.view.snapshotContentTree(unhide: false, keepPortals: true, keepTransform: true) {
|
||||||
contentNode.view.superview?.addSubview(snapshotView)
|
contentNode.view.superview?.addSubview(snapshotView)
|
||||||
self.snapshotView = snapshotView
|
self.snapshotView = snapshotView
|
||||||
|
|
@ -112,7 +118,13 @@ final class ChatMessageContextExtractedContentSource: ContextExtractedContentSou
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if item.content.contains(where: { $0.0.stableId == self.message.stableId }) {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,197 +0,0 @@
|
||||||
import Foundation
|
|
||||||
import UIKit
|
|
||||||
import Display
|
|
||||||
import SwiftSignalKit
|
|
||||||
import TelegramCore
|
|
||||||
import TelegramPresentationData
|
|
||||||
import TelegramUIPreferences
|
|
||||||
import ItemListUI
|
|
||||||
import PresentationDataUtils
|
|
||||||
import TelegramStringFormatting
|
|
||||||
import AccountContext
|
|
||||||
|
|
||||||
private final class LanguageSelectionControllerArguments {
|
|
||||||
let context: AccountContext
|
|
||||||
let updateLanguageSelected: (String) -> Void
|
|
||||||
|
|
||||||
init(context: AccountContext, updateLanguageSelected: @escaping (String) -> Void) {
|
|
||||||
self.context = context
|
|
||||||
self.updateLanguageSelected = updateLanguageSelected
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum LanguageSelectionControllerSection: Int32 {
|
|
||||||
case languages
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum LanguageSelectionControllerEntry: ItemListNodeEntry {
|
|
||||||
case language(Int32, PresentationTheme, String, String, Bool, String)
|
|
||||||
|
|
||||||
var section: ItemListSectionId {
|
|
||||||
switch self {
|
|
||||||
case .language:
|
|
||||||
return LanguageSelectionControllerSection.languages.rawValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var stableId: Int32 {
|
|
||||||
switch self {
|
|
||||||
case let .language(index, _, _, _, _, _):
|
|
||||||
return index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static func ==(lhs: LanguageSelectionControllerEntry, rhs: LanguageSelectionControllerEntry) -> Bool {
|
|
||||||
switch lhs {
|
|
||||||
case let .language(lhsIndex, lhsTheme, lhsTitle, lhsSubtitle, lhsValue, lhsCode):
|
|
||||||
if case let .language(rhsIndex, rhsTheme, rhsTitle, rhsSubtitle, rhsValue, rhsCode) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsValue == rhsValue, lhsCode == rhsCode {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static func <(lhs: LanguageSelectionControllerEntry, rhs: LanguageSelectionControllerEntry) -> Bool {
|
|
||||||
return lhs.stableId < rhs.stableId
|
|
||||||
}
|
|
||||||
|
|
||||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
|
||||||
let arguments = arguments as! LanguageSelectionControllerArguments
|
|
||||||
switch self {
|
|
||||||
case let .language(_, _, title, subtitle, value, code):
|
|
||||||
return LocalizationListItem(presentationData: presentationData, id: code, title: title, subtitle: subtitle, checked: value, activity: false, loading: false, editing: LocalizationListItemEditing(editable: false, editing: false, revealed: false, reorderable: false), sectionId: self.section, alwaysPlain: false, action: {
|
|
||||||
arguments.updateLanguageSelected(code)
|
|
||||||
}, setItemWithRevealedOptions: { _, _ in }, removeItem: { _ in })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func languageSelectionControllerEntries(theme: PresentationTheme, strings: PresentationStrings, selectedLanguage: String, languages: [(String, String, String)]) -> [LanguageSelectionControllerEntry] {
|
|
||||||
var entries: [LanguageSelectionControllerEntry] = []
|
|
||||||
|
|
||||||
var index: Int32 = 0
|
|
||||||
for (code, title, subtitle) in languages {
|
|
||||||
entries.append(.language(index, theme, title, subtitle, code == selectedLanguage, code))
|
|
||||||
index += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return entries
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct LanguageSelectionControllerState: Equatable {
|
|
||||||
enum Section {
|
|
||||||
case original
|
|
||||||
case translation
|
|
||||||
}
|
|
||||||
|
|
||||||
var section: Section
|
|
||||||
var fromLanguage: String
|
|
||||||
var toLanguage: String
|
|
||||||
}
|
|
||||||
|
|
||||||
public func languageSelectionController(context: AccountContext, forceTheme: PresentationTheme? = nil, fromLanguage: String, toLanguage: String, completion: @escaping (String, String) -> Void) -> ViewController {
|
|
||||||
let statePromise = ValuePromise(LanguageSelectionControllerState(section: .translation, fromLanguage: fromLanguage, toLanguage: toLanguage), ignoreRepeated: true)
|
|
||||||
let stateValue = Atomic(value: LanguageSelectionControllerState(section: .translation, fromLanguage: fromLanguage, toLanguage: toLanguage))
|
|
||||||
let updateState: ((LanguageSelectionControllerState) -> LanguageSelectionControllerState) -> Void = { f in
|
|
||||||
statePromise.set(stateValue.modify { f($0) })
|
|
||||||
}
|
|
||||||
|
|
||||||
let actionsDisposable = DisposableSet()
|
|
||||||
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
||||||
let interfaceLanguageCode = presentationData.strings.baseLanguageCode
|
|
||||||
|
|
||||||
var dismissImpl: (() -> Void)?
|
|
||||||
|
|
||||||
let arguments = LanguageSelectionControllerArguments(context: context, updateLanguageSelected: { code in
|
|
||||||
updateState { current in
|
|
||||||
var updated = current
|
|
||||||
switch updated.section {
|
|
||||||
case .original:
|
|
||||||
updated.fromLanguage = code
|
|
||||||
case .translation:
|
|
||||||
updated.toLanguage = code
|
|
||||||
}
|
|
||||||
return updated
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
let enLocale = Locale(identifier: "en")
|
|
||||||
var languages: [(String, String, String)] = []
|
|
||||||
var addedLanguages = Set<String>()
|
|
||||||
for code in popularTranslationLanguages {
|
|
||||||
if let title = enLocale.localizedString(forLanguageCode: code) {
|
|
||||||
let languageLocale = Locale(identifier: code)
|
|
||||||
let subtitle = languageLocale.localizedString(forLanguageCode: code) ?? title
|
|
||||||
let value = (code, title.capitalized, subtitle.capitalized)
|
|
||||||
if code == interfaceLanguageCode {
|
|
||||||
languages.insert(value, at: 0)
|
|
||||||
} else {
|
|
||||||
languages.append(value)
|
|
||||||
}
|
|
||||||
addedLanguages.insert(code)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for code in supportedTranslationLanguages {
|
|
||||||
if !addedLanguages.contains(code), let title = enLocale.localizedString(forLanguageCode: code) {
|
|
||||||
let languageLocale = Locale(identifier: code)
|
|
||||||
let subtitle = languageLocale.localizedString(forLanguageCode: code) ?? title
|
|
||||||
let value = (code, title.capitalized, subtitle.capitalized)
|
|
||||||
if code == interfaceLanguageCode {
|
|
||||||
languages.insert(value, at: 0)
|
|
||||||
} else {
|
|
||||||
languages.append(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let signal = combineLatest(queue: Queue.mainQueue(), context.sharedContext.presentationData, statePromise.get())
|
|
||||||
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
|
||||||
var presentationData = presentationData
|
|
||||||
if let forceTheme {
|
|
||||||
presentationData = presentationData.withUpdated(theme: forceTheme)
|
|
||||||
}
|
|
||||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .sectionControl([presentationData.strings.Translate_Languages_Original, presentationData.strings.Translate_Languages_Translation], 1), leftNavigationButton: ItemListNavigationButton(content: .none, style: .regular, enabled: false, action: {}), rightNavigationButton: ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: {
|
|
||||||
completion(state.fromLanguage, state.toLanguage)
|
|
||||||
dismissImpl?()
|
|
||||||
}), backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
|
||||||
|
|
||||||
let selectedLanguage: String
|
|
||||||
switch state.section {
|
|
||||||
case.original:
|
|
||||||
selectedLanguage = state.fromLanguage
|
|
||||||
case .translation:
|
|
||||||
selectedLanguage = state.toLanguage
|
|
||||||
}
|
|
||||||
|
|
||||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: languageSelectionControllerEntries(theme: presentationData.theme, strings: presentationData.strings, selectedLanguage: selectedLanguage, languages: languages), style: .blocks, animateChanges: false)
|
|
||||||
|
|
||||||
return (controllerState, (listState, arguments))
|
|
||||||
}
|
|
||||||
|> afterDisposed {
|
|
||||||
actionsDisposable.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
let controller = ItemListController(context: context, state: signal)
|
|
||||||
controller.titleControlValueChanged = { value in
|
|
||||||
updateState { current in
|
|
||||||
var updated = current
|
|
||||||
if value == 0 {
|
|
||||||
updated.section = .original
|
|
||||||
} else {
|
|
||||||
updated.section = .translation
|
|
||||||
}
|
|
||||||
return updated
|
|
||||||
}
|
|
||||||
}
|
|
||||||
controller.alwaysSynchronous = true
|
|
||||||
controller.navigationPresentation = .modal
|
|
||||||
|
|
||||||
dismissImpl = { [weak controller] in
|
|
||||||
controller?.dismiss(animated: true, completion: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return controller
|
|
||||||
}
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
import Foundation
|
|
||||||
import UIKit
|
|
||||||
import ComponentFlow
|
|
||||||
import ManagedAnimationNode
|
|
||||||
|
|
||||||
enum PlayPauseIconNodeState: Equatable {
|
|
||||||
case play
|
|
||||||
case pause
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class PlayPauseIconNode: ManagedAnimationNode {
|
|
||||||
private let duration: Double = 0.35
|
|
||||||
private var iconState: PlayPauseIconNodeState = .play
|
|
||||||
|
|
||||||
init() {
|
|
||||||
super.init(size: CGSize(width: 40.0, height: 40.0))
|
|
||||||
|
|
||||||
self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.01))
|
|
||||||
}
|
|
||||||
|
|
||||||
func enqueueState(_ state: PlayPauseIconNodeState, animated: Bool) {
|
|
||||||
guard self.iconState != state else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let previousState = self.iconState
|
|
||||||
self.iconState = state
|
|
||||||
|
|
||||||
switch previousState {
|
|
||||||
case .pause:
|
|
||||||
switch state {
|
|
||||||
case .play:
|
|
||||||
if animated {
|
|
||||||
self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 41, endFrame: 83), duration: self.duration))
|
|
||||||
} else {
|
|
||||||
self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.01))
|
|
||||||
}
|
|
||||||
case .pause:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case .play:
|
|
||||||
switch state {
|
|
||||||
case .pause:
|
|
||||||
if animated {
|
|
||||||
self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 0, endFrame: 41), duration: self.duration))
|
|
||||||
} else {
|
|
||||||
self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 41, endFrame: 41), duration: 0.01))
|
|
||||||
}
|
|
||||||
case .play:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final class PlayPauseIconComponent: Component {
|
|
||||||
let state: PlayPauseIconNodeState
|
|
||||||
let tintColor: UIColor?
|
|
||||||
let size: CGSize
|
|
||||||
|
|
||||||
init(state: PlayPauseIconNodeState, tintColor: UIColor?, size: CGSize) {
|
|
||||||
self.state = state
|
|
||||||
self.tintColor = tintColor
|
|
||||||
self.size = size
|
|
||||||
}
|
|
||||||
|
|
||||||
static func ==(lhs: PlayPauseIconComponent, rhs: PlayPauseIconComponent) -> Bool {
|
|
||||||
if lhs.state != rhs.state {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.tintColor != rhs.tintColor {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.size != rhs.size {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
final class View: UIView {
|
|
||||||
private var component: PlayPauseIconComponent?
|
|
||||||
private var animationNode: PlayPauseIconNode
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
|
||||||
self.animationNode = PlayPauseIconNode()
|
|
||||||
|
|
||||||
super.init(frame: frame)
|
|
||||||
|
|
||||||
self.addSubview(self.animationNode.view)
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func update(component: PlayPauseIconComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
|
|
||||||
if self.component?.state != component.state {
|
|
||||||
self.component = component
|
|
||||||
|
|
||||||
self.animationNode.enqueueState(component.state, animated: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.animationNode.customColor = component.tintColor
|
|
||||||
|
|
||||||
let animationSize = component.size
|
|
||||||
let size = CGSize(width: min(animationSize.width, availableSize.width), height: min(animationSize.height, availableSize.height))
|
|
||||||
self.animationNode.view.frame = CGRect(origin: CGPoint(x: floor((size.width - animationSize.width) / 2.0), y: floor((size.height - animationSize.height) / 2.0)), size: animationSize)
|
|
||||||
|
|
||||||
return size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeView() -> View {
|
|
||||||
return View(frame: CGRect())
|
|
||||||
}
|
|
||||||
|
|
||||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
|
||||||
return view.update(component: self, availableSize: availableSize, transition: transition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -86,6 +86,7 @@ public var supportedTranslationLanguages = [
|
||||||
"fa",
|
"fa",
|
||||||
"pl",
|
"pl",
|
||||||
"pt",
|
"pt",
|
||||||
|
"pt-BR",
|
||||||
"pa",
|
"pa",
|
||||||
"ro",
|
"ro",
|
||||||
"ru",
|
"ru",
|
||||||
|
|
@ -132,7 +133,7 @@ public var popularTranslationLanguages = [
|
||||||
"it",
|
"it",
|
||||||
"ja",
|
"ja",
|
||||||
"ko",
|
"ko",
|
||||||
"pt",
|
"pt-BR",
|
||||||
"ru",
|
"ru",
|
||||||
"es",
|
"es",
|
||||||
"uk"
|
"uk"
|
||||||
|
|
|
||||||
|
|
@ -1,184 +0,0 @@
|
||||||
import Foundation
|
|
||||||
import UIKit
|
|
||||||
import Display
|
|
||||||
import ComponentFlow
|
|
||||||
import TelegramPresentationData
|
|
||||||
import BundleIconComponent
|
|
||||||
|
|
||||||
private final class TranslateButtonContentComponent: CombinedComponent {
|
|
||||||
let theme: PresentationTheme
|
|
||||||
let title: String
|
|
||||||
let icon: String
|
|
||||||
|
|
||||||
init(
|
|
||||||
theme: PresentationTheme,
|
|
||||||
title: String,
|
|
||||||
icon: String
|
|
||||||
) {
|
|
||||||
self.theme = theme
|
|
||||||
self.title = title
|
|
||||||
self.icon = icon
|
|
||||||
}
|
|
||||||
|
|
||||||
static func ==(lhs: TranslateButtonContentComponent, rhs: TranslateButtonContentComponent) -> Bool {
|
|
||||||
if lhs.theme !== rhs.theme {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.title != rhs.title {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.icon != rhs.icon {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
static var body: Body {
|
|
||||||
let title = Child(Text.self)
|
|
||||||
let icon = Child(BundleIconComponent.self)
|
|
||||||
|
|
||||||
return { context in
|
|
||||||
let component = context.component
|
|
||||||
|
|
||||||
let icon = icon.update(
|
|
||||||
component: BundleIconComponent(
|
|
||||||
name: component.icon,
|
|
||||||
tintColor: component.theme.list.itemPrimaryTextColor
|
|
||||||
),
|
|
||||||
availableSize: CGSize(width: 30.0, height: 30.0),
|
|
||||||
transition: context.transition
|
|
||||||
)
|
|
||||||
|
|
||||||
let title = title.update(
|
|
||||||
component: Text(
|
|
||||||
text: component.title,
|
|
||||||
font: Font.regular(17.0),
|
|
||||||
color: component.theme.list.itemPrimaryTextColor
|
|
||||||
),
|
|
||||||
availableSize: context.availableSize,
|
|
||||||
transition: .immediate
|
|
||||||
)
|
|
||||||
|
|
||||||
let sideInset: CGFloat = 16.0
|
|
||||||
let textSideInset: CGFloat = 60.0
|
|
||||||
|
|
||||||
context.add(title
|
|
||||||
.position(CGPoint(x: textSideInset + title.size.width / 2.0, y: context.availableSize.height / 2.0))
|
|
||||||
)
|
|
||||||
|
|
||||||
context.add(icon
|
|
||||||
.position(CGPoint(x: sideInset + icon.size.width / 2.0, y: context.availableSize.height / 2.0))
|
|
||||||
)
|
|
||||||
|
|
||||||
return context.availableSize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final class TranslateButtonComponent: Component {
|
|
||||||
private let content: TranslateButtonContentComponent
|
|
||||||
private let theme: PresentationTheme
|
|
||||||
private let isEnabled: Bool
|
|
||||||
private let action: () -> Void
|
|
||||||
|
|
||||||
init(
|
|
||||||
theme: PresentationTheme,
|
|
||||||
title: String,
|
|
||||||
icon: String,
|
|
||||||
isEnabled: Bool,
|
|
||||||
action: @escaping () -> Void
|
|
||||||
) {
|
|
||||||
self.content = TranslateButtonContentComponent(theme: theme, title: title, icon: icon)
|
|
||||||
self.isEnabled = isEnabled
|
|
||||||
self.theme = theme
|
|
||||||
self.action = action
|
|
||||||
}
|
|
||||||
|
|
||||||
static func ==(lhs: TranslateButtonComponent, rhs: TranslateButtonComponent) -> Bool {
|
|
||||||
if lhs.theme !== rhs.theme {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.content !== rhs.content {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.isEnabled != rhs.isEnabled {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
final class View: HighlightTrackingButton {
|
|
||||||
private let backgroundView: UIView
|
|
||||||
private let centralContentView: ComponentHostView<Empty>
|
|
||||||
|
|
||||||
private var component: TranslateButtonComponent?
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
|
||||||
self.backgroundView = UIView()
|
|
||||||
self.backgroundView.isUserInteractionEnabled = false
|
|
||||||
|
|
||||||
self.centralContentView = ComponentHostView()
|
|
||||||
self.centralContentView.isUserInteractionEnabled = false
|
|
||||||
|
|
||||||
super.init(frame: frame)
|
|
||||||
|
|
||||||
self.backgroundView.clipsToBounds = true
|
|
||||||
|
|
||||||
self.addSubview(self.backgroundView)
|
|
||||||
self.addSubview(self.centralContentView)
|
|
||||||
|
|
||||||
self.highligthedChanged = { [weak self] highlighted in
|
|
||||||
if let strongSelf = self, let component = strongSelf.component {
|
|
||||||
if highlighted {
|
|
||||||
strongSelf.backgroundView.backgroundColor = component.theme.list.itemHighlightedBackgroundColor
|
|
||||||
} else {
|
|
||||||
UIView.animate(withDuration: 0.3, animations: {
|
|
||||||
strongSelf.backgroundView.backgroundColor = component.theme.list.itemBlocksBackgroundColor
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func pressed() {
|
|
||||||
if let component = self.component {
|
|
||||||
component.action()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func update(component: TranslateButtonComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
|
|
||||||
self.component = component
|
|
||||||
|
|
||||||
self.backgroundView.backgroundColor = component.theme.list.itemBlocksBackgroundColor
|
|
||||||
self.backgroundView.layer.cornerRadius = 26.0
|
|
||||||
|
|
||||||
let _ = self.centralContentView.update(
|
|
||||||
transition: transition,
|
|
||||||
component: AnyComponent(component.content),
|
|
||||||
environment: {},
|
|
||||||
containerSize: availableSize
|
|
||||||
)
|
|
||||||
transition.setFrame(view: self.centralContentView, frame: CGRect(origin: CGPoint(), size: availableSize), completion: nil)
|
|
||||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: availableSize), completion: nil)
|
|
||||||
|
|
||||||
self.centralContentView.alpha = component.isEnabled ? 1.0 : 0.4
|
|
||||||
self.isUserInteractionEnabled = component.isEnabled
|
|
||||||
|
|
||||||
return availableSize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeView() -> View {
|
|
||||||
return View()
|
|
||||||
}
|
|
||||||
|
|
||||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
|
||||||
return view.update(component: self, availableSize: availableSize, transition: transition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -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 let ageBotUsername = self.context.currentAppConfiguration.with({ $0 }).data?["verify_age_bot_username"] as? String {
|
||||||
if self.botAddress == ageBotUsername {
|
if self.botAddress == ageBotUsername {
|
||||||
return true
|
return true
|
||||||
} else if self.botAddress == "mod_bot" {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue