mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
ed6511a79e
22 changed files with 565 additions and 80 deletions
|
|
@ -1537,14 +1537,18 @@ public protocol SharedAccountContext: AnyObject {
|
|||
|
||||
func makeLoginEmailSetupController(context: AccountContext, blocking: Bool, emailPattern: String?, canAutoDismissIfNeeded: Bool, navigationController: NavigationController?, completion: @escaping () -> Void, dismiss: @escaping () -> Void) -> ViewController
|
||||
func makePasskeySetupController(context: AccountContext, displaySkip: Bool, navigationController: NavigationController?, completion: @escaping () -> Void, dismiss: @escaping () -> Void) -> ViewController
|
||||
|
||||
func makeChatCustomRankSetupScreen(context: AccountContext, peerId: EnginePeer.Id, participantId: EnginePeer.Id, rank: String?, role: ChatRankInfoScreenRole) -> ViewController
|
||||
|
||||
func makePeerCopyProtectionInfoScreen(context: AccountContext, completion: @escaping () -> Void) -> ViewController
|
||||
|
||||
func makeChatRankInfoScreen(context: AccountContext, chatPeer: EnginePeer, userPeer: EnginePeer, role: ChatRankInfoScreenRole, rank: String, canChange: Bool, completion: @escaping () -> Void) -> ViewController
|
||||
|
||||
func makeChatRankPreviewItem(context: AccountContext, peer: EnginePeer, rank: String, rankRole: ChatRankInfoScreenRole, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder, sectionId: Int32) -> ListViewItem
|
||||
func makeCreateBotScreen(
|
||||
context: AccountContext,
|
||||
parentBot: EnginePeer.Id,
|
||||
initialUsername: String?,
|
||||
initialTitle: String?,
|
||||
openAutomatically: Bool,
|
||||
completion: @escaping (EnginePeer.Id?) -> Void
|
||||
) async -> ViewController?
|
||||
|
||||
func navigateToCurrentCall()
|
||||
var hasOngoingCall: ValuePromise<Bool> { get }
|
||||
|
|
|
|||
|
|
@ -1382,7 +1382,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
|||
)),
|
||||
hasEntityKeyboard: hasEntityKeyboard,
|
||||
gesture: gesture,
|
||||
sourceSendButton: node,
|
||||
sourceSendButton: node.view,
|
||||
textInputView: textInputNode.textView,
|
||||
emojiViewProvider: textInputPanelNode.emojiViewProvider,
|
||||
completion: {
|
||||
|
|
|
|||
|
|
@ -78,9 +78,9 @@ public func makeChatSendMessageActionSheetController(
|
|||
peerId: EnginePeer.Id?,
|
||||
params: SendMessageActionSheetControllerParams,
|
||||
hasEntityKeyboard: Bool,
|
||||
gesture: ContextGesture,
|
||||
sourceSendButton: ASDisplayNode,
|
||||
textInputView: UITextView,
|
||||
gesture: ContextGesture?,
|
||||
sourceSendButton: UIView,
|
||||
textInputView: UITextView?,
|
||||
emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?,
|
||||
wallpaperBackgroundNode: WallpaperBackgroundNode? = nil,
|
||||
completion: @escaping () -> Void,
|
||||
|
|
|
|||
|
|
@ -59,9 +59,9 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||
let peerId: EnginePeer.Id?
|
||||
let params: SendMessageActionSheetControllerParams
|
||||
let hasEntityKeyboard: Bool
|
||||
let gesture: ContextGesture
|
||||
let sourceSendButton: ASDisplayNode
|
||||
let textInputView: UITextView
|
||||
let gesture: ContextGesture?
|
||||
let sourceSendButton: UIView
|
||||
let textInputView: UITextView?
|
||||
let emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?
|
||||
let wallpaperBackgroundNode: WallpaperBackgroundNode?
|
||||
let completion: () -> Void
|
||||
|
|
@ -80,9 +80,9 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||
peerId: EnginePeer.Id?,
|
||||
params: SendMessageActionSheetControllerParams,
|
||||
hasEntityKeyboard: Bool,
|
||||
gesture: ContextGesture,
|
||||
sourceSendButton: ASDisplayNode,
|
||||
textInputView: UITextView,
|
||||
gesture: ContextGesture?,
|
||||
sourceSendButton: UIView,
|
||||
textInputView: UITextView?,
|
||||
emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?,
|
||||
wallpaperBackgroundNode: WallpaperBackgroundNode?,
|
||||
completion: @escaping () -> Void,
|
||||
|
|
@ -286,7 +286,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
let stableSourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton.view, to: self)
|
||||
let stableSourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton, to: self)
|
||||
if self.stableSourceSendButtonFrame != stableSourceSendButtonFrame {
|
||||
self.stableSourceSendButtonFrame = stableSourceSendButtonFrame
|
||||
if !self.isUpdating {
|
||||
|
|
@ -331,7 +331,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||
self.mediaCaptionIsAbove = editMessage.mediaCaptionIsAbove?.0 ?? false
|
||||
}
|
||||
|
||||
component.gesture.externalUpdated = { [weak self] view, location in
|
||||
component.gesture?.externalUpdated = { [weak self] view, location in
|
||||
guard let self, let actionsStackNode = self.actionsStackNode else {
|
||||
return
|
||||
}
|
||||
|
|
@ -346,7 +346,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||
}
|
||||
actionsStackNode.highlightGestureMoved(location: self.convert(localPoint, to: actionsStackNode.view))
|
||||
}
|
||||
component.gesture.externalEnded = { [weak self] viewAndLocation in
|
||||
component.gesture?.externalEnded = { [weak self] viewAndLocation in
|
||||
guard let self, let actionsStackNode = self.actionsStackNode else {
|
||||
return
|
||||
}
|
||||
|
|
@ -389,7 +389,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||
var isMessageVisible: Bool = mediaPreview != nil
|
||||
|
||||
let textString: NSAttributedString
|
||||
if let attributedText = component.textInputView.attributedText {
|
||||
if let attributedText = component.textInputView?.attributedText {
|
||||
textString = attributedText
|
||||
if textString.length != 0 {
|
||||
isMessageVisible = true
|
||||
|
|
@ -423,23 +423,27 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||
let sourceSendButtonFrame: CGRect
|
||||
switch self.presentationAnimationState {
|
||||
case .animatedOut:
|
||||
sourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton.view, to: self)
|
||||
sourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton, to: self)
|
||||
self.stableSourceSendButtonFrame = sourceSendButtonFrame
|
||||
default:
|
||||
if let stableSourceSendButtonFrame = self.stableSourceSendButtonFrame {
|
||||
sourceSendButtonFrame = stableSourceSendButtonFrame
|
||||
} else {
|
||||
sourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton.view, to: self)
|
||||
sourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton, to: self)
|
||||
self.stableSourceSendButtonFrame = sourceSendButtonFrame
|
||||
}
|
||||
}
|
||||
|
||||
let sendButtonScale: CGFloat
|
||||
switch self.presentationAnimationState {
|
||||
case .initial:
|
||||
sendButtonScale = 0.75
|
||||
default:
|
||||
if component.sourceSendButton is ContextExtractedContentContainingView {
|
||||
sendButtonScale = 1.0
|
||||
} else {
|
||||
switch self.presentationAnimationState {
|
||||
case .initial:
|
||||
sendButtonScale = 0.75
|
||||
default:
|
||||
sendButtonScale = 1.0
|
||||
}
|
||||
}
|
||||
|
||||
var reminders = false
|
||||
|
|
@ -731,7 +735,12 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||
wallpaperBackgroundNode.alpha = 0.0
|
||||
}
|
||||
|
||||
let localSourceTextInputViewFrame = convertFrame(component.textInputView.bounds, from: component.textInputView, to: self)
|
||||
let localSourceTextInputViewFrame: CGRect
|
||||
if let textInputView = component.textInputView {
|
||||
localSourceTextInputViewFrame = convertFrame(textInputView.bounds, from: textInputView, to: self)
|
||||
} else {
|
||||
localSourceTextInputViewFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton, to: self)
|
||||
}
|
||||
|
||||
let sourceMessageTextInsets = UIEdgeInsets(top: 7.0, left: 12.0, bottom: 6.0, right: 20.0)
|
||||
let sourceBackgroundSize = CGSize(width: localSourceTextInputViewFrame.width + 32.0, height: localSourceTextInputViewFrame.height + 4.0)
|
||||
|
|
@ -1116,7 +1125,12 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
let sendButtonSize = CGSize(width: min(sourceSendButtonFrame.width, 40.0), height: sourceSendButtonFrame.height)
|
||||
let sendButtonSize: CGSize
|
||||
if component.sourceSendButton is ContextExtractedContentContainingView {
|
||||
sendButtonSize = sourceSendButtonFrame.size
|
||||
} else {
|
||||
sendButtonSize = CGSize(width: min(sourceSendButtonFrame.width, 40.0), height: sourceSendButtonFrame.height)
|
||||
}
|
||||
var readySendButtonFrame = CGRect(origin: CGPoint(x: sourceSendButtonFrame.maxX - sendButtonSize.width, y: sourceSendButtonFrame.minY), size: sendButtonSize)
|
||||
|
||||
var sourceActionsStackFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 1.0 - 8.0 - actionsStackSize.width, y: sourceMessageItemFrame.maxY + messageActionsSpacing), size: actionsStackSize)
|
||||
|
|
@ -1134,23 +1148,36 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
var readyActionsStackFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 1.0 - actionsStackSize.width, y: readyMessageItemFrame.maxY + messageActionsSpacing), size: actionsStackSize)
|
||||
var readyActionsStackFrame: CGRect
|
||||
if component.sourceSendButton is ContextExtractedContentContainingView {
|
||||
readyActionsStackFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 1.0 - 8.0 - actionsStackSize.width, y: readyMessageItemFrame.maxY + messageActionsSpacing + 4.0), size: actionsStackSize)
|
||||
} else {
|
||||
readyActionsStackFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 1.0 - actionsStackSize.width, y: readyMessageItemFrame.maxY + messageActionsSpacing), size: actionsStackSize)
|
||||
}
|
||||
if !isMessageVisible {
|
||||
readyActionsStackFrame.origin.y = readySendButtonFrame.maxY - readyActionsStackFrame.height - 5.0
|
||||
if component.sourceSendButton is ContextExtractedContentContainingView {
|
||||
readyActionsStackFrame.origin.y = readySendButtonFrame.maxY - readyActionsStackFrame.height
|
||||
} else {
|
||||
readyActionsStackFrame.origin.y = readySendButtonFrame.maxY - readyActionsStackFrame.height - 5.0
|
||||
}
|
||||
}
|
||||
|
||||
let bottomOverflow = readyActionsStackFrame.maxY - (availableSize.height - environment.safeInsets.bottom)
|
||||
if bottomOverflow > 0.0 {
|
||||
readyMessageItemFrame.origin.y -= bottomOverflow
|
||||
readyActionsStackFrame.origin.y -= bottomOverflow
|
||||
readySendButtonFrame.origin.y -= bottomOverflow
|
||||
if !(component.sourceSendButton is ContextExtractedContentContainingView) {
|
||||
readySendButtonFrame.origin.y -= bottomOverflow
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let inputCoverOverflow = readyMessageItemFrame.maxY + 7.0 - (availableSize.height - environment.inputHeight)
|
||||
if inputCoverOverflow > 0.0 {
|
||||
readyMessageItemFrame.origin.y -= inputCoverOverflow
|
||||
readyActionsStackFrame.origin.y -= inputCoverOverflow
|
||||
readySendButtonFrame.origin.y -= inputCoverOverflow
|
||||
if !(component.sourceSendButton is ContextExtractedContentContainingView) {
|
||||
readySendButtonFrame.origin.y -= inputCoverOverflow
|
||||
}
|
||||
}
|
||||
|
||||
if let mediaPreview {
|
||||
|
|
@ -1337,14 +1364,14 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||
return
|
||||
}
|
||||
if mediaPreview == nil {
|
||||
component.textInputView.isHidden = true
|
||||
component.textInputView?.isHidden = true
|
||||
}
|
||||
component.sourceSendButton.isHidden = true
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if mediaPreview == nil {
|
||||
component.textInputView.isHidden = true
|
||||
component.textInputView?.isHidden = true
|
||||
}
|
||||
component.sourceSendButton.isHidden = true
|
||||
}
|
||||
|
|
@ -1356,7 +1383,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||
|
||||
if self.animateOutToEmpty {
|
||||
if mediaPreview == nil {
|
||||
component.textInputView.isHidden = false
|
||||
component.textInputView?.isHidden = false
|
||||
}
|
||||
component.sourceSendButton.isHidden = false
|
||||
|
||||
|
|
@ -1378,7 +1405,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||
self.performedActionsOnAnimateOut = true
|
||||
if let component = self.component, !self.animateOutToEmpty {
|
||||
if mediaPreview == nil {
|
||||
component.textInputView.isHidden = false
|
||||
component.textInputView?.isHidden = false
|
||||
}
|
||||
component.sourceSendButton.isHidden = false
|
||||
}
|
||||
|
|
@ -1431,9 +1458,9 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha
|
|||
peerId: EnginePeer.Id?,
|
||||
params: SendMessageActionSheetControllerParams,
|
||||
hasEntityKeyboard: Bool,
|
||||
gesture: ContextGesture,
|
||||
sourceSendButton: ASDisplayNode,
|
||||
textInputView: UITextView,
|
||||
gesture: ContextGesture?,
|
||||
sourceSendButton: UIView,
|
||||
textInputView: UITextView?,
|
||||
emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?,
|
||||
wallpaperBackgroundNode: WallpaperBackgroundNode?,
|
||||
completion: @escaping () -> Void,
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@ final class SendButton: HighlightTrackingButton {
|
|||
|
||||
private var previousIsAnimatedIn: Bool?
|
||||
private var sourceCustomContentView: UIView?
|
||||
|
||||
private weak var extractedContainerView: ContextExtractedContentContainingView?
|
||||
|
||||
init(kind: Kind) {
|
||||
self.kind = kind
|
||||
|
||||
|
|
@ -64,12 +65,58 @@ final class SendButton: HighlightTrackingButton {
|
|||
context: AccountContext,
|
||||
presentationData: PresentationData,
|
||||
backgroundNode: WallpaperBackgroundNode?,
|
||||
sourceSendButton: ASDisplayNode,
|
||||
sourceSendButton: UIView,
|
||||
isAnimatedIn: Bool,
|
||||
isLoadingEffectAnimation: Bool,
|
||||
size: CGSize,
|
||||
transition: ComponentTransition
|
||||
) {
|
||||
if let extractedContainer = sourceSendButton as? ContextExtractedContentContainingView {
|
||||
self.containerView.isHidden = true
|
||||
self.sourceCustomContentView?.isHidden = true
|
||||
|
||||
let contentView = extractedContainer.contentView
|
||||
|
||||
if self.extractedContainerView !== extractedContainer {
|
||||
self.extractedContainerView = extractedContainer
|
||||
self.previousIsAnimatedIn = nil
|
||||
}
|
||||
|
||||
if self.previousIsAnimatedIn != isAnimatedIn {
|
||||
self.previousIsAnimatedIn = isAnimatedIn
|
||||
|
||||
if isAnimatedIn {
|
||||
extractedContainer.willUpdateIsExtractedToContextPreview?(true, .animated(duration: 0.3, curve: .spring))
|
||||
extractedContainer.isExtractedToContextPreview = true
|
||||
extractedContainer.isExtractedToContextPreviewUpdated?(true)
|
||||
|
||||
extractedContainer.layer.removeAnimation(forKey: "extractedContentReturn")
|
||||
|
||||
self.addSubview(contentView)
|
||||
} else {
|
||||
extractedContainer.willUpdateIsExtractedToContextPreview?(false, .animated(duration: 0.3, curve: .spring))
|
||||
extractedContainer.isExtractedToContextPreview = false
|
||||
extractedContainer.isExtractedToContextPreviewUpdated?(false)
|
||||
|
||||
transition.attachAnimation(view: self, id: "extractedContentReturn", completion: { [weak extractedContainer] _ in
|
||||
guard let extractedContainer else {
|
||||
return
|
||||
}
|
||||
extractedContainer.addSubview(extractedContainer.contentView)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if contentView.superview === self {
|
||||
transition.setFrame(view: contentView, frame: CGRect(origin: CGPoint(), size: size))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
self.containerView.isHidden = false
|
||||
self.extractedContainerView = nil
|
||||
|
||||
let innerSize: CGSize
|
||||
if size.height == 40.0 {
|
||||
innerSize = CGSize(width: size.width - 3.0 * 2.0, height: size.height - 3.0 * 2.0)
|
||||
|
|
@ -93,7 +140,7 @@ final class SendButton: HighlightTrackingButton {
|
|||
self.sourceCustomContentView = nil
|
||||
}
|
||||
|
||||
if let sourceSendButton = sourceSendButton as? ChatSendMessageActionSheetControllerSourceSendButtonNode {
|
||||
if let sourceSendButton = sourceSendButton.asyncdisplaykit_node as? ChatSendMessageActionSheetControllerSourceSendButtonNode {
|
||||
if let sourceCustomContentView = sourceSendButton.makeCustomContents() {
|
||||
self.sourceCustomContentView = sourceCustomContentView
|
||||
sourceCustomContentView.alpha = sourceCustomContentViewAlpha
|
||||
|
|
|
|||
|
|
@ -484,7 +484,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
case 61:
|
||||
self = .copyProtectionRequest(hasExpired: decoder.decodeBoolForKey("hasExpired", orElse: false), previousValue: decoder.decodeBoolForKey("previousValue", orElse: false), newValue: decoder.decodeBoolForKey("newValue", orElse: false))
|
||||
case 62:
|
||||
self = .managedBotCreated(botId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(decoder.decodeInt64ForKey("botId", orElse: 0))))
|
||||
self = .managedBotCreated(botId: PeerId(decoder.decodeInt64ForKey("botId", orElse: 0)))
|
||||
case 63:
|
||||
self = .pollOptionAppended(decoder.decodeObjectForKey("option", decoder: { TelegramMediaPollOption(decoder: $0) }) as! TelegramMediaPollOption)
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -1778,6 +1778,26 @@ public extension TelegramEngine {
|
|||
public func composeAIMessage(text: TextWithEntities, mode: TelegramComposeAIMessageMode) -> Signal<TelegramAIComposeMessageResult, TelegramAIComposeMessageError> {
|
||||
return _internal_composeAIMessage(account: self.account, text: text, mode: mode)
|
||||
}
|
||||
|
||||
public func requestMiniAppButton(peerId: EnginePeer.Id, requestId: String) -> Signal<ReplyMarkupButton?, NoError> {
|
||||
let account = self.account
|
||||
return self.account.postbox.transaction { transaction -> Api.InputUser? in
|
||||
return transaction.getPeer(peerId).flatMap(apiInputUser)
|
||||
}
|
||||
|> mapToSignal { inputUser -> Signal<ReplyMarkupButton?, NoError> in
|
||||
guard let inputUser else {
|
||||
return .single(nil)
|
||||
}
|
||||
return account.network.request(Api.functions.bots.getRequestedWebViewButton(bot: inputUser, webappReqId: requestId))
|
||||
|> map { result -> ReplyMarkupButton in
|
||||
return ReplyMarkupButton(apiButton: result)
|
||||
}
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<ReplyMarkupButton?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -509,7 +509,6 @@ func _internal_composeAIMessage(account: Account, text: TextWithEntities, mode:
|
|||
let inputText: Api.TextWithEntities = .textWithEntities(Api.TextWithEntities.Cons_textWithEntities(text: text.text, entities: apiEntitiesFromMessageTextEntities(text.entities, associatedPeers: SimpleDictionary())))
|
||||
|
||||
return account.network.request(Api.functions.messages.composeMessageWithAI(flags: flags, text: inputText, translateToLang: translateToLang, changeTone: changeTone))
|
||||
|> delay(0.4, queue: .mainQueue())
|
||||
|> `catch` { error -> Signal<Api.messages.ComposedMessageWithAI, TelegramAIComposeMessageError> in
|
||||
if error.errorDescription == "AICOMPOSE_FLOOD_PREMIUM" {
|
||||
return .fail(.nonPremiumFlood)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import Foundation
|
|||
import SwiftSignalKit
|
||||
import Postbox
|
||||
|
||||
public typealias EnginePreferencesEntry = PreferencesEntry
|
||||
|
||||
public extension TelegramEngine {
|
||||
final class Preferences {
|
||||
private let account: Account
|
||||
|
|
|
|||
|
|
@ -389,7 +389,8 @@ public final class ButtonComponent: Component {
|
|||
public let allowActionWhenDisabled: Bool
|
||||
public let displaysProgress: Bool
|
||||
public let action: () -> Void
|
||||
|
||||
public let longPressAction: (() -> Void)?
|
||||
|
||||
public init(
|
||||
background: Background,
|
||||
content: AnyComponentWithIdentity<Empty>,
|
||||
|
|
@ -399,7 +400,8 @@ public final class ButtonComponent: Component {
|
|||
tintWhenDisabled: Bool = true,
|
||||
allowActionWhenDisabled: Bool = false,
|
||||
displaysProgress: Bool = false,
|
||||
action: @escaping () -> Void
|
||||
action: @escaping () -> Void,
|
||||
longPressAction: (() -> Void)? = nil
|
||||
) {
|
||||
self.background = background
|
||||
self.content = content
|
||||
|
|
@ -410,6 +412,7 @@ public final class ButtonComponent: Component {
|
|||
self.allowActionWhenDisabled = allowActionWhenDisabled
|
||||
self.displaysProgress = displaysProgress
|
||||
self.action = action
|
||||
self.longPressAction = longPressAction
|
||||
}
|
||||
|
||||
public static func ==(lhs: ButtonComponent, rhs: ButtonComponent) -> Bool {
|
||||
|
|
@ -437,6 +440,9 @@ public final class ButtonComponent: Component {
|
|||
if lhs.displaysProgress != rhs.displaysProgress {
|
||||
return false
|
||||
}
|
||||
if (lhs.longPressAction == nil) != (rhs.longPressAction == nil) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -462,6 +468,7 @@ public final class ButtonComponent: Component {
|
|||
private var contentItem: ContentItem?
|
||||
|
||||
private var activityIndicator: ActivityIndicator?
|
||||
private var longPressGesture: UILongPressGestureRecognizer?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.containerView = UIView()
|
||||
|
|
@ -479,7 +486,12 @@ public final class ButtonComponent: Component {
|
|||
self.addSubview(self.button)
|
||||
|
||||
self.button.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||
|
||||
|
||||
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.longPressed(_:)))
|
||||
longPressGesture.isEnabled = false
|
||||
self.longPressGesture = longPressGesture
|
||||
self.button.addGestureRecognizer(longPressGesture)
|
||||
|
||||
self.button.highligthedChanged = { [weak self] highlighted in
|
||||
if let self, let component = self.component, component.isEnabled {
|
||||
switch component.background.style {
|
||||
|
|
@ -530,6 +542,13 @@ public final class ButtonComponent: Component {
|
|||
}
|
||||
component.action()
|
||||
}
|
||||
|
||||
@objc private func longPressed(_ gesture: UILongPressGestureRecognizer) {
|
||||
guard gesture.state == .began, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.longPressAction?()
|
||||
}
|
||||
|
||||
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
return super.hitTest(point, with: event)
|
||||
|
|
@ -538,6 +557,8 @@ public final class ButtonComponent: Component {
|
|||
func update(component: ButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
self.componentState = state
|
||||
|
||||
self.longPressGesture?.isEnabled = component.longPressAction != nil
|
||||
|
||||
self.button.isEnabled = (component.isEnabled || component.allowActionWhenDisabled) && !component.displaysProgress
|
||||
|
||||
|
|
|
|||
|
|
@ -315,17 +315,23 @@ private final class CreateBotSheetComponent: Component {
|
|||
let parentPeer: EnginePeer
|
||||
let initialUsername: String?
|
||||
let initialTitle: String?
|
||||
let openAutomatically: Bool
|
||||
let completion: (EnginePeer.Id) -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
parentPeer: EnginePeer,
|
||||
initialUsername: String?,
|
||||
initialTitle: String?
|
||||
initialTitle: String?,
|
||||
openAutomatically: Bool,
|
||||
completion: @escaping (EnginePeer.Id) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.parentPeer = parentPeer
|
||||
self.initialUsername = initialUsername
|
||||
self.initialTitle = initialTitle
|
||||
self.openAutomatically = openAutomatically
|
||||
self.completion = completion
|
||||
}
|
||||
|
||||
static func ==(lhs: CreateBotSheetComponent, rhs: CreateBotSheetComponent) -> Bool {
|
||||
|
|
@ -410,14 +416,14 @@ private final class CreateBotSheetComponent: Component {
|
|||
self.animateOut.invoke(Action { [weak controller, weak navigationController] _ in
|
||||
if let controller, let navigationController {
|
||||
controller.dismiss(completion: { [weak navigationController] in
|
||||
guard let navigationController else {
|
||||
return
|
||||
if component.openAutomatically, let navigationController {
|
||||
component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(
|
||||
navigationController: navigationController,
|
||||
context: context,
|
||||
chatLocation: .peer(botPeer)
|
||||
))
|
||||
}
|
||||
component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(
|
||||
navigationController: navigationController,
|
||||
context: context,
|
||||
chatLocation: .peer(botPeer)
|
||||
))
|
||||
component.completion(botPeer.id)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
@ -573,7 +579,9 @@ public class CreateBotScreen: ViewControllerComponentContainer {
|
|||
context: AccountContext,
|
||||
parentBot: EnginePeer.Id,
|
||||
initialUsername: String?,
|
||||
initialTitle: String?
|
||||
initialTitle: String?,
|
||||
openAutomatically: Bool,
|
||||
completion: @escaping (EnginePeer.Id) -> Void
|
||||
) async {
|
||||
self.context = context
|
||||
|
||||
|
|
@ -589,7 +597,9 @@ public class CreateBotScreen: ViewControllerComponentContainer {
|
|||
context: context,
|
||||
parentPeer: parentPeer,
|
||||
initialUsername: initialUsername,
|
||||
initialTitle: initialTitle
|
||||
initialTitle: initialTitle,
|
||||
openAutomatically: openAutomatically,
|
||||
completion: completion
|
||||
),
|
||||
navigationBarAppearance: .none,
|
||||
statusBarStyle: .ignore,
|
||||
|
|
|
|||
|
|
@ -765,7 +765,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||
)),
|
||||
hasEntityKeyboard: hasEntityKeyboard,
|
||||
gesture: gesture,
|
||||
sourceSendButton: node,
|
||||
sourceSendButton: node.view,
|
||||
textInputView: textInputNode.textView,
|
||||
emojiViewProvider: textInputPanelNode.emojiViewProvider,
|
||||
completion: {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/ToastComponent",
|
||||
"//submodules/TelegramNotices",
|
||||
"//submodules/Markdown",
|
||||
"//submodules/TelegramUIPreferences",
|
||||
"//submodules/ChatSendMessageActionUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ import ListActionItemComponent
|
|||
import ToastComponent
|
||||
import TelegramNotices
|
||||
import Markdown
|
||||
import TelegramUIPreferences
|
||||
import ChatSendMessageActionUI
|
||||
|
||||
final class TextProcessingContentComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
|
@ -40,6 +42,7 @@ final class TextProcessingContentComponent: Component {
|
|||
let mode: TextProcessingScreen.Mode
|
||||
let styles: [TelegramComposeAIMessageMode.Style]
|
||||
let inputText: TextWithEntities
|
||||
let initialEditState: TextProcessingScreen.EditState?
|
||||
let shouldDisplayStyleNotice: Bool
|
||||
let copyCurrentResult: (() -> Void)?
|
||||
let translateChat: ((String) -> Void)?
|
||||
|
|
@ -51,6 +54,7 @@ final class TextProcessingContentComponent: Component {
|
|||
mode: TextProcessingScreen.Mode,
|
||||
styles: [TelegramComposeAIMessageMode.Style],
|
||||
inputText: TextWithEntities,
|
||||
initialEditState: TextProcessingScreen.EditState?,
|
||||
shouldDisplayStyleNotice: Bool,
|
||||
copyCurrentResult: (() -> Void)?,
|
||||
translateChat: ((String) -> Void)?,
|
||||
|
|
@ -61,6 +65,7 @@ final class TextProcessingContentComponent: Component {
|
|||
self.context = context
|
||||
self.mode = mode
|
||||
self.inputText = inputText
|
||||
self.initialEditState = initialEditState
|
||||
self.shouldDisplayStyleNotice = shouldDisplayStyleNotice
|
||||
self.copyCurrentResult = copyCurrentResult
|
||||
self.translateChat = translateChat
|
||||
|
|
@ -94,7 +99,7 @@ final class TextProcessingContentComponent: Component {
|
|||
|
||||
private var currentContent: (mode: Mode, view: ComponentView<Empty>)?
|
||||
|
||||
private var currentMode: Mode = .translate
|
||||
private var currentMode: Mode = .stylize
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.currentContentBackground = UIImageView()
|
||||
|
|
@ -106,7 +111,6 @@ final class TextProcessingContentComponent: Component {
|
|||
self.addSubview(self.currentContentBackground)
|
||||
self.addSubview(self.currentContentContainer)
|
||||
|
||||
|
||||
self.translateState.resultUpdated = { [weak self] _ in
|
||||
self?.externalStatesUpdated()
|
||||
}
|
||||
|
|
@ -163,6 +167,30 @@ final class TextProcessingContentComponent: Component {
|
|||
component.externalState.nonPremiumFloodTriggered = true
|
||||
#endif*/
|
||||
}
|
||||
|
||||
private func saveState() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
if case let .edit(saveRestoreStateId, _, _, _) = component.mode, let saveRestoreStateId {
|
||||
let mappedMode: Int32
|
||||
switch self.currentMode {
|
||||
case .translate:
|
||||
mappedMode = 0
|
||||
case .stylize:
|
||||
mappedMode = 1
|
||||
case .fix:
|
||||
mappedMode = 2
|
||||
}
|
||||
|
||||
let state = TextProcessingScreen.EditState(
|
||||
selectedMode: mappedMode
|
||||
)
|
||||
let _ = component.context.engine.preferences.update(id: ApplicationSpecificPreferencesKeys.textProcessingEditingState(peerId: saveRestoreStateId), { _ in
|
||||
return EnginePreferencesEntry(state)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: TextProcessingContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
|
|
@ -202,6 +230,7 @@ final class TextProcessingContentComponent: Component {
|
|||
}
|
||||
if self.currentMode != .translate {
|
||||
self.currentMode = .translate
|
||||
self.saveState()
|
||||
self.externalStatesUpdated()
|
||||
}
|
||||
if !self.isUpdating {
|
||||
|
|
@ -223,6 +252,7 @@ final class TextProcessingContentComponent: Component {
|
|||
}
|
||||
if self.currentMode != .stylize {
|
||||
self.currentMode = .stylize
|
||||
self.saveState()
|
||||
let _ = ApplicationSpecificNotice.incrementAITextProcessingStyleSelection(accountManager: component.context.sharedContext.accountManager).startStandalone()
|
||||
self.externalStatesUpdated()
|
||||
}
|
||||
|
|
@ -245,6 +275,7 @@ final class TextProcessingContentComponent: Component {
|
|||
}
|
||||
if self.currentMode != .fix {
|
||||
self.currentMode = .fix
|
||||
self.saveState()
|
||||
self.externalStatesUpdated()
|
||||
}
|
||||
if !self.isUpdating {
|
||||
|
|
@ -498,6 +529,7 @@ private final class TextProcessingSheetComponent: Component {
|
|||
let ignoredTranslationLanguages: [String]
|
||||
let styles: [TelegramComposeAIMessageMode.Style]
|
||||
let inputText: TextWithEntities
|
||||
let initialEditState: TextProcessingScreen.EditState?
|
||||
let shouldDisplayStyleNotice: Bool
|
||||
let copyCurrentResult: ((TextWithEntities) -> Void)?
|
||||
let translateChat: ((String) -> Void)?
|
||||
|
|
@ -508,6 +540,7 @@ private final class TextProcessingSheetComponent: Component {
|
|||
ignoredTranslationLanguages: [String],
|
||||
styles: [TelegramComposeAIMessageMode.Style],
|
||||
inputText: TextWithEntities,
|
||||
initialEditState: TextProcessingScreen.EditState?,
|
||||
shouldDisplayStyleNotice: Bool,
|
||||
copyCurrentResult: ((TextWithEntities) -> Void)?,
|
||||
translateChat: ((String) -> Void)?
|
||||
|
|
@ -517,6 +550,7 @@ private final class TextProcessingSheetComponent: Component {
|
|||
self.ignoredTranslationLanguages = ignoredTranslationLanguages
|
||||
self.styles = styles
|
||||
self.inputText = inputText
|
||||
self.initialEditState = initialEditState
|
||||
self.shouldDisplayStyleNotice = shouldDisplayStyleNotice
|
||||
self.copyCurrentResult = copyCurrentResult
|
||||
self.translateChat = translateChat
|
||||
|
|
@ -561,6 +595,122 @@ private final class TextProcessingSheetComponent: Component {
|
|||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func displayLongPressSendMenu(sourceSendButton: UIView) {
|
||||
Task { @MainActor [weak self, weak sourceSendButton] in
|
||||
guard let self, let sourceSendButton, let component = self.component, case let .edit(_, _, _, sendContextActions) = component.mode, let sendContextActions else {
|
||||
return
|
||||
}
|
||||
guard let controller = self.environment?.controller() else {
|
||||
return
|
||||
}
|
||||
let peerId = sendContextActions.peerId
|
||||
let previousSupportedOrientations = controller.supportedOrientations
|
||||
|
||||
let availableMessageEffects = await (component.context.availableMessageEffects |> take(1)).get()
|
||||
let hasPremium = await (component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: component.context.account.peerId))
|
||||
|> map { peer -> Bool in
|
||||
guard case let .user(user) = peer else {
|
||||
return false
|
||||
}
|
||||
return user.isPremium
|
||||
}).get()
|
||||
|
||||
let peerStatus = await (component.context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Presence(id: peerId)
|
||||
)).get()
|
||||
guard let peer = await (component.context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
|
||||
)).get() else {
|
||||
return
|
||||
}
|
||||
|
||||
let initialData = await ChatSendMessageContextScreen.initialData(context: component.context, currentMessageEffectId: nil).get()
|
||||
|
||||
var sendWhenOnlineAvailable = false
|
||||
if let peerStatus, case let .present(until) = peerStatus.status {
|
||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
if currentTime > until {
|
||||
sendWhenOnlineAvailable = true
|
||||
}
|
||||
}
|
||||
if peerId.namespace == Namespaces.Peer.CloudUser && peerId.id._internalGetInt64Value() == 777000 {
|
||||
sendWhenOnlineAvailable = false
|
||||
}
|
||||
|
||||
let messageActionsController = makeChatSendMessageActionSheetController(
|
||||
initialData: initialData,
|
||||
context: component.context,
|
||||
updatedPresentationData: nil,
|
||||
peerId: peerId,
|
||||
params: .sendMessage(SendMessageActionSheetControllerParams.SendMessage(
|
||||
isScheduledMessages: false,
|
||||
mediaPreview: nil,
|
||||
mediaCaptionIsAbove: nil,
|
||||
messageEffect: (nil, { [weak self] updatedEffect in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let _ = self
|
||||
let _ = updatedEffect
|
||||
}),
|
||||
attachment: false,
|
||||
canSendWhenOnline: sendWhenOnlineAvailable,
|
||||
forwardMessageIds: [],
|
||||
canMakePaidContent: false,
|
||||
currentPrice: nil,
|
||||
hasTimers: false,
|
||||
sendPaidMessageStars: nil,
|
||||
isMonoforum: peer._asPeer().isMonoForum
|
||||
)),
|
||||
hasEntityKeyboard: false,
|
||||
gesture: nil,
|
||||
sourceSendButton: sourceSendButton,
|
||||
textInputView: UITextView(),
|
||||
emojiViewProvider: nil,
|
||||
completion: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.environment?.controller()?.supportedOrientations = previousSupportedOrientations
|
||||
},
|
||||
sendMessage: { [weak self] mode, parameters in
|
||||
guard let self, let result = self.contentExternalState.result else {
|
||||
return
|
||||
}
|
||||
sendContextActions.send(result, mode, parameters)
|
||||
let controller = self.environment?.controller
|
||||
self.animateOut.invoke(Action { _ in
|
||||
if let controller = controller?() {
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
})
|
||||
},
|
||||
schedule: { [weak self] params in
|
||||
guard let self, let result = self.contentExternalState.result else {
|
||||
return
|
||||
}
|
||||
sendContextActions.schedule(result, params)
|
||||
let controller = self.environment?.controller
|
||||
self.animateOut.invoke(Action { _ in
|
||||
if let controller = controller?() {
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
})
|
||||
}, editPrice: { _ in
|
||||
}, openPremiumPaywall: { [weak self] c in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.environment?.controller()?.push(c)
|
||||
},
|
||||
reactionItems: nil,
|
||||
availableMessageEffects: availableMessageEffects,
|
||||
isPremium: hasPremium
|
||||
)
|
||||
controller.present(messageActionsController, in: .window(.root))
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: TextProcessingSheetComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
|
|
@ -587,6 +737,7 @@ private final class TextProcessingSheetComponent: Component {
|
|||
|
||||
let performMainAction: () -> Void
|
||||
var performSendAction: ((TextWithEntities) -> Void)?
|
||||
var hasLongPressActions = false
|
||||
let isMainActionEnabled: Bool
|
||||
//TODO:localize (for AI: don't remove this line)
|
||||
let actionButtonTitle: String
|
||||
|
|
@ -614,10 +765,11 @@ private final class TextProcessingSheetComponent: Component {
|
|||
}
|
||||
} else {
|
||||
switch component.mode {
|
||||
case let .edit(completion, send):
|
||||
case let .edit(_, completion, send, sendContextActions):
|
||||
actionButtonTitle = "Apply"
|
||||
performSendAction = send
|
||||
isMainActionEnabled = !self.contentExternalState.isProcessing
|
||||
hasLongPressActions = sendContextActions != nil
|
||||
performMainAction = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
|
|
@ -663,6 +815,7 @@ private final class TextProcessingSheetComponent: Component {
|
|||
mode: component.mode,
|
||||
styles: component.styles,
|
||||
inputText: component.inputText,
|
||||
initialEditState: component.initialEditState,
|
||||
shouldDisplayStyleNotice: component.shouldDisplayStyleNotice,
|
||||
copyCurrentResult: component.copyCurrentResult != nil ? {
|
||||
copyCurrentResultImpl()
|
||||
|
|
@ -737,7 +890,13 @@ private final class TextProcessingSheetComponent: Component {
|
|||
dismiss(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
longPressSendAction: (performSendAction != nil && hasLongPressActions) ? { [weak self] sourceView in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.displayLongPressSendMenu(sourceSendButton: sourceView)
|
||||
} : nil
|
||||
)
|
||||
),
|
||||
backgroundColor: .color(theme.list.blocksBackgroundColor),
|
||||
|
|
@ -890,11 +1049,31 @@ private final class TextProcessingSheetComponent: Component {
|
|||
}
|
||||
|
||||
public class TextProcessingScreen: ViewControllerComponentContainer {
|
||||
public final class SendContextActions {
|
||||
public let peerId: EnginePeer.Id
|
||||
public let send: (TextWithEntities, ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void
|
||||
public let schedule: (TextWithEntities, ChatSendMessageActionSheetController.SendParameters?) -> Void
|
||||
|
||||
public init(peerId: EnginePeer.Id, send: @escaping (TextWithEntities, ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void, schedule: @escaping (TextWithEntities, ChatSendMessageActionSheetController.SendParameters?) -> Void) {
|
||||
self.peerId = peerId
|
||||
self.send = send
|
||||
self.schedule = schedule
|
||||
}
|
||||
}
|
||||
|
||||
public enum Mode {
|
||||
case edit(completion: (TextWithEntities) -> Void, send: ((TextWithEntities) -> Void)?)
|
||||
case edit(saveRestoreStateId: EnginePeer.Id?, completion: (TextWithEntities) -> Void, send: ((TextWithEntities) -> Void)?, sendContextActions: SendContextActions?)
|
||||
case translate(fromLanguage: String?)
|
||||
}
|
||||
|
||||
struct EditState: Codable {
|
||||
var selectedMode: Int32
|
||||
|
||||
init(selectedMode: Int32) {
|
||||
self.selectedMode = selectedMode
|
||||
}
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
|
||||
public init(
|
||||
|
|
@ -908,8 +1087,14 @@ public class TextProcessingScreen: ViewControllerComponentContainer {
|
|||
self.context = context
|
||||
|
||||
let styles = await context.engine.messages.composeAIMessageStyles().get()
|
||||
|
||||
let shouldDisplayStyleNotice = await ApplicationSpecificNotice.getAITextProcessingStyleSelection(accountManager: context.sharedContext.accountManager).get() < 3
|
||||
|
||||
var initialEditState: EditState?
|
||||
if case let .edit(saveRestoreStateId, _, _, _) = mode, let saveRestoreStateId {
|
||||
initialEditState = await context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: ApplicationSpecificPreferencesKeys.textProcessingEditingState(peerId: saveRestoreStateId))
|
||||
).get()?.get(EditState.self)
|
||||
}
|
||||
|
||||
super.init(
|
||||
context: context,
|
||||
|
|
@ -919,6 +1104,7 @@ public class TextProcessingScreen: ViewControllerComponentContainer {
|
|||
ignoredTranslationLanguages: ignoredTranslationLanguages,
|
||||
styles: styles,
|
||||
inputText: inputText,
|
||||
initialEditState: initialEditState,
|
||||
shouldDisplayStyleNotice: shouldDisplayStyleNotice,
|
||||
copyCurrentResult: copyResult,
|
||||
translateChat: translateChat
|
||||
|
|
@ -1075,19 +1261,22 @@ private final class ActionButtonsComponent: Component {
|
|||
let actionButtonShowsIncreaseLimit: Bool
|
||||
let action: (() -> Void)?
|
||||
let sendAction: (() -> Void)?
|
||||
let longPressSendAction: ((UIView) -> Void)?
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
actionTitle: String,
|
||||
actionButtonShowsIncreaseLimit: Bool,
|
||||
action: (() -> Void)?,
|
||||
sendAction: (() -> Void)?
|
||||
sendAction: (() -> Void)?,
|
||||
longPressSendAction: ((UIView) -> Void)?
|
||||
) {
|
||||
self.theme = theme
|
||||
self.actionTitle = actionTitle
|
||||
self.actionButtonShowsIncreaseLimit = actionButtonShowsIncreaseLimit
|
||||
self.action = action
|
||||
self.sendAction = sendAction
|
||||
self.longPressSendAction = longPressSendAction
|
||||
}
|
||||
|
||||
static func ==(lhs: ActionButtonsComponent, rhs: ActionButtonsComponent) -> Bool {
|
||||
|
|
@ -1106,18 +1295,24 @@ private final class ActionButtonsComponent: Component {
|
|||
if (lhs.sendAction == nil) != (rhs.sendAction == nil) {
|
||||
return false
|
||||
}
|
||||
if (lhs.longPressSendAction == nil) != (rhs.longPressSendAction == nil) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let actionButton = ComponentView<Empty>()
|
||||
private let sendButton = ComponentView<Empty>()
|
||||
|
||||
private let extractedContainerView = ContextExtractedContentContainingView()
|
||||
|
||||
private var component: ActionButtonsComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.extractedContainerView)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
|
|
@ -1205,6 +1400,12 @@ private final class ActionButtonsComponent: Component {
|
|||
return
|
||||
}
|
||||
component.sendAction?()
|
||||
},
|
||||
longPressAction: component.longPressSendAction == nil ? nil : { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
component.longPressSendAction?(self.extractedContainerView)
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
|
|
@ -1213,13 +1414,16 @@ private final class ActionButtonsComponent: Component {
|
|||
let sendButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - sendButtonSize.width, y: 0.0), size: sendButtonSize)
|
||||
if let sendButtonView = self.sendButton.view {
|
||||
if sendButtonView.superview == nil {
|
||||
self.addSubview(sendButtonView)
|
||||
self.extractedContainerView.contentView.addSubview(sendButtonView)
|
||||
}
|
||||
transition.setPosition(view: sendButtonView, position: sendButtonFrame.center)
|
||||
transition.setBounds(view: sendButtonView, bounds: CGRect(origin: CGPoint(), size: sendButtonFrame.size))
|
||||
transition.setAlpha(view: sendButtonView, alpha: component.sendAction != nil ? 1.0 : 0.0)
|
||||
transition.setScale(view: sendButtonView, scale: component.sendAction != nil ? 1.0 : 0.001)
|
||||
sendButtonView.frame = CGRect(origin: CGPoint(), size: sendButtonFrame.size)
|
||||
}
|
||||
transition.setPosition(view: self.extractedContainerView, position: sendButtonFrame.center)
|
||||
transition.setBounds(view: self.extractedContainerView, bounds: CGRect(origin: CGPoint(), size: sendButtonFrame.size))
|
||||
transition.setPosition(view: self.extractedContainerView.contentView, position: CGPoint(x: sendButtonFrame.width * 0.5, y: sendButtonFrame.height * 0.5))
|
||||
transition.setBounds(view: self.extractedContainerView.contentView, bounds: CGRect(origin: CGPoint(), size: sendButtonFrame.size))
|
||||
transition.setAlpha(view: self.extractedContainerView, alpha: component.sendAction != nil ? 1.0 : 0.0)
|
||||
transition.setScale(view: self.extractedContainerView, scale: component.sendAction != nil ? 1.0 : 0.001)
|
||||
|
||||
return CGSize(width: availableSize.width, height: actionButtonSize.height)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, no
|
|||
)),
|
||||
hasEntityKeyboard: hasEntityKeyboard,
|
||||
gesture: gesture,
|
||||
sourceSendButton: node,
|
||||
sourceSendButton: node.view,
|
||||
textInputView: textInputView,
|
||||
emojiViewProvider: selfController.chatDisplayNode.textInputPanelNode?.emojiViewProvider,
|
||||
wallpaperBackgroundNode: selfController.chatDisplayNode.backgroundNode,
|
||||
|
|
@ -242,7 +242,7 @@ func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, no
|
|||
)),
|
||||
hasEntityKeyboard: hasEntityKeyboard,
|
||||
gesture: gesture,
|
||||
sourceSendButton: node,
|
||||
sourceSendButton: node.view,
|
||||
textInputView: textInputView,
|
||||
emojiViewProvider: selfController.chatDisplayNode.textInputPanelNode?.emojiViewProvider,
|
||||
wallpaperBackgroundNode: selfController.chatDisplayNode.backgroundNode,
|
||||
|
|
|
|||
|
|
@ -4077,8 +4077,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
mode: .translate(fromLanguage: language),
|
||||
ignoredTranslationLanguages: translationSettings.ignoredLanguages ?? [],
|
||||
inputText: TextWithEntities(text: text.string, entities: entities ?? []),
|
||||
copyResult: canCopy ? { text in
|
||||
copyResult: canCopy ? { [weak self] text in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
storeMessageTextInPasteboard(text.text, entities: text.entities)
|
||||
|
||||
let infoText = self.presentationData.strings.Conversation_TextCopied
|
||||
self.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: infoText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in
|
||||
return true
|
||||
}), in: .current)
|
||||
} : nil,
|
||||
translateChat: { [weak self] toLang in
|
||||
guard let self else {
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ import GroupCallHeaderPanelComponent
|
|||
import PresentationDataUtils
|
||||
import TextProcessingScreen
|
||||
import Pasteboard
|
||||
import UndoUI
|
||||
|
||||
final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem {
|
||||
let itemNode: OverlayMediaItemNode
|
||||
|
|
@ -4462,7 +4463,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
}
|
||||
|
||||
let effectiveInputText: NSAttributedString
|
||||
if effectivePresentationInterfaceState.interfaceState.editMessage != nil && effectivePresentationInterfaceState.interfaceState.postSuggestionState != nil {
|
||||
if effectivePresentationInterfaceState.interfaceState.editMessage != nil {
|
||||
effectiveInputText = expandedInputStateAttributedString(effectivePresentationInterfaceState.interfaceState.effectiveInputState.inputText)
|
||||
} else {
|
||||
effectiveInputText = expandedInputStateAttributedString(effectivePresentationInterfaceState.interfaceState.composeInputState.inputText)
|
||||
|
|
@ -4493,6 +4494,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
self.controller?.push(await TextProcessingScreen(
|
||||
context: self.context,
|
||||
mode: .edit(
|
||||
saveRestoreStateId: self.chatLocation.peerId,
|
||||
completion: { [weak self] text in
|
||||
guard let self, let controller = self.controller else {
|
||||
return
|
||||
|
|
@ -4500,7 +4502,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
controller.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
|
||||
return state.updatedInterfaceState { interfaceState in
|
||||
return interfaceState.withUpdatedEffectiveInputState(ChatTextInputState(inputText: chatInputStateStringWithAppliedEntities(text.text, entities: text.entities)))
|
||||
}
|
||||
}.updatedInputMode({ _ in return .text })
|
||||
})
|
||||
},
|
||||
send: { [weak self] text in
|
||||
|
|
@ -4513,16 +4515,61 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
}
|
||||
})
|
||||
self.sendCurrentMessage()
|
||||
},
|
||||
sendContextActions: self.chatLocation.peerId.flatMap { peerId in return TextProcessingScreen.SendContextActions(
|
||||
peerId: peerId,
|
||||
send: { [weak self] text, mode, parameters in
|
||||
guard let self, let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
controller.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
|
||||
return state.updatedInterfaceState { interfaceState in
|
||||
return interfaceState.withUpdatedEffectiveInputState(ChatTextInputState(inputText: chatInputStateStringWithAppliedEntities(text.text, entities: text.entities)))
|
||||
}
|
||||
})
|
||||
switch mode {
|
||||
case .generic:
|
||||
controller.controllerInteraction?.sendCurrentMessage(false, parameters?.effect.flatMap(ChatSendMessageEffect.init))
|
||||
case .silently:
|
||||
controller.controllerInteraction?.sendCurrentMessage(true, parameters?.effect.flatMap(ChatSendMessageEffect.init))
|
||||
case .whenOnline:
|
||||
controller.chatDisplayNode.sendCurrentMessage(scheduleTime: scheduleWhenOnlineTimestamp, messageEffect: parameters?.effect.flatMap(ChatSendMessageEffect.init)) { [weak self] in
|
||||
guard let self, let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
controller.updateChatPresentationInterfaceState(animated: true, interactive: false, saveInterfaceState: controller.presentationInterfaceState.subject != .scheduledMessages, {
|
||||
$0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil).withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))) }
|
||||
})
|
||||
controller.openScheduledMessages()
|
||||
}
|
||||
}
|
||||
},
|
||||
schedule: { [weak self] text, params in
|
||||
guard let self, let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
controller.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
|
||||
return state.updatedInterfaceState { interfaceState in
|
||||
return interfaceState.withUpdatedEffectiveInputState(ChatTextInputState(inputText: chatInputStateStringWithAppliedEntities(text.text, entities: text.entities)))
|
||||
}
|
||||
})
|
||||
controller.controllerInteraction?.scheduleCurrentMessage(params)
|
||||
}
|
||||
)
|
||||
}
|
||||
),
|
||||
ignoredTranslationLanguages: translationSettings.ignoredLanguages ?? [],
|
||||
inputText: TextWithEntities(text: inputText.string, entities: entities),
|
||||
copyResult: { [weak self] text in
|
||||
guard let self else {
|
||||
guard let self, let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
let _ = self
|
||||
storeMessageTextInPasteboard(text.text, entities: text.entities)
|
||||
|
||||
let infoText = controller.presentationData.strings.Conversation_TextCopied
|
||||
controller.present(UndoOverlayController(presentationData: controller.presentationData, content: .copy(text: infoText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in
|
||||
return true
|
||||
}), in: .current)
|
||||
},
|
||||
translateChat: nil
|
||||
))
|
||||
|
|
|
|||
|
|
@ -1944,7 +1944,10 @@ func openResolvedUrlImpl(
|
|||
context: context,
|
||||
parentBot: parentBot,
|
||||
initialUsername: username,
|
||||
initialTitle: title
|
||||
initialTitle: title,
|
||||
openAutomatically: true,
|
||||
completion: { _ in
|
||||
}
|
||||
) else {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ import ChatParticipantRightsScreen
|
|||
import PeerCopyProtectionInfoScreen
|
||||
import ChatRankInfoScreen
|
||||
import RankChatPreviewItem
|
||||
import CreateBotScreen
|
||||
|
||||
private final class AccountUserInterfaceInUseContext {
|
||||
let subscribers = Bag<(Bool) -> Void>()
|
||||
|
|
@ -4363,6 +4364,24 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
)
|
||||
return RankChatPreviewItem(context: context, systemStyle: .glass, theme: theme, componentTheme: theme, strings: strings, sectionId: sectionId, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameOrder, messageItems: [messageItem])
|
||||
}
|
||||
|
||||
public func makeCreateBotScreen(
|
||||
context: AccountContext,
|
||||
parentBot: EnginePeer.Id,
|
||||
initialUsername: String?,
|
||||
initialTitle: String?,
|
||||
openAutomatically: Bool,
|
||||
completion: @escaping (EnginePeer.Id?) -> Void
|
||||
) async -> ViewController? {
|
||||
return await CreateBotScreen(
|
||||
context: context,
|
||||
parentBot: parentBot,
|
||||
initialUsername: initialUsername,
|
||||
initialTitle: initialTitle,
|
||||
openAutomatically: openAutomatically,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ private enum ApplicationSpecificPreferencesKeyValues: Int32 {
|
|||
case widgetSettings = 19
|
||||
case mediaAutoSaveSettings = 20
|
||||
case ageVerificationState = 21
|
||||
case textProcessingEditingState = 22
|
||||
}
|
||||
|
||||
public struct ApplicationSpecificPreferencesKeys {
|
||||
|
|
@ -18,6 +19,13 @@ public struct ApplicationSpecificPreferencesKeys {
|
|||
public static let widgetSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.widgetSettings.rawValue)
|
||||
public static let mediaAutoSaveSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.mediaAutoSaveSettings.rawValue)
|
||||
public static let ageVerificationState = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.ageVerificationState.rawValue)
|
||||
|
||||
public static func textProcessingEditingState(peerId: PeerId) -> ValueBoxKey {
|
||||
let key = ValueBoxKey(length: 4 + 8)
|
||||
key.setInt32(0, value: ApplicationSpecificPreferencesKeyValues.textProcessingEditingState.rawValue)
|
||||
key.setInt64(4, value: peerId.toInt64())
|
||||
return key
|
||||
}
|
||||
}
|
||||
|
||||
private enum ApplicationSpecificSharedDataKeyValues: Int32 {
|
||||
|
|
|
|||
|
|
@ -258,7 +258,7 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, context: Accou
|
|||
if queryItem.name == "url" {
|
||||
url = value
|
||||
} else if queryItem.name == "text" {
|
||||
text = value
|
||||
text = value.replacingOccurrences(of: "+", with: " ")
|
||||
} else if queryItem.name == "to" && peerName != "share" {
|
||||
to = value
|
||||
}
|
||||
|
|
@ -601,7 +601,7 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, context: Accou
|
|||
for queryItem in components.queryItems ?? [] {
|
||||
if let value = queryItem.value {
|
||||
if queryItem.name == "name" {
|
||||
title = value
|
||||
title = value.replacingOccurrences(of: "+", with: " ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1930,6 +1930,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
self.controller?.verifyAgeCompletion?(Int(ageValue))
|
||||
}
|
||||
}
|
||||
case "web_app_request_chat":
|
||||
if let json, let requestId = json["req_id"] as? String {
|
||||
self.requestChat(requestId: requestId)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
@ -2299,6 +2303,66 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
})
|
||||
}
|
||||
|
||||
fileprivate func requestChat(requestId: String) {
|
||||
guard let controller = self.controller, !self.dismissed else {
|
||||
return
|
||||
}
|
||||
let _ = (self.context.engine.messages.requestMiniAppButton(peerId: controller.botId, requestId: requestId)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] button in
|
||||
guard let self, let button else {
|
||||
return
|
||||
}
|
||||
switch button.action {
|
||||
case let .requestPeer(peerType, buttonId, maxQuantity):
|
||||
let _ = maxQuantity
|
||||
|
||||
switch peerType {
|
||||
case let .createBot(createBot):
|
||||
Task { @MainActor [weak self] in
|
||||
guard let self, let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
let createBotScreen = await self.context.sharedContext.makeCreateBotScreen(
|
||||
context: self.context,
|
||||
parentBot: controller.botId,
|
||||
initialUsername: createBot.suggestedUsername,
|
||||
initialTitle: createBot.suggestedName,
|
||||
openAutomatically: false,
|
||||
completion: { [weak self] resultId in
|
||||
guard let self, let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
if let resultId {
|
||||
let _ = self.context.engine.peers.sendBotRequestedPeer(peerId: controller.botId, requestId: requestId, buttonId: buttonId, requestedPeerIds: [resultId]
|
||||
).startStandalone(error: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.webView?.sendEvent(name: "requested_chat_failed", data: nil)
|
||||
}, completed: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.webView?.sendEvent(name: "requested_chat_sent", data: nil)
|
||||
})
|
||||
} else {
|
||||
self.webView?.sendEvent(name: "requested_chat_failed", data: nil)
|
||||
}
|
||||
}
|
||||
)
|
||||
if let createBotScreen {
|
||||
controller.push(createBotScreen)
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fileprivate func invokeCustomMethod(requestId: String, method: String, params: String) {
|
||||
guard let controller = self.controller, !self.dismissed else {
|
||||
return
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue