mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
Input state updates
This commit is contained in:
parent
53fe1718e9
commit
dcfc4d9364
@ -478,7 +478,7 @@ public final class ChatInterfaceState: Codable, Equatable {
|
|||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
let sourceText = expandedInputStateAttributedString(self.composeInputState.inputText)
|
let sourceText = expandedInputStateAttributedString(self.composeInputState.inputText)
|
||||||
return SynchronizeableChatInputState(replySubject: self.replyMessageSubject?.subjectModel, text: sourceText.string, entities: generateChatInputTextEntities(sourceText), timestamp: self.timestamp, textSelection: self.composeInputState.selectionRange)
|
return SynchronizeableChatInputState(replySubject: self.replyMessageSubject?.subjectModel, text: sourceText.string, entities: generateChatInputTextEntities(sourceText), timestamp: self.timestamp, textSelection: self.composeInputState.selectionRange, messageEffectId: self.sendMessageEffect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,6 +145,30 @@ public func chatTextInputAddLinkAttribute(_ state: ChatTextInputState, selection
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func chatTextInputRemoveLinkAttribute(_ state: ChatTextInputState, selectionRange: Range<Int>) -> ChatTextInputState {
|
||||||
|
if !selectionRange.isEmpty {
|
||||||
|
let nsRange = NSRange(location: selectionRange.lowerBound, length: selectionRange.count)
|
||||||
|
var attributesToRemove: [(NSAttributedString.Key, NSRange)] = []
|
||||||
|
state.inputText.enumerateAttributes(in: nsRange, options: .longestEffectiveRangeNotRequired) { attributes, range, stop in
|
||||||
|
for (key, _) in attributes {
|
||||||
|
if key == ChatTextInputAttributes.textUrl {
|
||||||
|
attributesToRemove.append((key, range))
|
||||||
|
} else {
|
||||||
|
attributesToRemove.append((key, nsRange))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = NSMutableAttributedString(attributedString: state.inputText)
|
||||||
|
for (attribute, range) in attributesToRemove {
|
||||||
|
result.removeAttribute(attribute, range: range)
|
||||||
|
}
|
||||||
|
return ChatTextInputState(inputText: result, selectionRange: selectionRange)
|
||||||
|
} else {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func chatTextInputAddMentionAttribute(_ state: ChatTextInputState, peer: EnginePeer) -> ChatTextInputState {
|
public func chatTextInputAddMentionAttribute(_ state: ChatTextInputState, peer: EnginePeer) -> ChatTextInputState {
|
||||||
let inputText = NSMutableAttributedString(attributedString: state.inputText)
|
let inputText = NSMutableAttributedString(attributedString: state.inputText)
|
||||||
|
|
||||||
|
@ -176,11 +176,13 @@ private final class ChatTextLinkEditAlertContentNode: AlertContentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var isEditing = false
|
private var isEditing = false
|
||||||
|
private let allowEmpty: Bool
|
||||||
|
|
||||||
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], text: String, link: String?) {
|
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], text: String, link: String?, allowEmpty: Bool) {
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.text = text
|
self.text = text
|
||||||
self.isEditing = link != nil
|
self.isEditing = link != nil
|
||||||
|
self.allowEmpty = allowEmpty
|
||||||
|
|
||||||
self.titleNode = ASTextNode()
|
self.titleNode = ASTextNode()
|
||||||
self.titleNode.maximumNumberOfLines = 2
|
self.titleNode.maximumNumberOfLines = 2
|
||||||
@ -220,6 +222,9 @@ private final class ChatTextLinkEditAlertContentNode: AlertContentNode {
|
|||||||
self.addSubnode(actionNode)
|
self.addSubnode(actionNode)
|
||||||
}
|
}
|
||||||
self.actionNodes.last?.actionEnabled = !(link ?? "").isEmpty
|
self.actionNodes.last?.actionEnabled = !(link ?? "").isEmpty
|
||||||
|
if allowEmpty {
|
||||||
|
self.actionNodes.last?.actionEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
for separatorNode in self.actionVerticalSeparators {
|
for separatorNode in self.actionVerticalSeparators {
|
||||||
self.addSubnode(separatorNode)
|
self.addSubnode(separatorNode)
|
||||||
@ -235,7 +240,11 @@ private final class ChatTextLinkEditAlertContentNode: AlertContentNode {
|
|||||||
|
|
||||||
self.inputFieldNode.textChanged = { [weak self] text in
|
self.inputFieldNode.textChanged = { [weak self] text in
|
||||||
if let strongSelf = self, let lastNode = strongSelf.actionNodes.last {
|
if let strongSelf = self, let lastNode = strongSelf.actionNodes.last {
|
||||||
lastNode.actionEnabled = !text.isEmpty
|
if strongSelf.allowEmpty {
|
||||||
|
lastNode.actionEnabled = true
|
||||||
|
} else {
|
||||||
|
lastNode.actionEnabled = !text.isEmpty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -402,7 +411,7 @@ private final class ChatTextLinkEditAlertContentNode: AlertContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func chatTextLinkEditController(sharedContext: SharedAccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, account: Account, text: String, link: String?, apply: @escaping (String?) -> Void) -> AlertController {
|
public func chatTextLinkEditController(sharedContext: SharedAccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, account: Account, text: String, link: String?, allowEmpty: Bool = false, apply: @escaping (String?) -> Void) -> AlertController {
|
||||||
let presentationData = updatedPresentationData?.initial ?? sharedContext.currentPresentationData.with { $0 }
|
let presentationData = updatedPresentationData?.initial ?? sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
var dismissImpl: ((Bool) -> Void)?
|
var dismissImpl: ((Bool) -> Void)?
|
||||||
@ -415,7 +424,7 @@ public func chatTextLinkEditController(sharedContext: SharedAccountContext, upda
|
|||||||
applyImpl?()
|
applyImpl?()
|
||||||
})]
|
})]
|
||||||
|
|
||||||
let contentNode = ChatTextLinkEditAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, text: text, link: link)
|
let contentNode = ChatTextLinkEditAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, text: text, link: link, allowEmpty: allowEmpty)
|
||||||
contentNode.complete = {
|
contentNode.complete = {
|
||||||
applyImpl?()
|
applyImpl?()
|
||||||
}
|
}
|
||||||
@ -427,6 +436,9 @@ public func chatTextLinkEditController(sharedContext: SharedAccountContext, upda
|
|||||||
if !updatedLink.isEmpty && isValidUrl(updatedLink, validSchemes: ["http": true, "https": true, "tg": false, "ton": false]) {
|
if !updatedLink.isEmpty && isValidUrl(updatedLink, validSchemes: ["http": true, "https": true, "tg": false, "ton": false]) {
|
||||||
dismissImpl?(true)
|
dismissImpl?(true)
|
||||||
apply(updatedLink)
|
apply(updatedLink)
|
||||||
|
} else if allowEmpty && contentNode.link.isEmpty {
|
||||||
|
dismissImpl?(true)
|
||||||
|
apply("")
|
||||||
} else {
|
} else {
|
||||||
contentNode.animateError()
|
contentNode.animateError()
|
||||||
}
|
}
|
||||||
|
@ -1554,7 +1554,7 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
|
|||||||
switch draft {
|
switch draft {
|
||||||
case .draftMessageEmpty:
|
case .draftMessageEmpty:
|
||||||
inputState = nil
|
inputState = nil
|
||||||
case let .draftMessage(_, replyToMsgHeader, message, entities, media, date, _):
|
case let .draftMessage(_, replyToMsgHeader, message, entities, media, date, messageEffectId):
|
||||||
let _ = media
|
let _ = media
|
||||||
var replySubject: EngineMessageReplySubject?
|
var replySubject: EngineMessageReplySubject?
|
||||||
if let replyToMsgHeader = replyToMsgHeader {
|
if let replyToMsgHeader = replyToMsgHeader {
|
||||||
@ -1600,7 +1600,7 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inputState = SynchronizeableChatInputState(replySubject: replySubject, text: message, entities: messageTextEntitiesFromApiEntities(entities ?? []), timestamp: date, textSelection: nil)
|
inputState = SynchronizeableChatInputState(replySubject: replySubject, text: message, entities: messageTextEntitiesFromApiEntities(entities ?? []), timestamp: date, textSelection: nil, messageEffectId: messageEffectId)
|
||||||
}
|
}
|
||||||
var threadId: Int64?
|
var threadId: Int64?
|
||||||
if let topMsgId = topMsgId {
|
if let topMsgId = topMsgId {
|
||||||
|
@ -7,13 +7,15 @@ public struct SynchronizeableChatInputState: Codable, Equatable {
|
|||||||
public let entities: [MessageTextEntity]
|
public let entities: [MessageTextEntity]
|
||||||
public let timestamp: Int32
|
public let timestamp: Int32
|
||||||
public let textSelection: Range<Int>?
|
public let textSelection: Range<Int>?
|
||||||
|
public let messageEffectId: Int64?
|
||||||
|
|
||||||
public init(replySubject: EngineMessageReplySubject?, text: String, entities: [MessageTextEntity], timestamp: Int32, textSelection: Range<Int>?) {
|
public init(replySubject: EngineMessageReplySubject?, text: String, entities: [MessageTextEntity], timestamp: Int32, textSelection: Range<Int>?, messageEffectId: Int64?) {
|
||||||
self.replySubject = replySubject
|
self.replySubject = replySubject
|
||||||
self.text = text
|
self.text = text
|
||||||
self.entities = entities
|
self.entities = entities
|
||||||
self.timestamp = timestamp
|
self.timestamp = timestamp
|
||||||
self.textSelection = textSelection
|
self.textSelection = textSelection
|
||||||
|
self.messageEffectId = messageEffectId
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
@ -32,6 +34,7 @@ public struct SynchronizeableChatInputState: Codable, Equatable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.textSelection = nil
|
self.textSelection = nil
|
||||||
|
self.messageEffectId = try container.decodeIfPresent(Int64.self, forKey: "messageEffectId")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(to encoder: Encoder) throws {
|
public func encode(to encoder: Encoder) throws {
|
||||||
@ -41,6 +44,7 @@ public struct SynchronizeableChatInputState: Codable, Equatable {
|
|||||||
try container.encode(self.entities, forKey: "e")
|
try container.encode(self.entities, forKey: "e")
|
||||||
try container.encode(self.timestamp, forKey: "s")
|
try container.encode(self.timestamp, forKey: "s")
|
||||||
try container.encodeIfPresent(self.replySubject, forKey: "rep")
|
try container.encodeIfPresent(self.replySubject, forKey: "rep")
|
||||||
|
try container.encodeIfPresent(self.messageEffectId, forKey: "messageEffectId")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: SynchronizeableChatInputState, rhs: SynchronizeableChatInputState) -> Bool {
|
public static func ==(lhs: SynchronizeableChatInputState, rhs: SynchronizeableChatInputState) -> Bool {
|
||||||
@ -59,6 +63,9 @@ public struct SynchronizeableChatInputState: Codable, Equatable {
|
|||||||
if lhs.textSelection != rhs.textSelection {
|
if lhs.textSelection != rhs.textSelection {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.messageEffectId != rhs.messageEffectId {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -930,11 +930,17 @@ public final class TextFieldComponent: Component {
|
|||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: component.theme)
|
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: component.theme)
|
||||||
let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>) = (presentationData, .single(presentationData))
|
let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>) = (presentationData, .single(presentationData))
|
||||||
let controller = chatTextLinkEditController(sharedContext: component.context.sharedContext, updatedPresentationData: updatedPresentationData, account: component.context.account, text: text.string, link: link, apply: { [weak self] link in
|
let controller = chatTextLinkEditController(sharedContext: component.context.sharedContext, updatedPresentationData: updatedPresentationData, account: component.context.account, text: text.string, link: link, allowEmpty: true, apply: { [weak self] link in
|
||||||
if let self {
|
if let self {
|
||||||
if let link = link {
|
if let link {
|
||||||
self.updateInputState { state in
|
if !link.isEmpty {
|
||||||
return state.addLinkAttribute(selectionRange: selectionRange, url: link)
|
self.updateInputState { state in
|
||||||
|
return state.addLinkAttribute(selectionRange: selectionRange, url: link)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.updateInputState { state in
|
||||||
|
return state.removeLinkAttribute(selectionRange: selectionRange)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.textView.becomeFirstResponder()
|
self.textView.becomeFirstResponder()
|
||||||
}
|
}
|
||||||
@ -1585,4 +1591,28 @@ extension TextFieldComponent.InputState {
|
|||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func removeLinkAttribute(selectionRange: Range<Int>) -> TextFieldComponent.InputState {
|
||||||
|
if !selectionRange.isEmpty {
|
||||||
|
let nsRange = NSRange(location: selectionRange.lowerBound, length: selectionRange.count)
|
||||||
|
var attributesToRemove: [(NSAttributedString.Key, NSRange)] = []
|
||||||
|
self.inputText.enumerateAttributes(in: nsRange, options: .longestEffectiveRangeNotRequired) { attributes, range, stop in
|
||||||
|
for (key, _) in attributes {
|
||||||
|
if key == ChatTextInputAttributes.textUrl {
|
||||||
|
attributesToRemove.append((key, range))
|
||||||
|
} else {
|
||||||
|
attributesToRemove.append((key, nsRange))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = NSMutableAttributedString(attributedString: self.inputText)
|
||||||
|
for (attribute, range) in attributesToRemove {
|
||||||
|
result.removeAttribute(attribute, range: range)
|
||||||
|
}
|
||||||
|
return TextFieldComponent.InputState(inputText: result, selectionRange: selectionRange)
|
||||||
|
} else {
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3706,14 +3706,18 @@ extension ChatControllerImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let controller = chatTextLinkEditController(sharedContext: strongSelf.context.sharedContext, updatedPresentationData: strongSelf.updatedPresentationData, account: strongSelf.context.account, text: text?.string ?? "", link: link, apply: { [weak self] link in
|
let controller = chatTextLinkEditController(sharedContext: strongSelf.context.sharedContext, updatedPresentationData: strongSelf.updatedPresentationData, account: strongSelf.context.account, text: text?.string ?? "", link: link, allowEmpty: true, apply: { [weak self] link in
|
||||||
if let strongSelf = self, let inputMode = inputMode, let selectionRange = selectionRange {
|
if let strongSelf = self, let inputMode = inputMode, let selectionRange = selectionRange {
|
||||||
if let link = link {
|
if let link {
|
||||||
strongSelf.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in
|
if !link.isEmpty {
|
||||||
return (chatTextInputAddLinkAttribute(current, selectionRange: selectionRange, url: link), inputMode)
|
strongSelf.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in
|
||||||
|
return (chatTextInputAddLinkAttribute(current, selectionRange: selectionRange, url: link), inputMode)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
strongSelf.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in
|
||||||
|
return (chatTextInputRemoveLinkAttribute(current, selectionRange: selectionRange), inputMode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: true, {
|
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: true, {
|
||||||
return $0.updatedInputMode({ _ in return inputMode }).updatedInterfaceState({
|
return $0.updatedInputMode({ _ in return inputMode }).updatedInterfaceState({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user