Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2024-03-28 17:26:08 +04:00
commit 140bd3c526
13 changed files with 184 additions and 31 deletions

View File

@ -11599,7 +11599,7 @@ Sorry for the inconvenience.";
"ChatbotSetup.Recipients.IncludedSectionFooter" = "Select chats or entire chat categories which the bot **WILL** have access to."; "ChatbotSetup.Recipients.IncludedSectionFooter" = "Select chats or entire chat categories which the bot **WILL** have access to.";
"ChatbotSetup.PermissionsSectionHeader" = "BOT PERMISSIONS"; "ChatbotSetup.PermissionsSectionHeader" = "BOT PERMISSIONS";
"ChatbotSetup.PermissionsSectionFooter" = "The bot will be able to view all new incoming messages, but not the messages that had been sent before you added the bot."; "ChatbotSetup.PermissionsSectionFooter" = "The bot can only reply on your behalf in chats that were active during the last 24h.";
"ChatbotSetup.Permission.ReplyToMessages" = "Reply to Messages"; "ChatbotSetup.Permission.ReplyToMessages" = "Reply to Messages";
"ChatbotSetup.BotAddAction" = "ADD"; "ChatbotSetup.BotAddAction" = "ADD";

View File

@ -60,7 +60,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView {
} }
} }
self.anchor = anchor self.anchor = anchor
self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], clipHoles: true, trackHoles: false, peerIds: peerIds, ignoreMessagesInTimestampRange: nil, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, appendMessagesFromTheSameGroup: false, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: []) self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], clipHoles: true, trackHoles: true, peerIds: peerIds, ignoreMessagesInTimestampRange: nil, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, appendMessagesFromTheSameGroup: false, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [])
let _ = self.updateFromView() let _ = self.updateFromView()
} }

View File

@ -167,19 +167,34 @@ public func incomingMessagePrivacyScreen(context: AccountContext, value: Bool, u
} }
) )
let enableSetting: Signal<Bool, NoError> = context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId),
TelegramEngine.EngineData.Item.Configuration.App()
)
|> map { accountPeer, appConfig -> Bool in
if let accountPeer, accountPeer.isPremium {
return true
}
if let data = appConfig.data, let setting = data["new_noncontact_peers_require_premium_without_ownpremium"] as? Bool {
if setting {
return true
}
}
return false
}
|> distinctUntilChanged
let signal = combineLatest(queue: .mainQueue(), let signal = combineLatest(queue: .mainQueue(),
context.sharedContext.presentationData, context.sharedContext.presentationData,
context.engine.data.subscribe( statePromise.get(),
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId) enableSetting
),
statePromise.get()
) )
|> map { presentationData, accountPeer, state -> (ItemListControllerState, (ItemListNodeState, Any)) in |> map { presentationData, state, enableSetting -> (ItemListControllerState, (ItemListNodeState, Any)) in
let rightNavigationButton: ItemListNavigationButton? = nil let rightNavigationButton: ItemListNavigationButton? = nil
let title: ItemListControllerTitle = .text(presentationData.strings.Privacy_Messages_Title) let title: ItemListControllerTitle = .text(presentationData.strings.Privacy_Messages_Title)
let entries: [GlobalAutoremoveEntry] = incomingMessagePrivacyScreenEntries(presentationData: presentationData, state: state, isPremium: accountPeer?.isPremium ?? false) let entries: [GlobalAutoremoveEntry] = incomingMessagePrivacyScreenEntries(presentationData: presentationData, state: state, isPremium: enableSetting)
let animateChanges = false let animateChanges = false

View File

@ -376,6 +376,11 @@ final class ChatHistoryPreloadManager {
} }
private func update(indices: [(ChatHistoryPreloadIndex, Bool, Bool)], additionalPeerIds: Set<PeerId>) { private func update(indices: [(ChatHistoryPreloadIndex, Bool, Bool)], additionalPeerIds: Set<PeerId>) {
/*#if DEBUG
var indices = indices
indices.removeAll()
#endif*/
self.queue.async { self.queue.async {
var validEntityIds = Set(indices.map { $0.0.entity }) var validEntityIds = Set(indices.map { $0.0.entity })
for peerId in additionalPeerIds { for peerId in additionalPeerIds {
@ -431,7 +436,7 @@ final class ChatHistoryPreloadManager {
let key: PostboxViewKey let key: PostboxViewKey
switch index.entity { switch index.entity {
case let .peer(peerId, threadId): case let .peer(peerId, threadId):
key = .messageOfInterestHole(location: .peer(peerId: peerId, threadId: threadId), namespace: Namespaces.Message.Cloud, count: 70) key = .messageOfInterestHole(location: .peer(peerId: peerId, threadId: threadId), namespace: Namespaces.Message.Cloud, count: 50)
} }
view.disposable.set((self.postbox.combinedView(keys: [key]) view.disposable.set((self.postbox.combinedView(keys: [key])
|> deliverOn(self.queue)).start(next: { [weak self] next in |> deliverOn(self.queue)).start(next: { [weak self] next in

View File

@ -47,6 +47,7 @@ public final class EmojiActionIconComponent: Component {
let size = CGSize(width: 24.0, height: 24.0) let size = CGSize(width: 24.0, height: 24.0)
if let fileId = component.fileId { if let fileId = component.fileId {
var iconSize = size
let icon: ComponentView<Empty> let icon: ComponentView<Empty>
if let current = self.icon { if let current = self.icon {
icon = current icon = current
@ -56,6 +57,9 @@ public final class EmojiActionIconComponent: Component {
} }
let content: EmojiStatusComponent.AnimationContent let content: EmojiStatusComponent.AnimationContent
if let file = component.file { if let file = component.file {
if let dimensions = file.dimensions {
iconSize = dimensions.cgSize.aspectFitted(size)
}
content = .file(file: file) content = .file(file: file)
} else { } else {
content = .customEmoji(fileId: fileId) content = .customEmoji(fileId: fileId)
@ -68,7 +72,7 @@ public final class EmojiActionIconComponent: Component {
animationRenderer: component.context.animationRenderer, animationRenderer: component.context.animationRenderer,
content: .animation( content: .animation(
content: content, content: content,
size: size, size: iconSize,
placeholderColor: .lightGray, placeholderColor: .lightGray,
themeColor: component.color, themeColor: component.color,
loopMode: .forever loopMode: .forever
@ -77,9 +81,9 @@ public final class EmojiActionIconComponent: Component {
action: nil action: nil
)), )),
environment: {}, environment: {},
containerSize: size containerSize: iconSize
) )
let iconFrame = CGRect(origin: CGPoint(), size: size) let iconFrame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) * 0.5), y: floor((size.height - iconSize.height) * 0.5)), size: iconSize)
if let iconView = icon.view { if let iconView = icon.view {
if iconView.superview == nil { if iconView.superview == nil {
self.addSubview(iconView) self.addSubview(iconView)

View File

@ -30,6 +30,12 @@ public final class ListMultilineTextFieldItemComponent: Component {
} }
} }
public enum EmptyLineHandling {
case allowed
case oneConsecutive
case notAllowed
}
public let externalState: ExternalState? public let externalState: ExternalState?
public let context: AccountContext public let context: AccountContext
public let theme: PresentationTheme public let theme: PresentationTheme
@ -39,10 +45,12 @@ public final class ListMultilineTextFieldItemComponent: Component {
public let placeholder: String public let placeholder: String
public let autocapitalizationType: UITextAutocapitalizationType public let autocapitalizationType: UITextAutocapitalizationType
public let autocorrectionType: UITextAutocorrectionType public let autocorrectionType: UITextAutocorrectionType
public let returnKeyType: UIReturnKeyType
public let characterLimit: Int? public let characterLimit: Int?
public let displayCharacterLimit: Bool public let displayCharacterLimit: Bool
public let allowEmptyLines: Bool public let emptyLineHandling: EmptyLineHandling
public let updated: ((String) -> Void)? public let updated: ((String) -> Void)?
public let returnKeyAction: (() -> Void)?
public let textUpdateTransition: Transition public let textUpdateTransition: Transition
public let tag: AnyObject? public let tag: AnyObject?
@ -56,10 +64,12 @@ public final class ListMultilineTextFieldItemComponent: Component {
placeholder: String, placeholder: String,
autocapitalizationType: UITextAutocapitalizationType = .sentences, autocapitalizationType: UITextAutocapitalizationType = .sentences,
autocorrectionType: UITextAutocorrectionType = .default, autocorrectionType: UITextAutocorrectionType = .default,
returnKeyType: UIReturnKeyType = .default,
characterLimit: Int? = nil, characterLimit: Int? = nil,
displayCharacterLimit: Bool = false, displayCharacterLimit: Bool = false,
allowEmptyLines: Bool = true, emptyLineHandling: EmptyLineHandling = .allowed,
updated: ((String) -> Void)?, updated: ((String) -> Void)?,
returnKeyAction: (() -> Void)? = nil,
textUpdateTransition: Transition = .immediate, textUpdateTransition: Transition = .immediate,
tag: AnyObject? = nil tag: AnyObject? = nil
) { ) {
@ -72,10 +82,12 @@ public final class ListMultilineTextFieldItemComponent: Component {
self.placeholder = placeholder self.placeholder = placeholder
self.autocapitalizationType = autocapitalizationType self.autocapitalizationType = autocapitalizationType
self.autocorrectionType = autocorrectionType self.autocorrectionType = autocorrectionType
self.returnKeyType = returnKeyType
self.characterLimit = characterLimit self.characterLimit = characterLimit
self.displayCharacterLimit = displayCharacterLimit self.displayCharacterLimit = displayCharacterLimit
self.allowEmptyLines = allowEmptyLines self.emptyLineHandling = emptyLineHandling
self.updated = updated self.updated = updated
self.returnKeyAction = returnKeyAction
self.textUpdateTransition = textUpdateTransition self.textUpdateTransition = textUpdateTransition
self.tag = tag self.tag = tag
} }
@ -108,13 +120,16 @@ public final class ListMultilineTextFieldItemComponent: Component {
if lhs.autocorrectionType != rhs.autocorrectionType { if lhs.autocorrectionType != rhs.autocorrectionType {
return false return false
} }
if lhs.returnKeyType != rhs.returnKeyType {
return false
}
if lhs.characterLimit != rhs.characterLimit { if lhs.characterLimit != rhs.characterLimit {
return false return false
} }
if lhs.displayCharacterLimit != rhs.displayCharacterLimit { if lhs.displayCharacterLimit != rhs.displayCharacterLimit {
return false return false
} }
if lhs.allowEmptyLines != rhs.allowEmptyLines { if lhs.emptyLineHandling != rhs.emptyLineHandling {
return false return false
} }
if (lhs.updated == nil) != (rhs.updated == nil) { if (lhs.updated == nil) != (rhs.updated == nil) {
@ -190,6 +205,12 @@ public final class ListMultilineTextFieldItemComponent: Component {
return false return false
} }
public func activateInput() {
if let textFieldView = self.textField.view as? TextFieldComponent.View {
textFieldView.activateInput()
}
}
func update(component: ListMultilineTextFieldItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize { func update(component: ListMultilineTextFieldItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
self.isUpdating = true self.isUpdating = true
defer { defer {
@ -225,6 +246,16 @@ public final class ListMultilineTextFieldItemComponent: Component {
self.measureTextLimitLabel = nil self.measureTextLimitLabel = nil
} }
let mappedEmptyLineHandling: TextFieldComponent.EmptyLineHandling
switch component.emptyLineHandling {
case .allowed:
mappedEmptyLineHandling = .allowed
case .oneConsecutive:
mappedEmptyLineHandling = .oneConsecutive
case .notAllowed:
mappedEmptyLineHandling = .notAllowed
}
let textFieldSize = self.textField.update( let textFieldSize = self.textField.update(
transition: transition, transition: transition,
component: AnyComponent(TextFieldComponent( component: AnyComponent(TextFieldComponent(
@ -242,13 +273,20 @@ public final class ListMultilineTextFieldItemComponent: Component {
}, },
isOneLineWhenUnfocused: false, isOneLineWhenUnfocused: false,
characterLimit: component.characterLimit, characterLimit: component.characterLimit,
allowEmptyLines: component.allowEmptyLines, emptyLineHandling: mappedEmptyLineHandling,
formatMenuAvailability: .none, formatMenuAvailability: .none,
returnKeyType: component.returnKeyType,
lockedFormatAction: { lockedFormatAction: {
}, },
present: { _ in present: { _ in
}, },
paste: { _ in paste: { _ in
},
returnKeyAction: { [weak self] in
guard let self, let component = self.component else {
return
}
component.returnKeyAction?()
} }
)), )),
environment: {}, environment: {},

View File

@ -321,7 +321,12 @@ private final class SendInviteLinkScreenComponent: Component {
self.scrollContentView.addSubview(avatarsNode.view) self.scrollContentView.addSubview(avatarsNode.view)
} }
let avatarPeers = component.peers.map(\.peer) let avatarPeers: [EnginePeer]
if !premiumRestrictedUsers.isEmpty {
avatarPeers = premiumRestrictedUsers.map(\.peer)
} else {
avatarPeers = component.peers.map(\.peer)
}
let avatarsContent = self.avatarsContext.update(peers: avatarPeers.count <= 3 ? avatarPeers : Array(avatarPeers.prefix(upTo: 3)), animated: false) let avatarsContent = self.avatarsContext.update(peers: avatarPeers.count <= 3 ? avatarPeers : Array(avatarPeers.prefix(upTo: 3)), animated: false)
let avatarsSize = avatarsNode.update( let avatarsSize = avatarsNode.update(
context: component.context, context: component.context,

View File

@ -77,6 +77,7 @@ final class BusinessIntroSetupScreenComponent: Component {
private let introSection = ComponentView<Empty>() private let introSection = ComponentView<Empty>()
private let deleteSection = ComponentView<Empty>() private let deleteSection = ComponentView<Empty>()
private var ignoreScrolling: Bool = false
private var isUpdating: Bool = false private var isUpdating: Bool = false
private var component: BusinessIntroSetupScreenComponent? private var component: BusinessIntroSetupScreenComponent?
@ -161,7 +162,9 @@ final class BusinessIntroSetupScreenComponent: Component {
} }
func scrollViewDidScroll(_ scrollView: UIScrollView) { func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.updateScrolling(transition: .immediate) if !self.ignoreScrolling {
self.updateScrolling(transition: .immediate)
}
} }
private var scrolledUp = true private var scrolledUp = true
@ -765,11 +768,21 @@ final class BusinessIntroSetupScreenComponent: Component {
placeholder: "Enter Title", placeholder: "Enter Title",
autocapitalizationType: .none, autocapitalizationType: .none,
autocorrectionType: .no, autocorrectionType: .no,
returnKeyType: .next,
characterLimit: 32, characterLimit: 32,
displayCharacterLimit: true, displayCharacterLimit: true,
allowEmptyLines: false, emptyLineHandling: .notAllowed,
updated: { _ in updated: { _ in
}, },
returnKeyAction: { [weak self] in
guard let self else {
return
}
if let titleView = self.introSection.findTaggedView(tag: self.textInputTag) as? ListMultilineTextFieldItemComponent.View {
titleView.activateInput()
}
},
textUpdateTransition: .spring(duration: 0.4), textUpdateTransition: .spring(duration: 0.4),
tag: self.titleInputTag tag: self.titleInputTag
)))) ))))
@ -786,11 +799,20 @@ final class BusinessIntroSetupScreenComponent: Component {
placeholder: "Enter Message", placeholder: "Enter Message",
autocapitalizationType: .none, autocapitalizationType: .none,
autocorrectionType: .no, autocorrectionType: .no,
returnKeyType: .done,
characterLimit: 70, characterLimit: 70,
displayCharacterLimit: true, displayCharacterLimit: true,
allowEmptyLines: false, emptyLineHandling: .notAllowed,
updated: { _ in updated: { _ in
}, },
returnKeyAction: { [weak self] in
guard let self else {
return
}
if let titleView = self.introSection.findTaggedView(tag: self.textInputTag) as? ListMultilineTextFieldItemComponent.View {
titleView.endEditing(true)
}
},
textUpdateTransition: .spring(duration: 0.4), textUpdateTransition: .spring(duration: 0.4),
tag: self.textInputTag tag: self.textInputTag
)))) ))))
@ -1083,6 +1105,7 @@ final class BusinessIntroSetupScreenComponent: Component {
let previousBounds = self.scrollView.bounds let previousBounds = self.scrollView.bounds
self.ignoreScrolling = true
let contentSize = CGSize(width: availableSize.width, height: contentHeight) let contentSize = CGSize(width: availableSize.width, height: contentHeight)
if self.scrollView.frame != CGRect(origin: CGPoint(), size: availableSize) { if self.scrollView.frame != CGRect(origin: CGPoint(), size: availableSize) {
self.scrollView.frame = CGRect(origin: CGPoint(), size: availableSize) self.scrollView.frame = CGRect(origin: CGPoint(), size: availableSize)
@ -1123,6 +1146,7 @@ final class BusinessIntroSetupScreenComponent: Component {
} }
self.topOverscrollLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: -3000.0), size: CGSize(width: availableSize.width, height: 3000.0)) self.topOverscrollLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: -3000.0), size: CGSize(width: availableSize.width, height: 3000.0))
self.ignoreScrolling = false
self.updateScrolling(transition: transition) self.updateScrolling(transition: transition)

View File

@ -406,7 +406,7 @@ final class BusinessLocationSetupScreenComponent: Component {
autocapitalizationType: .none, autocapitalizationType: .none,
autocorrectionType: .no, autocorrectionType: .no,
characterLimit: 256, characterLimit: 256,
allowEmptyLines: false, emptyLineHandling: .oneConsecutive,
updated: { _ in updated: { _ in
}, },
textUpdateTransition: .spring(duration: 0.4), textUpdateTransition: .spring(duration: 0.4),

View File

@ -295,7 +295,7 @@ final class ChatbotSetupScreenComponent: Component {
break break
case let .result(peer): case let .result(peer):
let previousState = self.botResolutionState?.state let previousState = self.botResolutionState?.state
if let peer { if let peer, case let .user(user) = peer, user.botInfo != nil {
self.botResolutionState?.state = .found(peer: peer, isInstalled: false) self.botResolutionState?.state = .found(peer: peer, isInstalled: false)
} else { } else {
self.botResolutionState?.state = .notFound self.botResolutionState?.state = .notFound

View File

@ -89,6 +89,12 @@ public final class TextFieldComponent: Component {
case none case none
} }
public enum EmptyLineHandling {
case allowed
case oneConsecutive
case notAllowed
}
public let context: AccountContext public let context: AccountContext
public let theme: PresentationTheme public let theme: PresentationTheme
public let strings: PresentationStrings public let strings: PresentationStrings
@ -101,11 +107,13 @@ public final class TextFieldComponent: Component {
public let resetText: NSAttributedString? public let resetText: NSAttributedString?
public let isOneLineWhenUnfocused: Bool public let isOneLineWhenUnfocused: Bool
public let characterLimit: Int? public let characterLimit: Int?
public let allowEmptyLines: Bool public let emptyLineHandling: EmptyLineHandling
public let formatMenuAvailability: FormatMenuAvailability public let formatMenuAvailability: FormatMenuAvailability
public let returnKeyType: UIReturnKeyType
public let lockedFormatAction: () -> Void public let lockedFormatAction: () -> Void
public let present: (ViewController) -> Void public let present: (ViewController) -> Void
public let paste: (PasteData) -> Void public let paste: (PasteData) -> Void
public let returnKeyAction: (() -> Void)?
public init( public init(
context: AccountContext, context: AccountContext,
@ -120,11 +128,13 @@ public final class TextFieldComponent: Component {
resetText: NSAttributedString?, resetText: NSAttributedString?,
isOneLineWhenUnfocused: Bool, isOneLineWhenUnfocused: Bool,
characterLimit: Int? = nil, characterLimit: Int? = nil,
allowEmptyLines: Bool = true, emptyLineHandling: EmptyLineHandling = .allowed,
formatMenuAvailability: FormatMenuAvailability, formatMenuAvailability: FormatMenuAvailability,
returnKeyType: UIReturnKeyType = .default,
lockedFormatAction: @escaping () -> Void, lockedFormatAction: @escaping () -> Void,
present: @escaping (ViewController) -> Void, present: @escaping (ViewController) -> Void,
paste: @escaping (PasteData) -> Void paste: @escaping (PasteData) -> Void,
returnKeyAction: (() -> Void)? = nil
) { ) {
self.context = context self.context = context
self.theme = theme self.theme = theme
@ -138,11 +148,13 @@ public final class TextFieldComponent: Component {
self.resetText = resetText self.resetText = resetText
self.isOneLineWhenUnfocused = isOneLineWhenUnfocused self.isOneLineWhenUnfocused = isOneLineWhenUnfocused
self.characterLimit = characterLimit self.characterLimit = characterLimit
self.allowEmptyLines = allowEmptyLines self.emptyLineHandling = emptyLineHandling
self.formatMenuAvailability = formatMenuAvailability self.formatMenuAvailability = formatMenuAvailability
self.returnKeyType = returnKeyType
self.lockedFormatAction = lockedFormatAction self.lockedFormatAction = lockedFormatAction
self.present = present self.present = present
self.paste = paste self.paste = paste
self.returnKeyAction = returnKeyAction
} }
public static func ==(lhs: TextFieldComponent, rhs: TextFieldComponent) -> Bool { public static func ==(lhs: TextFieldComponent, rhs: TextFieldComponent) -> Bool {
@ -182,12 +194,15 @@ public final class TextFieldComponent: Component {
if lhs.characterLimit != rhs.characterLimit { if lhs.characterLimit != rhs.characterLimit {
return false return false
} }
if lhs.allowEmptyLines != rhs.allowEmptyLines { if lhs.emptyLineHandling != rhs.emptyLineHandling {
return false return false
} }
if lhs.formatMenuAvailability != rhs.formatMenuAvailability { if lhs.formatMenuAvailability != rhs.formatMenuAvailability {
return false return false
} }
if lhs.returnKeyType != rhs.returnKeyType {
return false
}
return true return true
} }
@ -402,6 +417,13 @@ public final class TextFieldComponent: Component {
} }
public func chatInputTextNodeShouldReturn() -> Bool { public func chatInputTextNodeShouldReturn() -> Bool {
guard let component = self.component else {
return true
}
if let returnKeyAction = component.returnKeyAction {
returnKeyAction()
return false
}
return true return true
} }
@ -559,6 +581,7 @@ public final class TextFieldComponent: Component {
guard let component = self.component else { guard let component = self.component else {
return true return true
} }
if let characterLimit = component.characterLimit { if let characterLimit = component.characterLimit {
let string = self.inputState.inputText.string as NSString let string = self.inputState.inputText.string as NSString
let updatedString = string.replacingCharacters(in: range, with: text) let updatedString = string.replacingCharacters(in: range, with: text)
@ -566,12 +589,26 @@ public final class TextFieldComponent: Component {
return false return false
} }
} }
if !component.allowEmptyLines { switch component.emptyLineHandling {
case .allowed:
break
case .oneConsecutive:
let string = self.inputState.inputText.string as NSString let string = self.inputState.inputText.string as NSString
let updatedString = string.replacingCharacters(in: range, with: text) let updatedString = string.replacingCharacters(in: range, with: text)
if updatedString.range(of: "\n\n") != nil { if updatedString.range(of: "\n\n") != nil {
return false return false
} }
case .notAllowed:
if (range.length == 0 && text == "\n"), let returnKeyAction = component.returnKeyAction {
returnKeyAction()
return false
}
let string = self.inputState.inputText.string as NSString
let updatedString = string.replacingCharacters(in: range, with: text)
if updatedString.range(of: "\n") != nil {
return false
}
} }
return true return true
@ -1066,6 +1103,10 @@ public final class TextFieldComponent: Component {
} }
} }
if self.textView.returnKeyType != component.returnKeyType {
self.textView.returnKeyType = component.returnKeyType
}
if let initialText = component.externalState.initialText { if let initialText = component.externalState.initialText {
component.externalState.initialText = nil component.externalState.initialText = nil
self.updateInputState { _ in self.updateInputState { _ in

View File

@ -17,7 +17,7 @@ extension ChatControllerImpl {
guard let self, let itemNode else { guard let self, let itemNode else {
return return
} }
if value >= 1 { if value >= 2 {
return return
} }
@ -29,7 +29,7 @@ extension ChatControllerImpl {
let location = CGPoint(x: bounds.midX, y: bounds.minY - 11.0) let location = CGPoint(x: bounds.midX, y: bounds.minY - 11.0)
//TODO:localize //TODO:localize
let tooltipController = TooltipController(content: .text("Only you can see that this message was sent by the bot."), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, balancedTextLayout: true, timeout: 3.5, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true) let tooltipController = TooltipController(content: .text("Only you can see that this\nmessage was sent by the bot."), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, balancedTextLayout: true, timeout: 3.5, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true)
self.checksTooltipController = tooltipController self.checksTooltipController = tooltipController
tooltipController.dismissed = { [weak self, weak tooltipController] _ in tooltipController.dismissed = { [weak self, weak tooltipController] _ in
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.checksTooltipController === tooltipController { if let strongSelf = self, let tooltipController = tooltipController, strongSelf.checksTooltipController === tooltipController {
@ -57,6 +57,11 @@ extension ChatControllerImpl {
return (self.chatDisplayNode, CGRect(origin: location, size: CGSize())) return (self.chatDisplayNode, CGRect(origin: location, size: CGSize()))
})) }))
#if DEBUG
if "".isEmpty {
return
}
#endif
let _ = ApplicationSpecificNotice.incrementBusinessBotMessageTooltip(accountManager: self.context.sharedContext.accountManager).startStandalone() let _ = ApplicationSpecificNotice.incrementBusinessBotMessageTooltip(accountManager: self.context.sharedContext.accountManager).startStandalone()
}) })
} }

View File

@ -691,6 +691,22 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
convertedUrl = "https://t.me/giftcode/\(slug)" convertedUrl = "https://t.me/giftcode/\(slug)"
} }
} }
} else if parsedUrl.host == "message" {
if let components = URLComponents(string: "/?" + query) {
var parameter: String?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "slug" {
parameter = value
}
}
}
}
if let parameter {
convertedUrl = "https://t.me/m/\(parameter)"
}
}
} }
if parsedUrl.host == "resolve" { if parsedUrl.host == "resolve" {