mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-05 19:28:46 +02:00
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ swift_library(
|
|||
"//submodules/ContextUI",
|
||||
"//submodules/UndoUI",
|
||||
"//submodules/TranslateUI",
|
||||
"//submodules/TelegramUI/Components/TextProcessingScreen",
|
||||
"//submodules/ComponentFlow:ComponentFlow",
|
||||
"//submodules/Components/ViewControllerComponent:ViewControllerComponent",
|
||||
"//submodules/Components/MultilineTextComponent:MultilineTextComponent",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import AppBundle
|
|||
import InstantPageUI
|
||||
import UndoUI
|
||||
import TranslateUI
|
||||
import TextProcessingScreen
|
||||
import ContextUI
|
||||
import Pasteboard
|
||||
import SaveToCameraRoll
|
||||
|
|
@ -1821,15 +1822,22 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
|||
let (canTranslate, language) = canTranslateText(context: context, text: text, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: false, ignoredLanguages: translationSettings.ignoredLanguages)
|
||||
if canTranslate {
|
||||
actions.append(ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuTranslate, accessibilityLabel: strings.Conversation_ContextMenuTranslate), action: { [weak self] in
|
||||
let controller = TranslateScreen(context: context, text: text, canCopy: true, fromLanguage: language)
|
||||
controller.pushController = { [weak self] c in
|
||||
self?.getNavigationController()?._keepModalDismissProgress = true
|
||||
self?.push(c)
|
||||
Task { @MainActor [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let controller = await TextProcessingScreen(
|
||||
context: context,
|
||||
mode: .translate(fromLanguage: language, applyResult: nil),
|
||||
inputText: TextWithEntities(text: text, entities: []),
|
||||
copyResult: { [weak self] text in
|
||||
storeMessageTextInPasteboard(text.text, entities: text.entities)
|
||||
self?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), nil)
|
||||
},
|
||||
translateChat: nil
|
||||
)
|
||||
self.present(controller, nil)
|
||||
}
|
||||
controller.presentController = { [weak self] c in
|
||||
self?.present(c, nil)
|
||||
}
|
||||
self?.present(controller, nil)
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1150,7 +1150,7 @@ public final class Camera {
|
|||
|
||||
public static func isDualCameraSupported(forRoundVideo: Bool = false) -> Bool {
|
||||
if #available(iOS 13.0, *), AVCaptureMultiCamSession.isMultiCamSupported && !DeviceModel.current.isIpad {
|
||||
if forRoundVideo && DeviceModel.current == .iPhoneXR {
|
||||
if forRoundVideo && (ProcessInfo.processInfo.isLowPowerModeEnabled || DeviceModel.current == .iPhoneXR) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -3134,9 +3134,10 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
default:
|
||||
break
|
||||
}
|
||||
let messageTypeIconScale = min(1.0, item.presentationData.fontSize.itemListBaseFontSize / 17.0)
|
||||
|
||||
if let currentMessageTypeIcon {
|
||||
textLeftCutout += currentMessageTypeIcon.size.width
|
||||
textLeftCutout += currentMessageTypeIcon.size.width * messageTypeIconScale
|
||||
if !contentImageSpecs.isEmpty {
|
||||
textLeftCutout += forwardedIconSpacing
|
||||
} else {
|
||||
|
|
@ -3654,6 +3655,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
textMaxWidth -= 18.0
|
||||
}
|
||||
|
||||
let textLineSpacing: CGFloat = min(0.2, item.presentationData.fontSize.itemListBaseFontSize * 0.2 / 17.0)
|
||||
let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(
|
||||
attributedString: textAttributedString,
|
||||
backgroundColor: nil,
|
||||
|
|
@ -3661,7 +3663,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
truncationType: .end,
|
||||
constrainedSize: CGSize(width: textMaxWidth, height: .greatestFiniteMagnitude),
|
||||
alignment: .natural,
|
||||
lineSpacing: 0.2,
|
||||
lineSpacing: textLineSpacing,
|
||||
cutout: textCutout,
|
||||
insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)
|
||||
))
|
||||
|
|
@ -4380,17 +4382,17 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
strongSelf.statusNode.fontSize = item.presentationData.fontSize.itemListBaseFontSize
|
||||
let _ = strongSelf.statusNode.transitionToState(statusState, animated: animateContent)
|
||||
|
||||
let rightAccessoryVerticalOffset: CGFloat = -2.0
|
||||
let rightAccessoryVerticalOffset: CGFloat = floorToScreenPixels(-4.0 * min(1.0, item.presentationData.fontSize.itemListBaseFontSize / 17.0))
|
||||
var nextBadgeX: CGFloat = contentRect.maxX
|
||||
if let _ = currentBadgeBackgroundImage {
|
||||
let badgeFrame = CGRect(x: nextBadgeX - badgeLayout.width, y: contentRect.maxY - badgeLayout.height - 2.0 + rightAccessoryVerticalOffset, width: badgeLayout.width, height: badgeLayout.height)
|
||||
let badgeFrame = CGRect(x: nextBadgeX - badgeLayout.width, y: contentRect.maxY - badgeLayout.height + rightAccessoryVerticalOffset, width: badgeLayout.width, height: badgeLayout.height)
|
||||
|
||||
transition.updateFrame(node: strongSelf.badgeNode, frame: badgeFrame)
|
||||
nextBadgeX -= badgeLayout.width + 6.0
|
||||
}
|
||||
|
||||
if currentMentionBadgeImage != nil || currentBadgeBackgroundImage != nil {
|
||||
let badgeFrame = CGRect(x: nextBadgeX - mentionBadgeLayout.width, y: contentRect.maxY - mentionBadgeLayout.height - 2.0 + rightAccessoryVerticalOffset, width: mentionBadgeLayout.width, height: mentionBadgeLayout.height)
|
||||
let badgeFrame = CGRect(x: nextBadgeX - mentionBadgeLayout.width, y: contentRect.maxY - mentionBadgeLayout.height + rightAccessoryVerticalOffset, width: mentionBadgeLayout.width, height: mentionBadgeLayout.height)
|
||||
|
||||
transition.updateFrame(node: strongSelf.mentionBadgeNode, frame: badgeFrame)
|
||||
nextBadgeX -= mentionBadgeLayout.width + 6.0
|
||||
|
|
@ -4401,7 +4403,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
strongSelf.pinnedIconNode.isHidden = false
|
||||
|
||||
let pinnedIconSize = currentPinnedIconImage.size
|
||||
let pinnedIconFrame = CGRect(x: nextBadgeX - pinnedIconSize.width, y: contentRect.maxY - pinnedIconSize.height - 2.0 + rightAccessoryVerticalOffset, width: pinnedIconSize.width, height: pinnedIconSize.height)
|
||||
let pinnedIconFrame = CGRect(x: nextBadgeX - pinnedIconSize.width, y: contentRect.maxY - pinnedIconSize.height + rightAccessoryVerticalOffset, width: pinnedIconSize.width, height: pinnedIconSize.height)
|
||||
|
||||
strongSelf.pinnedIconNode.frame = pinnedIconFrame
|
||||
nextBadgeX -= pinnedIconSize.width + 6.0
|
||||
|
|
@ -4836,8 +4838,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
if strongSelf.forwardedIconNode.supernode == nil {
|
||||
strongSelf.mainContentContainerNode.addSubnode(strongSelf.forwardedIconNode)
|
||||
}
|
||||
transition.updateFrame(node: strongSelf.forwardedIconNode, frame: CGRect(origin: messageTypeIconOffset, size: messageTypeIconImage.size))
|
||||
mediaPreviewOffset.x += messageTypeIconImage.size.width + forwardedIconSpacing
|
||||
let iconSize = CGSize(width: messageTypeIconImage.size.width * messageTypeIconScale, height: messageTypeIconImage.size.height * messageTypeIconScale)
|
||||
transition.updateFrame(node: strongSelf.forwardedIconNode, frame: CGRect(origin: messageTypeIconOffset, size: iconSize))
|
||||
mediaPreviewOffset.x += messageTypeIconImage.size.width * messageTypeIconScale + forwardedIconSpacing
|
||||
} else if strongSelf.forwardedIconNode.supernode != nil {
|
||||
strongSelf.forwardedIconNode.removeFromSupernode()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2070,7 +2070,13 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||
})
|
||||
.opacity(controlsAreVisible ? 1.0 : 0.0)
|
||||
)
|
||||
|
||||
|
||||
let modeConstrainedWidth: CGFloat
|
||||
if component.sourceHint == .storyEditor {
|
||||
modeConstrainedWidth = context.availableSize.width - 66.0 * 2.0
|
||||
} else {
|
||||
modeConstrainedWidth = context.availableSize.width - 76.0 * 2.0
|
||||
}
|
||||
let mode = mode.update(
|
||||
component: ModeComponent(
|
||||
isTablet: false,
|
||||
|
|
@ -2093,7 +2099,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||
},
|
||||
tag: modeTag
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - 66.0 * 2.0, height: 44.0),
|
||||
availableSize: CGSize(width: modeConstrainedWidth, height: 44.0),
|
||||
transition: context.transition
|
||||
)
|
||||
var modePosition = CGPoint(x: context.availableSize.width / 2.0 - (modeRightInset - 57.0) / 2.0, y: context.availableSize.height - environment.safeInsets.bottom - mode.size.height / 2.0 - 9.0)
|
||||
|
|
|
|||
|
|
@ -332,9 +332,9 @@ final class ModeComponent: Component {
|
|||
if isTablet {
|
||||
spacing = 9.0
|
||||
} else {
|
||||
if availableSize.width < 200.0 {
|
||||
inset = 20.0
|
||||
spacing = 24.0
|
||||
if availableSize.width < 270.0 {
|
||||
inset = 16.0
|
||||
spacing = 20.0
|
||||
} else {
|
||||
spacing = 30.0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ swift_library(
|
|||
"//submodules/UndoUI:UndoUI",
|
||||
"//submodules/InvisibleInkDustNode:InvisibleInkDustNode",
|
||||
"//submodules/TranslateUI:TranslateUI",
|
||||
"//submodules/TelegramUI/Components/TextProcessingScreen",
|
||||
"//submodules/ShimmerEffect:ShimmerEffect",
|
||||
"//submodules/Utils/RangeSet:RangeSet",
|
||||
"//submodules/TelegramUI/Components/TextNodeWithEntities:TextNodeWithEntities",
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import MultiAnimationRenderer
|
|||
import Pasteboard
|
||||
import Speak
|
||||
import TranslateUI
|
||||
import TextProcessingScreen
|
||||
import TelegramNotices
|
||||
import SolidRoundedButtonNode
|
||||
import UrlHandling
|
||||
|
|
@ -546,34 +547,42 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
let (_, language) = canTranslateText(context: self.context, text: text.string, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: showTranslateIfTopical, ignoredLanguages: translationSettings.ignoredLanguages)
|
||||
|
||||
let _ = ApplicationSpecificNotice.incrementTranslationSuggestion(accountManager: self.context.sharedContext.accountManager, timestamp: Int32(Date().timeIntervalSince1970)).start()
|
||||
|
||||
let translateController = TranslateScreen(context: self.context, forceTheme: defaultDarkPresentationTheme, text: text.string, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages)
|
||||
translateController.pushController = { [weak self] c in
|
||||
|
||||
Task { @MainActor [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.controllerInteraction?.pushController(c)
|
||||
let translateController = await TextProcessingScreen(
|
||||
context: self.context,
|
||||
theme: defaultDarkPresentationTheme,
|
||||
mode: .translate(fromLanguage: language, applyResult: nil),
|
||||
inputText: TextWithEntities(text: text.string, entities: []),
|
||||
copyResult: { [weak self] text in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
storeMessageTextInPasteboard(text.text, entities: text.entities)
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let undoController = UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, appearance: UndoOverlayController.Appearance(isBlurred: true), action: { _ in true })
|
||||
self.controllerInteraction?.presentController(undoController, nil)
|
||||
},
|
||||
translateChat: nil
|
||||
)
|
||||
|
||||
//self.actionSheet = translateController
|
||||
//view.updateIsProgressPaused()
|
||||
|
||||
/*translateController.wasDismissed = { [weak self, weak view] in
|
||||
guard let self, let view else {
|
||||
return
|
||||
}
|
||||
self.actionSheet = nil
|
||||
view.updateIsProgressPaused()
|
||||
}*/
|
||||
|
||||
//component.controller()?.present(translateController, in: .window(.root))
|
||||
self.controllerInteraction?.presentController(translateController, nil)
|
||||
}
|
||||
translateController.presentController = { [weak self] c in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.controllerInteraction?.presentController(c, nil)
|
||||
}
|
||||
|
||||
//self.actionSheet = translateController
|
||||
//view.updateIsProgressPaused()
|
||||
|
||||
/*translateController.wasDismissed = { [weak self, weak view] in
|
||||
guard let self, let view else {
|
||||
return
|
||||
}
|
||||
self.actionSheet = nil
|
||||
view.updateIsProgressPaused()
|
||||
}*/
|
||||
|
||||
//component.controller()?.present(translateController, in: .window(.root))
|
||||
self.controllerInteraction?.presentController(translateController, nil)
|
||||
})
|
||||
case .quote:
|
||||
break
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import ImageContentAnalysis
|
|||
import TextSelectionNode
|
||||
import Speak
|
||||
import TranslateUI
|
||||
import TextProcessingScreen
|
||||
import UndoUI
|
||||
import ContextUI
|
||||
import SaveToCameraRoll
|
||||
|
|
@ -408,15 +409,24 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
}
|
||||
case .translate:
|
||||
if let parentController = strongSelf.baseNavigationController()?.topViewController as? ViewController {
|
||||
let controller = TranslateScreen(context: strongSelf.context, text: string, canCopy: true, fromLanguage: nil)
|
||||
controller.pushController = { [weak parentController] c in
|
||||
(parentController?.navigationController as? NavigationController)?._keepModalDismissProgress = true
|
||||
parentController?.push(c)
|
||||
Task { @MainActor [weak parentController, weak strongSelf] in
|
||||
guard let strongSelf else {
|
||||
return
|
||||
}
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with({ $0 })
|
||||
let controller = await TextProcessingScreen(
|
||||
context: strongSelf.context,
|
||||
mode: .translate(fromLanguage: nil, applyResult: nil),
|
||||
inputText: TextWithEntities(text: string, entities: []),
|
||||
copyResult: { [weak parentController] text in
|
||||
storeMessageTextInPasteboard(text.text, entities: text.entities)
|
||||
let tooltipController = UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false })
|
||||
parentController?.present(tooltipController, in: .window(.root))
|
||||
},
|
||||
translateChat: nil
|
||||
)
|
||||
parentController?.present(controller, in: .window(.root))
|
||||
}
|
||||
controller.presentController = { [weak parentController] c in
|
||||
parentController?.present(c, in: .window(.root))
|
||||
}
|
||||
parentController.present(controller, in: .window(.root))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ swift_library(
|
|||
"//submodules/LocationResources:LocationResources",
|
||||
"//submodules/UndoUI:UndoUI",
|
||||
"//submodules/TranslateUI:TranslateUI",
|
||||
"//submodules/TelegramUI/Components/TextProcessingScreen",
|
||||
"//submodules/Pasteboard",
|
||||
"//submodules/Tuples:Tuples",
|
||||
"//third-party/SwiftMath:SwiftMath",
|
||||
"//submodules/ComponentFlow:ComponentFlow",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ import LocationUI
|
|||
import UndoUI
|
||||
import ContextUI
|
||||
import TranslateUI
|
||||
import TextProcessingScreen
|
||||
import Pasteboard
|
||||
|
||||
final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
private weak var controller: InstantPageController?
|
||||
|
|
@ -1528,11 +1530,11 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
translationSettings = TranslationSettings.defaultSettings
|
||||
}
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
var actions: [ContextMenuAction] = [ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuCopy, accessibilityLabel: strings.Conversation_ContextMenuCopy), action: { [weak self] in
|
||||
UIPasteboard.general.string = text
|
||||
|
||||
if let strongSelf = self {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
|
||||
}
|
||||
}), ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuShare, accessibilityLabel: strings.Conversation_ContextMenuShare), action: { [weak self] in
|
||||
|
|
@ -1544,15 +1546,22 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
let (canTranslate, language) = canTranslateText(context: context, text: text, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: false, ignoredLanguages: translationSettings.ignoredLanguages)
|
||||
if canTranslate {
|
||||
actions.append(ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuTranslate, accessibilityLabel: strings.Conversation_ContextMenuTranslate), action: { [weak self] in
|
||||
let controller = TranslateScreen(context: context, text: text, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages)
|
||||
controller.pushController = { [weak self] c in
|
||||
(self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true
|
||||
self?.controller?.push(c)
|
||||
Task { @MainActor [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let controller = await TextProcessingScreen(
|
||||
context: context,
|
||||
mode: .translate(fromLanguage: language, applyResult: nil),
|
||||
inputText: TextWithEntities(text: text, entities: []),
|
||||
copyResult: { [weak self] text in
|
||||
storeMessageTextInPasteboard(text.text, entities: text.entities)
|
||||
self?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), nil)
|
||||
},
|
||||
translateChat: nil
|
||||
)
|
||||
self.present(controller, nil)
|
||||
}
|
||||
controller.presentController = { [weak self] c in
|
||||
self?.controller?.present(c, in: .window(.root))
|
||||
}
|
||||
self?.present(controller, nil)
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ private struct ItemListRevealOptionLayoutMetrics {
|
|||
}
|
||||
|
||||
var slotShapeInset: CGFloat {
|
||||
return floor((self.slotWidth - self.shapeSize.width) / 2.0)
|
||||
return floorToScreenPixels((self.slotWidth - self.shapeSize.width) / 2.0)
|
||||
}
|
||||
|
||||
static func metrics(for height: CGFloat, hasVisualIcons: Bool) -> ItemListRevealOptionLayoutMetrics {
|
||||
|
|
@ -120,7 +120,7 @@ private struct ItemListRevealOptionLayoutMetrics {
|
|||
let compactShapeSize = CGSize(width: 60.0, height: 32.0)
|
||||
let regularContentHeight = regularShapeSize.height + optionTitleSpacing + ceil(titleFont.lineHeight)
|
||||
if height < regularContentHeight || !hasVisualIcons {
|
||||
return ItemListRevealOptionLayoutMetrics(shapeSize: compactShapeSize, slotWidth: 70.0, titleWidth: 70.0, iconMaxSide: 20.0, cornerRadius: 16.0, expandedIconInset: 16.0)
|
||||
return ItemListRevealOptionLayoutMetrics(shapeSize: compactShapeSize, slotWidth: 70.0, titleWidth: 70.0, iconMaxSide: 24.0, cornerRadius: 16.0, expandedIconInset: 16.0)
|
||||
} else {
|
||||
return ItemListRevealOptionLayoutMetrics(shapeSize: regularShapeSize, slotWidth: 60.0, titleWidth: 60.0, iconMaxSide: 40.0, cornerRadius: 25.0, expandedIconInset: 20.0)
|
||||
}
|
||||
|
|
@ -163,7 +163,7 @@ private final class ItemListRevealOptionNode: ASDisplayNode {
|
|||
private let contentContainerNode: ASDisplayNode
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let highlightNode: ASDisplayNode
|
||||
private let titleNode: ASTextNode
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let iconNode: ASImageNode?
|
||||
private let animationNode: SimpleAnimationNode?
|
||||
|
||||
|
|
@ -187,7 +187,7 @@ private final class ItemListRevealOptionNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
var titleWidthForGroupPillSizing: CGFloat {
|
||||
var titleWidth = self.titleNode.measure(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)).width
|
||||
var titleWidth = self.titleNode.updateLayout(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)).width
|
||||
if self.displaysTitleInsidePill {
|
||||
titleWidth += optionIconlessTitleAdditionalHorizontalPadding
|
||||
}
|
||||
|
|
@ -199,9 +199,11 @@ private final class ItemListRevealOptionNode: ASDisplayNode {
|
|||
self.backgroundNode = ASDisplayNode()
|
||||
self.highlightNode = ASDisplayNode()
|
||||
|
||||
self.titleNode = ASTextNode()
|
||||
self.titleNode = ImmediateTextNode()
|
||||
self.titleNode.displaysAsynchronously = false
|
||||
self.titleNode.maximumNumberOfLines = 1
|
||||
self.titleNode.truncationMode = .byTruncatingTail
|
||||
self.titleNode.textAlignment = .center
|
||||
|
||||
let displaysTitleInsidePill: Bool
|
||||
if case .none = icon {
|
||||
|
|
@ -361,20 +363,27 @@ private final class ItemListRevealOptionNode: ASDisplayNode {
|
|||
let bounds = CGRect(origin: CGPoint(), size: self.bounds.size)
|
||||
transition.updateFrame(node: self.contentContainerNode, frame: bounds)
|
||||
|
||||
let titleSize = self.titleNode.measure(CGSize(width: metrics.titleWidth, height: CGFloat.greatestFiniteMagnitude))
|
||||
let titleSize: CGSize
|
||||
if self.titleNode.frame.isEmpty {
|
||||
titleSize = self.titleNode.updateLayout(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude))
|
||||
} else {
|
||||
titleSize = self.titleNode.cachedLayout?.size ?? CGSize()
|
||||
}
|
||||
|
||||
let pillSize = metrics.shapeSize
|
||||
let shapeY: CGFloat
|
||||
if self.displaysTitleInsidePill {
|
||||
shapeY = floor((bounds.height - pillSize.height) / 2.0)
|
||||
shapeY = floorToScreenPixels((bounds.height - pillSize.height) / 2.0)
|
||||
} else {
|
||||
shapeY = floor((bounds.height - metrics.contentHeight) / 2.0)
|
||||
let contentHeight = pillSize.height + optionTitleSpacing + titleSize.height
|
||||
shapeY = floorToScreenPixels((bounds.height - contentHeight) / 2.0)
|
||||
}
|
||||
|
||||
let shapeFrameX: CGFloat
|
||||
if isStretched {
|
||||
shapeFrameX = isLeft ? 0.0 : bounds.width - pillSize.width
|
||||
} else {
|
||||
shapeFrameX = floor((metrics.slotWidth - pillSize.width) / 2.0)
|
||||
shapeFrameX = floorToScreenPixels((metrics.slotWidth - pillSize.width) / 2.0)
|
||||
}
|
||||
let shapeFrame = CGRect(origin: CGPoint(x: shapeFrameX, y: shapeY), size: pillSize)
|
||||
let backgroundFrame: CGRect
|
||||
|
|
@ -424,10 +433,11 @@ private final class ItemListRevealOptionNode: ASDisplayNode {
|
|||
var imageSize = CGSize(width: animationNode.size.width * self.animationScale, height: animationNode.size.height * self.animationScale)
|
||||
let imageMaxSide = max(imageSize.width, imageSize.height)
|
||||
if imageMaxSide > metrics.iconMaxSide {
|
||||
let imageScale = metrics.iconMaxSide / imageMaxSide * 1.4
|
||||
let imageScale = metrics.iconMaxSide / imageMaxSide * 1.5
|
||||
imageSize = CGSize(width: floorToScreenPixels(imageSize.width * imageScale), height: floorToScreenPixels(imageSize.height * imageScale))
|
||||
}
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(iconCenterX - imageSize.width / 2.0), y: floorToScreenPixels(iconCenterY - imageSize.height / 2.0) + 6.0 + self.animationNodeOffset), size: imageSize)
|
||||
let scaleFraction = imageSize.height / 56.0
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(iconCenterX - imageSize.width / 2.0), y: floorToScreenPixels(iconCenterY - imageSize.height / 2.0) + (6.0 + self.animationNodeOffset) * scaleFraction), size: imageSize)
|
||||
|
||||
if isPrimary {
|
||||
didApplyManualIconCenter = true
|
||||
|
|
@ -493,7 +503,6 @@ private final class ItemListRevealOptionNode: ASDisplayNode {
|
|||
|
||||
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||
let metrics = ItemListRevealOptionLayoutMetrics.metrics(for: constrainedSize.height, hasVisualIcons: !self.displaysTitleInsidePill)
|
||||
let _ = self.titleNode.measure(CGSize(width: metrics.titleWidth, height: CGFloat.greatestFiniteMagnitude))
|
||||
return CGSize(width: metrics.slotWidth, height: constrainedSize.height)
|
||||
}
|
||||
}
|
||||
|
|
@ -510,6 +519,7 @@ public final class ItemListRevealOptionsNode: ASDisplayNode {
|
|||
private var optionNodes: [ItemListRevealOptionNode] = []
|
||||
private var revealOffset: CGFloat = 0.0
|
||||
private var sideInset: CGFloat = 0.0
|
||||
private var currentMetrics: (containerSize: CGSize, metrics: ItemListRevealOptionLayoutMetrics)?
|
||||
|
||||
public init(optionSelected: @escaping (ItemListRevealOption) -> Void, tapticAction: @escaping () -> Void) {
|
||||
self.optionSelected = optionSelected
|
||||
|
|
@ -564,20 +574,27 @@ public final class ItemListRevealOptionsNode: ASDisplayNode {
|
|||
self.optionsContainerNode.addSubnode(node)
|
||||
}
|
||||
}
|
||||
self.currentMetrics = nil
|
||||
self.invalidateCalculatedLayout()
|
||||
}
|
||||
}
|
||||
|
||||
private func layoutMetrics(for height: CGFloat) -> ItemListRevealOptionLayoutMetrics {
|
||||
let metrics = ItemListRevealOptionLayoutMetrics.metrics(for: height, hasVisualIcons: self.options.contains(where: { $0.icon.hasVisualIcon }))
|
||||
private func layoutMetrics(for containerSize: CGSize) -> ItemListRevealOptionLayoutMetrics {
|
||||
if let currentMetrics = self.currentMetrics, currentMetrics.containerSize == containerSize {
|
||||
return currentMetrics.metrics
|
||||
}
|
||||
|
||||
let metrics = ItemListRevealOptionLayoutMetrics.metrics(for: containerSize.height, hasVisualIcons: self.options.contains(where: { $0.icon.hasVisualIcon }))
|
||||
let maxTitleWidth = self.optionNodes.reduce(0.0) { result, node in
|
||||
return max(result, node.titleWidthForGroupPillSizing)
|
||||
}
|
||||
return metrics.withGroupTitleWidth(maxTitleWidth)
|
||||
let updatedMetrics = metrics.withGroupTitleWidth(maxTitleWidth)
|
||||
self.currentMetrics = (containerSize, updatedMetrics)
|
||||
return updatedMetrics
|
||||
}
|
||||
|
||||
override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||
let metrics = self.layoutMetrics(for: constrainedSize.height)
|
||||
let metrics = self.layoutMetrics(for: constrainedSize)
|
||||
for node in self.optionNodes {
|
||||
let _ = node.measure(constrainedSize)
|
||||
}
|
||||
|
|
@ -595,7 +612,7 @@ public final class ItemListRevealOptionsNode: ASDisplayNode {
|
|||
if size.width.isLessThanOrEqualTo(0.0) || self.optionNodes.isEmpty {
|
||||
return
|
||||
}
|
||||
let metrics = self.layoutMetrics(for: size.height)
|
||||
let metrics = self.layoutMetrics(for: size)
|
||||
let revealedDistance = abs(self.revealOffset)
|
||||
let boundedRevealedDistance = min(revealedDistance, size.width)
|
||||
let overswipeDistance = max(0.0, revealedDistance - size.width)
|
||||
|
|
|
|||
|
|
@ -506,7 +506,11 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
|||
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: layoutSize.width - params.rightInset - bottomStripeInset - separatorRightInset, height: separatorHeight)))
|
||||
}
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: topInset + 1.0), size: titleLayout.size)
|
||||
var titleOriginY = floorToScreenPixels((contentSize.height - titleLayout.size.height) / 2.0) + 1.0
|
||||
if textLayoutAndApply != nil {
|
||||
titleOriginY = topInset + 1.0
|
||||
}
|
||||
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: titleOriginY), size: titleLayout.size)
|
||||
transition.updatePosition(node: strongSelf.titleNode, position: titleFrame.origin)
|
||||
strongSelf.titleNode.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
|
||||
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@
|
|||
- (void)editorTransitionIn;
|
||||
- (void)editorTransitionOut;
|
||||
|
||||
- (bool)canBeginEditingCaption;
|
||||
- (void)beginEditingCaption;
|
||||
- (void)setupGifEditing;
|
||||
|
||||
|
|
|
|||
|
|
@ -39,4 +39,7 @@
|
|||
|
||||
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
|
||||
|
||||
- (bool)canBeginEditingCaption;
|
||||
- (void)beginEditingCaption;
|
||||
|
||||
@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 {
|
||||
[_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>
|
||||
{
|
||||
NSMutableDictionary *_reusableItemViewsByIdentifier;
|
||||
|
|
@ -1759,6 +1773,28 @@ static CGFloat transformRotation(CGAffineTransform transform)
|
|||
}
|
||||
}
|
||||
|
||||
- (bool)_canBeginEditingCaptionFromKeyCommand
|
||||
{
|
||||
UIView<TGModernGalleryInterfaceView> *interfaceView = _view.interfaceView;
|
||||
if (interfaceView == nil) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (![interfaceView respondsToSelector:@selector(beginEditingCaption)]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ([interfaceView respondsToSelector:@selector(canBeginEditingCaption)] && ![interfaceView canBeginEditingCaption]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TGModernGalleryViewHasFirstResponder(_view)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
- (void)processKeyCommand:(UIKeyCommand *)keyCommand
|
||||
{
|
||||
if ([keyCommand.input isEqualToString:UIKeyInputLeftArrow])
|
||||
|
|
@ -1777,17 +1813,27 @@ static CGFloat transformRotation(CGAffineTransform transform)
|
|||
{
|
||||
_view.transitionOut(0.0f);
|
||||
}
|
||||
else if ([keyCommand.input isEqualToString:@"\r"])
|
||||
{
|
||||
if ([self _canBeginEditingCaptionFromKeyCommand])
|
||||
[_view.interfaceView beginEditingCaption];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)availableKeyCommands
|
||||
{
|
||||
return @
|
||||
NSMutableArray *commands = [[NSMutableArray alloc] initWithArray:@
|
||||
[
|
||||
[TGKeyCommand keyCommandWithTitle:nil input:UIKeyInputLeftArrow modifierFlags:0],
|
||||
[TGKeyCommand keyCommandWithTitle:nil input:UIKeyInputRightArrow modifierFlags:0],
|
||||
[TGKeyCommand keyCommandWithTitle:nil input:UIKeyInputEscape modifierFlags:0],
|
||||
[TGKeyCommand keyCommandWithTitle:nil input:@"\t" modifierFlags:0]
|
||||
];
|
||||
]];
|
||||
|
||||
if ([self _canBeginEditingCaptionFromKeyCommand])
|
||||
[commands addObject:[TGKeyCommand keyCommandWithTitle:nil input:@"\r" modifierFlags:0]];
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
- (bool)isExclusive
|
||||
|
|
|
|||
|
|
@ -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
|
||||
switch component.doneButtonType {
|
||||
case TGPhotoEditorDoneButtonSend:
|
||||
doneIconName = "Chat/Input/Text/SendIcon"
|
||||
doneIconName = "Media Editor/Send"
|
||||
doneWidth = 46.0
|
||||
case TGPhotoEditorDoneButtonSchedule:
|
||||
doneIconName = "Chat/Input/ScheduleIcon"
|
||||
|
|
|
|||
|
|
@ -1179,9 +1179,12 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
|
|||
guardBotId = currentGuardBotId
|
||||
approveMembersInfo += " " + presentationData.strings.Group_Setup_ApproveNewMembersManagedBy("[@\(guardBotUsername)](guardbot)").string
|
||||
}
|
||||
|
||||
entries.append(.approveMembers(presentationData.theme, approveMembersTitle, approveMembers))
|
||||
entries.append(.approveMembersInfo(presentationData.theme, approveMembersInfo, guardBotId))
|
||||
|
||||
if !isGroup && selectedType == .publicChannel {
|
||||
} else {
|
||||
entries.append(.approveMembers(presentationData.theme, approveMembersTitle, approveMembers))
|
||||
entries.append(.approveMembersInfo(presentationData.theme, approveMembersInfo, guardBotId))
|
||||
}
|
||||
|
||||
entries.append(.forwardingHeader(presentationData.theme, isGroup ? presentationData.strings.Group_Setup_ForwardingGroupTitle.uppercased() : presentationData.strings.Group_Setup_ForwardingChannelTitle.uppercased()))
|
||||
entries.append(.forwardingDisabled(presentationData.theme, presentationData.strings.Group_Setup_ForwardingDisabled, !forwardingEnabled))
|
||||
|
|
|
|||
|
|
@ -178,6 +178,11 @@ public func archiveSettingsController(context: AccountContext) -> ViewController
|
|||
)
|
||||
|> deliverOnMainQueue
|
||||
|> map { presentationData, settings, appConfiguration, accountPeer -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
var presentationData = presentationData
|
||||
|
||||
let updatedTheme = presentationData.theme.withModalBlocksBackground()
|
||||
presentationData = presentationData.withUpdated(theme: updatedTheme)
|
||||
|
||||
let isPremium = accountPeer?.isPremium ?? false
|
||||
let isPremiumDisabled = PremiumConfiguration.with(appConfiguration: appConfiguration).isPremiumDisabled
|
||||
|
||||
|
|
|
|||
|
|
@ -292,7 +292,7 @@ public func logoutOptionsController(context: AccountContext, navigationControlle
|
|||
navigationController?.pushViewController(value, animated: false)
|
||||
}
|
||||
presentControllerImpl = { [weak controller] value, arguments in
|
||||
controller?.present(value, in: .window(.root), with: arguments ?? ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
controller?.present(value, in: .window(.root), with: arguments)
|
||||
}
|
||||
replaceTopControllerImpl = { [weak navigationController] c in
|
||||
navigationController?.replaceTopController(c, animated: true)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
|
||||
"//submodules/TelegramUI/Components/Ads/AdsReportScreen",
|
||||
"//submodules/TelegramUI/Components/LottieComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import UndoUI
|
|||
import GlassBarButtonComponent
|
||||
import ButtonComponent
|
||||
import AdsReportScreen
|
||||
import LottieComponent
|
||||
|
||||
private let moreTag = GenericComponentViewTag()
|
||||
|
||||
|
|
@ -135,7 +136,7 @@ private final class SheetContent: CombinedComponent {
|
|||
context.add(iconBackground.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + iconBackground.size.height / 2.0)))
|
||||
|
||||
let icon = icon.update(
|
||||
component: BundleIconComponent(name: "Ads/AdsLogo", tintColor: theme.list.itemCheckColors.foregroundColor),
|
||||
component: BundleIconComponent(name: "Ads/AdsLogo", tintColor: .white),
|
||||
availableSize: CGSize(width: 90.0, height: 90.0),
|
||||
transition: .immediate
|
||||
)
|
||||
|
|
@ -523,6 +524,8 @@ private final class AdsInfoSheetComponent: CombinedComponent {
|
|||
let sheet = Child(ResizableSheetComponent<(EnvironmentType)>.self)
|
||||
let animateOut = StoredActionSlot(Action<Void>.self)
|
||||
|
||||
let moreButtonPlayOnce = ActionSlot<Void>()
|
||||
|
||||
return { context in
|
||||
let environment = context.environment[EnvironmentType.self]
|
||||
let controller = environment.controller
|
||||
|
|
@ -569,14 +572,19 @@ private final class AdsInfoSheetComponent: CombinedComponent {
|
|||
component: AnyComponentWithIdentity(
|
||||
id: "more",
|
||||
component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Chat/Context Menu/More",
|
||||
tintColor: theme.chat.inputPanel.panelControlColor
|
||||
LottieComponent(
|
||||
content: LottieComponent.AppBundleContent(
|
||||
name: "anim_morewide"
|
||||
),
|
||||
color: theme.chat.inputPanel.panelControlColor,
|
||||
size: CGSize(width: 34.0, height: 34.0),
|
||||
playOnce: moreButtonPlayOnce
|
||||
)
|
||||
)
|
||||
),
|
||||
action: { _ in
|
||||
(controller() as? AdsInfoScreen)?.infoPressed()
|
||||
moreButtonPlayOnce.invoke(Void())
|
||||
},
|
||||
tag: moreTag
|
||||
)
|
||||
|
|
|
|||
|
|
@ -116,7 +116,11 @@ public final class NavigationButtonComponent: Component {
|
|||
|
||||
switch component.content {
|
||||
case let .text(title, isBold):
|
||||
textString = NSAttributedString(string: title, font: isBold ? Font.bold(17.0) : Font.medium(17.0), textColor: theme.chat.inputPanel.panelControlColor)
|
||||
if title == "___done" {
|
||||
imageName = "Navigation/Done"
|
||||
} else {
|
||||
textString = NSAttributedString(string: title, font: isBold ? Font.bold(17.0) : Font.medium(17.0), textColor: theme.chat.inputPanel.panelControlColor)
|
||||
}
|
||||
case .more:
|
||||
isMore = true
|
||||
case let .icon(imageNameValue):
|
||||
|
|
|
|||
|
|
@ -1764,8 +1764,7 @@ private final class ChatThemeSheetContentComponent: Component {
|
|||
style: .glass,
|
||||
color: destructiveColor.withMultipliedAlpha(0.1),
|
||||
foreground: destructiveColor,
|
||||
pressedColor: destructiveColor.withMultipliedAlpha(0.2),
|
||||
cornerRadius: 22.0
|
||||
pressedColor: destructiveColor.withMultipliedAlpha(0.2)
|
||||
),
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable("resetWallpaper"),
|
||||
|
|
|
|||
|
|
@ -632,7 +632,7 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
|
|||
}
|
||||
|
||||
self.buttonsContainerNode = SparseNode()
|
||||
self.buttonsContainerNode.clipsToBounds = true
|
||||
//self.buttonsContainerNode.clipsToBounds = true
|
||||
|
||||
self.backButtonNodeImpl.color = self.presentationData.theme.buttonColor
|
||||
self.backButtonNodeImpl.disabledColor = self.presentationData.theme.disabledButtonColor
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import ContextUI
|
|||
import AvatarNode
|
||||
import PlainButtonComponent
|
||||
import ToastComponent
|
||||
import TextFormat
|
||||
|
||||
private final class JoinAffiliateProgramScreenComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
|
@ -1220,13 +1221,28 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
|||
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemSecondaryTextColor),
|
||||
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.itemSecondaryTextColor),
|
||||
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemAccentColor),
|
||||
linkAttribute: { url in
|
||||
return ("URL", url)
|
||||
linkAttribute: { contents in
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
}
|
||||
)
|
||||
),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0
|
||||
maximumNumberOfLines: 0,
|
||||
highlightColor: environment.theme.list.itemAccentColor.withAlphaComponent(0.2),
|
||||
highlightAction: { attributes in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
||||
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
},
|
||||
tapAction: { [weak self] attributes, _ in
|
||||
guard let environment = self?.environment, let controller = environment.controller(), let navigationController = controller.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: environment.strings.Stars_Purchase_Terms_URL, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {})
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0)
|
||||
|
|
|
|||
|
|
@ -142,6 +142,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/Settings/PeerNameColorItem",
|
||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||
"//submodules/TelegramUI/Components/TextLoadingEffect",
|
||||
"//submodules/TelegramUI/Components/TextProcessingScreen",
|
||||
"//submodules/TelegramUI/Components/Settings/BirthdayPickerScreen",
|
||||
"//submodules/TelegramUI/Components/Settings/PeerSelectionScreen",
|
||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import AsyncDisplayKit
|
|||
import TelegramUIPreferences
|
||||
import ContextUI
|
||||
import TranslateUI
|
||||
import TextProcessingScreen
|
||||
import Pasteboard
|
||||
import UndoUI
|
||||
|
||||
extension PeerInfoScreenNode {
|
||||
|
|
@ -105,16 +107,24 @@ extension PeerInfoScreenNode {
|
|||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let controller = TranslateScreen(context: self.context, text: bioText, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages)
|
||||
controller.pushController = { [weak self] c in
|
||||
(self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true
|
||||
self?.controller?.push(c)
|
||||
|
||||
Task { @MainActor [weak self] in
|
||||
guard let self, let parentController = self.controller else {
|
||||
return
|
||||
}
|
||||
let presentationData = self.presentationData
|
||||
let controller = await TextProcessingScreen(
|
||||
context: self.context,
|
||||
mode: .translate(fromLanguage: language, applyResult: nil),
|
||||
inputText: TextWithEntities(text: bioText, entities: []),
|
||||
copyResult: { [weak parentController] text in
|
||||
storeMessageTextInPasteboard(text.text, entities: text.entities)
|
||||
parentController?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||
},
|
||||
translateChat: nil
|
||||
)
|
||||
parentController.present(controller, in: .window(.root))
|
||||
}
|
||||
controller.presentController = { [weak self] c in
|
||||
self?.controller?.present(c, in: .window(.root))
|
||||
}
|
||||
self.controller?.present(controller, in: .window(.root))
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import TelegramCore
|
|||
import AsyncDisplayKit
|
||||
import UndoUI
|
||||
import TranslateUI
|
||||
import TextProcessingScreen
|
||||
import Pasteboard
|
||||
import TelegramStringFormatting
|
||||
import TelegramUIPreferences
|
||||
|
||||
|
|
@ -70,16 +72,22 @@ extension PeerInfoScreenNode {
|
|||
let (canTranslate, language) = canTranslateText(context: context, text: text, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: false, ignoredLanguages: translationSettings.ignoredLanguages)
|
||||
if canTranslate {
|
||||
actions.append(ContextMenuAction(content: .text(title: presentationData.strings.Conversation_ContextMenuTranslate, accessibilityLabel: presentationData.strings.Conversation_ContextMenuTranslate), action: { [weak self] in
|
||||
|
||||
let controller = TranslateScreen(context: context, text: text, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages)
|
||||
controller.pushController = { [weak self] c in
|
||||
(self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true
|
||||
self?.controller?.push(c)
|
||||
Task { @MainActor [weak self] in
|
||||
guard let self, let parentController = self.controller else {
|
||||
return
|
||||
}
|
||||
let controller = await TextProcessingScreen(
|
||||
context: context,
|
||||
mode: .translate(fromLanguage: language, applyResult: nil),
|
||||
inputText: TextWithEntities(text: text, entities: []),
|
||||
copyResult: { [weak parentController] text in
|
||||
storeMessageTextInPasteboard(text.text, entities: text.entities)
|
||||
parentController?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||
},
|
||||
translateChat: nil
|
||||
)
|
||||
parentController.present(controller, in: .window(.root))
|
||||
}
|
||||
controller.presentController = { [weak self] c in
|
||||
self?.controller?.present(c, in: .window(.root))
|
||||
}
|
||||
self?.controller?.present(controller, in: .window(.root))
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -186,6 +186,7 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent {
|
|||
let component = context.component
|
||||
let scrollEnvironment = context.environment[ScrollChildEnvironment.self].value
|
||||
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
|
||||
let controller = environment.controller
|
||||
let state = context.state
|
||||
|
||||
state.products = component.products
|
||||
|
|
@ -475,7 +476,10 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent {
|
|||
}
|
||||
},
|
||||
tapAction: { attributes, _ in
|
||||
component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: strings.Stars_Purchase_Terms_URL, forceExternal: false, presentationData: presentationData, navigationController: nil, dismissInput: {})
|
||||
guard let controller = controller(), let navigationController = controller.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: strings.Stars_Purchase_Terms_URL, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {})
|
||||
}
|
||||
),
|
||||
environment: {},
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ swift_library(
|
|||
"//submodules/TextSelectionNode",
|
||||
"//submodules/Pasteboard",
|
||||
"//submodules/Speak",
|
||||
"//submodules/TelegramUI/Components/TextProcessingScreen",
|
||||
"//submodules/TranslateUI",
|
||||
"//submodules/TelegramNotices",
|
||||
"//submodules/MediaPlayer:UniversalMediaPlayer",
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ import ChatScheduleTimeController
|
|||
import StoryStealthModeSheetScreen
|
||||
import Speak
|
||||
import TranslateUI
|
||||
import TextProcessingScreen
|
||||
import Pasteboard
|
||||
import TelegramNotices
|
||||
import ObjectiveC
|
||||
import LocationUI
|
||||
|
|
@ -1543,32 +1545,39 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) {
|
|||
|
||||
let _ = ApplicationSpecificNotice.incrementTranslationSuggestion(accountManager: component.context.sharedContext.accountManager, timestamp: Int32(Date().timeIntervalSince1970)).start()
|
||||
|
||||
let translateController = TranslateScreen(context: component.context, forceTheme: defaultDarkPresentationTheme, text: text, entities: entities, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages)
|
||||
translateController.pushController = { [weak view] c in
|
||||
guard let view, let component = view.component else {
|
||||
Task { @MainActor [weak self, weak view] in
|
||||
guard let self, let view, let component = view.component else {
|
||||
return
|
||||
}
|
||||
component.controller()?.push(c)
|
||||
}
|
||||
translateController.presentController = { [weak view] c in
|
||||
guard let view, let component = view.component else {
|
||||
return
|
||||
}
|
||||
component.controller()?.present(c, in: .window(.root))
|
||||
}
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let translateController = await TextProcessingScreen(
|
||||
context: component.context,
|
||||
theme: defaultDarkPresentationTheme,
|
||||
mode: .translate(fromLanguage: language, applyResult: nil),
|
||||
inputText: TextWithEntities(text: text, entities: entities),
|
||||
copyResult: { [weak view] text in
|
||||
guard let component = view?.component else {
|
||||
return
|
||||
}
|
||||
storeMessageTextInPasteboard(text.text, entities: text.entities)
|
||||
component.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||
},
|
||||
translateChat: nil
|
||||
)
|
||||
|
||||
self.actionSheet = translateController
|
||||
view.updateIsProgressPaused()
|
||||
|
||||
translateController.wasDismissed = { [weak self, weak view] in
|
||||
guard let self, let view else {
|
||||
return
|
||||
}
|
||||
self.actionSheet = nil
|
||||
self.actionSheet = translateController
|
||||
view.updateIsProgressPaused()
|
||||
}
|
||||
|
||||
component.controller()?.present(translateController, in: .window(.root))
|
||||
translateController.wasDismissed = { [weak self, weak view] in
|
||||
guard let self, let view else {
|
||||
return
|
||||
}
|
||||
self.actionSheet = nil
|
||||
view.updateIsProgressPaused()
|
||||
}
|
||||
|
||||
component.controller()?.present(translateController, in: .window(.root))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -791,7 +791,11 @@ public final class TabBarComponent: Component {
|
|||
var itemFrame = CGRect(origin: CGPoint(x: nextItemX, y: floor((tabsSize.height - itemSize.height) * 0.5)), size: itemSize)
|
||||
nextItemX += itemSize.width
|
||||
if isItemSelected {
|
||||
selectionFrame = itemFrame
|
||||
if itemFrame.size.width < itemFrame.size.height {
|
||||
selectionFrame = itemFrame.insetBy(dx: floor((itemFrame.size.height * 1.2 - itemFrame.size.width) * -0.5), dy: 0.0)
|
||||
} else {
|
||||
selectionFrame = itemFrame
|
||||
}
|
||||
}
|
||||
|
||||
if let itemComponentView = itemView.view as? ItemComponent.View, let selectedItemComponentView = selectedItemView.view as? ItemComponent.View {
|
||||
|
|
|
|||
|
|
@ -471,6 +471,7 @@ final class TextProcessingContentComponent: Component {
|
|||
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.2)
|
||||
|
||||
let environment = environment[ViewControllerComponentContainer.Environment.self].value
|
||||
let theme = environment.theme.withModalBlocksBackground()
|
||||
|
||||
let isFirstTime = self.component == nil
|
||||
|
||||
|
|
@ -516,8 +517,8 @@ final class TextProcessingContentComponent: Component {
|
|||
content: .animation(
|
||||
content: .file(file: previewIconFile),
|
||||
size: previewIconSize,
|
||||
placeholderColor: environment.theme.list.mediaPlaceholderColor,
|
||||
themeColor: environment.theme.list.itemPrimaryTextColor,
|
||||
placeholderColor: theme.list.mediaPlaceholderColor,
|
||||
themeColor: theme.list.itemPrimaryTextColor,
|
||||
loopMode: .count(1)
|
||||
),
|
||||
isVisibleForAnimations: true,
|
||||
|
|
@ -557,7 +558,7 @@ final class TextProcessingContentComponent: Component {
|
|||
let titleSize = previewTitle.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: style.title, font: Font.bold(30.0), textColor: environment.theme.list.itemPrimaryTextColor))
|
||||
text: .plain(NSAttributedString(string: style.title, font: Font.bold(30.0), textColor: theme.list.itemPrimaryTextColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width, height: 100.0)
|
||||
|
|
@ -576,7 +577,7 @@ final class TextProcessingContentComponent: Component {
|
|||
let descriptionSize = previewDescription.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: environment.strings.TextProcessing_StylePreview_Subtitle, font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor)),
|
||||
text: .plain(NSAttributedString(string: environment.strings.TextProcessing_StylePreview_Subtitle, font: Font.regular(15.0), textColor: theme.list.itemPrimaryTextColor)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.12
|
||||
|
|
@ -683,7 +684,7 @@ final class TextProcessingContentComponent: Component {
|
|||
let modeTabsSize = self.modeTabs.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(TabBarComponent(
|
||||
theme: environment.theme,
|
||||
theme: theme,
|
||||
tintSelectedItem: false,
|
||||
isLiftedStateEnabled: false,
|
||||
strings: environment.strings,
|
||||
|
|
@ -725,7 +726,7 @@ final class TextProcessingContentComponent: Component {
|
|||
contentExternalState = self.translateState
|
||||
contentComponent = AnyComponent(TextProcessingTranslateContentComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
theme: theme,
|
||||
strings: environment.strings,
|
||||
styles: component.styles,
|
||||
externalState: self.translateState,
|
||||
|
|
@ -771,7 +772,7 @@ final class TextProcessingContentComponent: Component {
|
|||
contentExternalState = self.stylizeState
|
||||
contentComponent = AnyComponent(TextProcessingTranslateContentComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
theme: theme,
|
||||
strings: environment.strings,
|
||||
styles: component.styles,
|
||||
externalState: self.stylizeState,
|
||||
|
|
@ -892,7 +893,7 @@ final class TextProcessingContentComponent: Component {
|
|||
contentExternalState = self.fixState
|
||||
contentComponent = AnyComponent(TextProcessingTranslateContentComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
theme: theme,
|
||||
strings: environment.strings,
|
||||
styles: component.styles,
|
||||
externalState: self.fixState,
|
||||
|
|
@ -997,7 +998,7 @@ final class TextProcessingContentComponent: Component {
|
|||
if self.currentContentBackground.image == nil {
|
||||
self.currentContentBackground.image = generateStretchableFilledCircleImage(diameter: 60.0, color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
self.currentContentBackground.tintColor = environment.theme.list.itemBlocksBackgroundColor
|
||||
self.currentContentBackground.tintColor = theme.list.itemBlocksBackgroundColor
|
||||
transition.setFrame(view: self.currentContentBackground, frame: contentFrame)
|
||||
contentHeight += contentSize.height
|
||||
|
||||
|
|
@ -1057,19 +1058,19 @@ final class TextProcessingContentComponent: Component {
|
|||
if case .translate = component.mode {
|
||||
if let copyTranslation = component.copyCurrentResult {
|
||||
actionsSectionItems.append(AnyComponentWithIdentity(id: "copy", component: AnyComponent(ListActionItemComponent(
|
||||
theme: environment.theme,
|
||||
theme: theme,
|
||||
style: .glass,
|
||||
title: AnyComponent(
|
||||
MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: environment.strings.Translate_CopyTranslation,
|
||||
font: Font.regular(17.0),
|
||||
textColor: environment.theme.list.itemAccentColor
|
||||
textColor: theme.list.itemAccentColor
|
||||
)),
|
||||
maximumNumberOfLines: 1
|
||||
)
|
||||
),
|
||||
leftIcon: .custom(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Copy", tintColor: environment.theme.list.itemAccentColor))), false),
|
||||
leftIcon: .custom(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Copy", tintColor: theme.list.itemAccentColor))), false),
|
||||
action: { _ in
|
||||
copyTranslation()
|
||||
}
|
||||
|
|
@ -1077,19 +1078,19 @@ final class TextProcessingContentComponent: Component {
|
|||
}
|
||||
if let translateChat = component.translateChat {
|
||||
actionsSectionItems.append(AnyComponentWithIdentity(id: "translate", component: AnyComponent(ListActionItemComponent(
|
||||
theme: environment.theme,
|
||||
theme: theme,
|
||||
style: .glass,
|
||||
title: AnyComponent(
|
||||
MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: environment.strings.Localization_TranslateEntireChat,
|
||||
font: Font.regular(17.0),
|
||||
textColor: environment.theme.list.itemAccentColor
|
||||
textColor: theme.list.itemAccentColor
|
||||
)),
|
||||
maximumNumberOfLines: 1
|
||||
)
|
||||
),
|
||||
leftIcon: .custom(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Translate", tintColor: environment.theme.list.itemAccentColor))), false),
|
||||
leftIcon: .custom(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Translate", tintColor: theme.list.itemAccentColor))), false),
|
||||
action: { [weak self] _ in
|
||||
guard let self, let language = self.translateState.result?.language else {
|
||||
return
|
||||
|
|
@ -1105,7 +1106,7 @@ final class TextProcessingContentComponent: Component {
|
|||
let actionsSectionSize = self.actionsSection.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(ListSectionComponent(
|
||||
theme: environment.theme,
|
||||
theme: theme,
|
||||
style: .glass,
|
||||
header: nil,
|
||||
footer: nil,
|
||||
|
|
@ -1368,7 +1369,7 @@ private final class TextProcessingSheetComponent: Component {
|
|||
let environmentValue = environment[ViewControllerComponentContainer.Environment.self].value
|
||||
self.environment = environmentValue
|
||||
let controller = environmentValue.controller
|
||||
let theme = environmentValue.theme
|
||||
let theme = environmentValue.theme.withModalBlocksBackground()
|
||||
|
||||
let dismiss: (Bool) -> Void = { [weak self] animated in
|
||||
if animated {
|
||||
|
|
@ -1427,11 +1428,23 @@ private final class TextProcessingSheetComponent: Component {
|
|||
}
|
||||
dismiss(true)
|
||||
}
|
||||
case .translate:
|
||||
actionButtonTitle = environmentValue.strings.TextProcessing_ActionClose
|
||||
isMainActionEnabled = true
|
||||
performMainAction = {
|
||||
dismiss(true)
|
||||
case let .translate(_, applyResult):
|
||||
if let applyResult {
|
||||
actionButtonTitle = environmentValue.strings.TextProcessing_ActionApply
|
||||
isMainActionEnabled = !self.contentExternalState.isProcessing && self.contentExternalState.result != nil
|
||||
performMainAction = { [weak self] in
|
||||
guard let self, let result = self.contentExternalState.result else {
|
||||
return
|
||||
}
|
||||
applyResult(result)
|
||||
dismiss(true)
|
||||
}
|
||||
} else {
|
||||
actionButtonTitle = environmentValue.strings.TextProcessing_ActionClose
|
||||
isMainActionEnabled = true
|
||||
performMainAction = {
|
||||
dismiss(true)
|
||||
}
|
||||
}
|
||||
case let .preview(style, _, _, isAlreadyAdded, added):
|
||||
actionButtonTitle = isAlreadyAdded ? environmentValue.strings.TextProcessing_StyleMenu_ButtonClose : environmentValue.strings.TextProcessing_StyleMenu_ButtonAdd
|
||||
|
|
|
|||
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 Speak
|
||||
import TranslateUI
|
||||
import TextProcessingScreen
|
||||
import UniversalMediaPlayer
|
||||
import WallpaperBackgroundNode
|
||||
import ChatListUI
|
||||
|
|
@ -4625,27 +4626,56 @@ extension ChatControllerImpl {
|
|||
return
|
||||
}
|
||||
let (_, language) = canTranslateText(context: context, text: text.string, showTranslate: true, ignoredLanguages: nil)
|
||||
|
||||
|
||||
let entities = generateChatInputTextEntities(text)
|
||||
presentTranslateScreen(
|
||||
context: self.context,
|
||||
text: text.string,
|
||||
entities: entities,
|
||||
canCopy: true,
|
||||
fromLanguage: language,
|
||||
replaceText: { text, entities in
|
||||
replace(chatInputStateStringWithAppliedEntities(text, entities: entities))
|
||||
},
|
||||
pushController: { [weak self] c in
|
||||
self?.push(c)
|
||||
},
|
||||
presentController: { [weak self] c in
|
||||
self?.present(c, in: .window(.root))
|
||||
},
|
||||
display: { [weak self] c in
|
||||
self?.push(c)
|
||||
|
||||
let translationConfiguration = TranslationConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
|
||||
var useSystemTranslation = false
|
||||
switch translationConfiguration.manual {
|
||||
case .system:
|
||||
if #available(iOS 18.0, *) {
|
||||
useSystemTranslation = true
|
||||
}
|
||||
)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if useSystemTranslation {
|
||||
presentTranslateScreen(
|
||||
context: self.context,
|
||||
text: text.string,
|
||||
entities: entities,
|
||||
canCopy: true,
|
||||
fromLanguage: language,
|
||||
replaceText: { text, entities in
|
||||
replace(chatInputStateStringWithAppliedEntities(text, entities: entities))
|
||||
},
|
||||
pushController: { [weak self] c in
|
||||
self?.push(c)
|
||||
},
|
||||
presentController: { [weak self] c in
|
||||
self?.present(c, in: .window(.root))
|
||||
},
|
||||
display: { [weak self] c in
|
||||
self?.push(c)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
Task { @MainActor [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.push(await TextProcessingScreen(
|
||||
context: self.context,
|
||||
mode: .translate(fromLanguage: language, applyResult: { text in
|
||||
replace(chatInputStateStringWithAppliedEntities(text.text, entities: text.entities))
|
||||
}),
|
||||
inputText: TextWithEntities(text: text.string, entities: entities),
|
||||
copyResult: nil,
|
||||
translateChat: nil
|
||||
))
|
||||
}
|
||||
}
|
||||
}, sendEmoji: { [weak self] text, attribute, immediately in
|
||||
guard let self else {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -4328,7 +4328,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
} else {
|
||||
self.push(await TextProcessingScreen(
|
||||
context: self.context,
|
||||
mode: .translate(fromLanguage: language),
|
||||
mode: .translate(fromLanguage: language, applyResult: nil),
|
||||
inputText: TextWithEntities(text: text.string, entities: entities ?? []),
|
||||
copyResult: canCopy ? { [weak self] text in
|
||||
guard let self else {
|
||||
|
|
|
|||
|
|
@ -87,8 +87,14 @@ final class ChatMessageContextExtractedContentSource: ContextExtractedContentSou
|
|||
return
|
||||
}
|
||||
if item.content.contains(where: { $0.0.stableId == self.message.stableId }), let contentNode = itemNode.getMessageContextSourceNode(stableId: self.selectAll ? nil : self.message.stableId) {
|
||||
result = ContextControllerTakeViewInfo(containingItem: .node(contentNode), contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil), sourceTransitionSurface: chatNode.ensureContextTransitionContainer())
|
||||
|
||||
let sourceTransitionSurface: UIView?
|
||||
if self.snapshot {
|
||||
sourceTransitionSurface = nil
|
||||
} else {
|
||||
sourceTransitionSurface = chatNode.ensureContextTransitionContainer()
|
||||
}
|
||||
result = ContextControllerTakeViewInfo(containingItem: .node(contentNode), contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil), sourceTransitionSurface: sourceTransitionSurface)
|
||||
|
||||
if self.snapshot, let snapshotView = contentNode.contentNode.view.snapshotContentTree(unhide: false, keepPortals: true, keepTransform: true) {
|
||||
contentNode.view.superview?.addSubview(snapshotView)
|
||||
self.snapshotView = snapshotView
|
||||
|
|
@ -112,7 +118,13 @@ final class ChatMessageContextExtractedContentSource: ContextExtractedContentSou
|
|||
return
|
||||
}
|
||||
if item.content.contains(where: { $0.0.stableId == self.message.stableId }) {
|
||||
result = ContextControllerPutBackViewInfo(contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil), sourceTransitionSurface: chatNode.ensureContextTransitionContainer())
|
||||
let sourceTransitionSurface: UIView?
|
||||
if self.snapshot {
|
||||
sourceTransitionSurface = nil
|
||||
} else {
|
||||
sourceTransitionSurface = chatNode.ensureContextTransitionContainer()
|
||||
}
|
||||
result = ContextControllerPutBackViewInfo(contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil), sourceTransitionSurface: sourceTransitionSurface)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
"pl",
|
||||
"pt",
|
||||
"pt-BR",
|
||||
"pa",
|
||||
"ro",
|
||||
"ru",
|
||||
|
|
@ -132,7 +133,7 @@ public var popularTranslationLanguages = [
|
|||
"it",
|
||||
"ja",
|
||||
"ko",
|
||||
"pt",
|
||||
"pt-BR",
|
||||
"ru",
|
||||
"es",
|
||||
"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 self.botAddress == ageBotUsername {
|
||||
return true
|
||||
} else if self.botAddress == "mod_bot" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue