mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-29 03:21:29 +00:00
832 lines
30 KiB
Swift
832 lines
30 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import AsyncDisplayKit
|
|
import TelegramPresentationData
|
|
import ComponentFlow
|
|
import ChatControllerInteraction
|
|
import AccountContext
|
|
import ChatPresentationInterfaceState
|
|
import TelegramCore
|
|
import ComponentDisplayAdapters
|
|
|
|
private final class EmptyInputView: UIView, UIInputViewAudioFeedback {
|
|
var enableInputClicksWhenVisible: Bool {
|
|
return true
|
|
}
|
|
}
|
|
|
|
public final class ChatTextInputPanelComponent: Component {
|
|
public final class ExternalState {
|
|
public fileprivate(set) var isEditing: Bool = false
|
|
public fileprivate(set) var textInputState: ChatTextInputState = ChatTextInputState()
|
|
public var resetInputState: ChatTextInputState?
|
|
|
|
public init() {
|
|
}
|
|
}
|
|
|
|
public enum InputMode {
|
|
case text
|
|
case emoji
|
|
case stickers
|
|
case commands
|
|
}
|
|
|
|
public final class InlineAction: Equatable {
|
|
public enum Kind: Equatable {
|
|
case paidMessage
|
|
case inputMode(InputMode)
|
|
}
|
|
|
|
public let kind: Kind
|
|
public let action: () -> Void
|
|
|
|
public init(kind: Kind, action: @escaping () -> Void) {
|
|
self.kind = kind
|
|
self.action = action
|
|
}
|
|
|
|
public static func ==(lhs: InlineAction, rhs: InlineAction) -> Bool {
|
|
if lhs.kind != rhs.kind {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
public final class LeftAction: Equatable {
|
|
public enum Kind: Equatable {
|
|
case attach
|
|
case toggleExpanded(isVisible: Bool, isExpanded: Bool)
|
|
}
|
|
|
|
public let kind: Kind
|
|
public let action: () -> Void
|
|
|
|
public init(kind: Kind, action: @escaping () -> Void) {
|
|
self.kind = kind
|
|
self.action = action
|
|
}
|
|
|
|
public static func ==(lhs: LeftAction, rhs: LeftAction) -> Bool {
|
|
if lhs.kind != rhs.kind {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
public final class RightAction: Equatable {
|
|
public enum Kind: Equatable {
|
|
case stars(count: Int, isFilled: Bool)
|
|
}
|
|
|
|
public let kind: Kind
|
|
public let action: () -> Void
|
|
|
|
public init(kind: Kind, action: @escaping () -> Void) {
|
|
self.kind = kind
|
|
self.action = action
|
|
}
|
|
|
|
public static func ==(lhs: RightAction, rhs: RightAction) -> Bool {
|
|
if lhs.kind != rhs.kind {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
let externalState: ExternalState
|
|
let context: AccountContext
|
|
let theme: PresentationTheme
|
|
let strings: PresentationStrings
|
|
let chatPeerId: EnginePeer.Id
|
|
let inlineActions: [InlineAction]
|
|
let leftAction: LeftAction?
|
|
let rightAction: RightAction?
|
|
let placeholder: String
|
|
let paidMessagePrice: StarsAmount?
|
|
let sendColor: UIColor?
|
|
let hideKeyboard: Bool
|
|
let insets: UIEdgeInsets
|
|
let maxHeight: CGFloat
|
|
let maxLength: Int?
|
|
let sendAction: (() -> Void)?
|
|
let sendContextAction: ((UIView, ContextGesture) -> Void)?
|
|
|
|
public init(
|
|
externalState: ExternalState,
|
|
context: AccountContext,
|
|
theme: PresentationTheme,
|
|
strings: PresentationStrings,
|
|
chatPeerId: EnginePeer.Id,
|
|
inlineActions: [InlineAction],
|
|
leftAction: LeftAction?,
|
|
rightAction: RightAction?,
|
|
placeholder: String,
|
|
paidMessagePrice: StarsAmount?,
|
|
sendColor: UIColor?,
|
|
hideKeyboard: Bool,
|
|
insets: UIEdgeInsets,
|
|
maxHeight: CGFloat,
|
|
maxLength: Int?,
|
|
sendAction: (() -> Void)?,
|
|
sendContextAction: ((UIView, ContextGesture) -> Void)?
|
|
) {
|
|
self.externalState = externalState
|
|
self.context = context
|
|
self.theme = theme
|
|
self.strings = strings
|
|
self.chatPeerId = chatPeerId
|
|
self.inlineActions = inlineActions
|
|
self.leftAction = leftAction
|
|
self.rightAction = rightAction
|
|
self.placeholder = placeholder
|
|
self.paidMessagePrice = paidMessagePrice
|
|
self.sendColor = sendColor
|
|
self.hideKeyboard = hideKeyboard
|
|
self.insets = insets
|
|
self.maxHeight = maxHeight
|
|
self.maxLength = maxLength
|
|
self.sendAction = sendAction
|
|
self.sendContextAction = sendContextAction
|
|
}
|
|
|
|
public static func ==(lhs: ChatTextInputPanelComponent, rhs: ChatTextInputPanelComponent) -> Bool {
|
|
if lhs.externalState !== rhs.externalState {
|
|
return false
|
|
}
|
|
if lhs.context !== rhs.context {
|
|
return false
|
|
}
|
|
if lhs.theme !== rhs.theme {
|
|
return false
|
|
}
|
|
if lhs.strings !== rhs.strings {
|
|
return false
|
|
}
|
|
if lhs.chatPeerId != rhs.chatPeerId {
|
|
return false
|
|
}
|
|
if lhs.inlineActions != rhs.inlineActions {
|
|
return false
|
|
}
|
|
if lhs.leftAction != rhs.leftAction {
|
|
return false
|
|
}
|
|
if lhs.rightAction != rhs.rightAction {
|
|
return false
|
|
}
|
|
if lhs.placeholder != rhs.placeholder {
|
|
return false
|
|
}
|
|
if lhs.paidMessagePrice != rhs.paidMessagePrice {
|
|
return false
|
|
}
|
|
if lhs.sendColor != rhs.sendColor {
|
|
return false
|
|
}
|
|
if lhs.hideKeyboard != rhs.hideKeyboard {
|
|
return false
|
|
}
|
|
if lhs.insets != rhs.insets {
|
|
return false
|
|
}
|
|
if lhs.maxHeight != rhs.maxHeight {
|
|
return false
|
|
}
|
|
if lhs.maxLength != rhs.maxLength {
|
|
return false
|
|
}
|
|
if (lhs.sendAction == nil) != (rhs.sendAction == nil) {
|
|
return false
|
|
}
|
|
if (lhs.sendContextAction == nil) != (rhs.sendContextAction == nil) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
public final class View: UIView {
|
|
private var panelNode: ChatTextInputPanelNode?
|
|
|
|
private var interfaceInteraction: ChatPanelInterfaceInteraction?
|
|
|
|
private var component: ChatTextInputPanelComponent?
|
|
private weak var state: EmptyComponentState?
|
|
private var isUpdating: Bool = false
|
|
|
|
override public init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
}
|
|
|
|
required public init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
public func insertText(text: NSAttributedString) {
|
|
guard let panelNode = self.panelNode else {
|
|
return
|
|
}
|
|
panelNode.insertText(text: text)
|
|
}
|
|
|
|
public func deleteBackward() {
|
|
guard let panelNode = self.panelNode, let textView = panelNode.textInputNode?.textView else {
|
|
return
|
|
}
|
|
textView.deleteBackward()
|
|
}
|
|
|
|
public func activateInput() {
|
|
guard let panelNode = self.panelNode else {
|
|
return
|
|
}
|
|
panelNode.ensureFocused()
|
|
}
|
|
|
|
public func updateState(transition: ComponentTransition) {
|
|
self.state?.updated(transition: transition)
|
|
}
|
|
|
|
func update(component: ChatTextInputPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
|
self.isUpdating = true
|
|
defer {
|
|
self.isUpdating = false
|
|
}
|
|
|
|
self.component = component
|
|
self.state = state
|
|
|
|
if self.interfaceInteraction == nil {
|
|
let inputModeFromComponent: (ChatTextInputPanelComponent) -> ChatInputMode = { component in
|
|
for inlineAction in component.inlineActions {
|
|
switch inlineAction.kind {
|
|
case let .inputMode(inputMode):
|
|
switch inputMode {
|
|
case .text:
|
|
return .media(mode: .other, expanded: nil, focused: false)
|
|
case .commands:
|
|
return .text
|
|
case .stickers:
|
|
return .media(mode: .other, expanded: nil, focused: false)
|
|
case .emoji:
|
|
return .text
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
return .text
|
|
}
|
|
|
|
self.interfaceInteraction = ChatPanelInterfaceInteraction(
|
|
setupReplyMessage: { _, _, _ in
|
|
},
|
|
setupEditMessage: { _, _ in
|
|
},
|
|
beginMessageSelection: { _, _ in
|
|
},
|
|
cancelMessageSelection: { _ in
|
|
},
|
|
deleteSelectedMessages: {
|
|
},
|
|
reportSelectedMessages: {
|
|
},
|
|
reportMessages: { _, _ in
|
|
},
|
|
blockMessageAuthor: { _, _ in
|
|
},
|
|
deleteMessages: { _, _, f in
|
|
f(.default)
|
|
},
|
|
forwardSelectedMessages: {
|
|
},
|
|
forwardCurrentForwardMessages: {
|
|
},
|
|
forwardMessages: { _ in
|
|
},
|
|
updateForwardOptionsState: { _ in
|
|
},
|
|
presentForwardOptions: { _ in
|
|
},
|
|
presentReplyOptions: { _ in
|
|
},
|
|
presentLinkOptions: { _ in
|
|
},
|
|
presentSuggestPostOptions: {
|
|
},
|
|
shareSelectedMessages: {
|
|
},
|
|
updateTextInputStateAndMode: { [weak self] f in
|
|
guard let self else {
|
|
return
|
|
}
|
|
if let component = self.component {
|
|
let currentMode = inputModeFromComponent(component)
|
|
let (updatedTextInputState, updatedMode) = f(component.externalState.textInputState, currentMode)
|
|
component.externalState.textInputState = updatedTextInputState
|
|
if !self.isUpdating {
|
|
self.state?.updated(transition: .spring(duration: 0.4))
|
|
}
|
|
|
|
if updatedMode != currentMode {
|
|
/*for inlineAction in component.inlineActions {
|
|
switch inlineAction.kind {
|
|
case .inputMode:
|
|
inlineAction.action()
|
|
return
|
|
default:
|
|
break
|
|
}
|
|
}*/
|
|
}
|
|
}
|
|
},
|
|
updateInputModeAndDismissedButtonKeyboardMessageId: { [weak self] f in
|
|
guard let self, let component = self.component else {
|
|
return
|
|
}
|
|
|
|
var presentationInterfaceState = ChatPresentationInterfaceState(
|
|
chatWallpaper: .color(0),
|
|
theme: component.theme,
|
|
strings: component.strings,
|
|
dateTimeFormat: PresentationDateTimeFormat(),
|
|
nameDisplayOrder: .firstLast,
|
|
limitsConfiguration: component.context.currentLimitsConfiguration.with({ $0 }),
|
|
fontSize: .regular,
|
|
bubbleCorners: PresentationChatBubbleCorners(
|
|
mainRadius: 16.0,
|
|
auxiliaryRadius: 8.0,
|
|
mergeBubbleCorners: true
|
|
),
|
|
accountPeerId: component.context.account.peerId,
|
|
mode: .standard(.default),
|
|
chatLocation: .peer(id: component.chatPeerId),
|
|
subject: nil,
|
|
peerNearbyData: nil,
|
|
greetingData: nil,
|
|
pendingUnpinnedAllMessages: false,
|
|
activeGroupCallInfo: nil,
|
|
hasActiveGroupCall: false,
|
|
importState: nil,
|
|
threadData: nil,
|
|
isGeneralThreadClosed: false,
|
|
replyMessage: nil,
|
|
accountPeerColor: nil,
|
|
businessIntro: nil
|
|
)
|
|
let currentMode = inputModeFromComponent(component)
|
|
presentationInterfaceState = presentationInterfaceState.updatedInputMode { _ in
|
|
return currentMode
|
|
}
|
|
|
|
let (updatedMode, _) = f(presentationInterfaceState)
|
|
|
|
if updatedMode != currentMode {
|
|
/*for inlineAction in component.inlineActions {
|
|
switch inlineAction.kind {
|
|
case .inputMode:
|
|
inlineAction.action()
|
|
return
|
|
default:
|
|
break
|
|
}
|
|
}*/
|
|
}
|
|
|
|
if let panelNode = self.panelNode, let textView = panelNode.textInputNode?.textView {
|
|
component.externalState.isEditing = textView.isFirstResponder
|
|
} else {
|
|
component.externalState.isEditing = false
|
|
}
|
|
},
|
|
openStickers: { [weak self] in
|
|
guard let self, let component = self.component else {
|
|
return
|
|
}
|
|
for inlineAction in component.inlineActions {
|
|
switch inlineAction.kind {
|
|
case .inputMode:
|
|
inlineAction.action()
|
|
return
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
},
|
|
editMessage: {
|
|
},
|
|
beginMessageSearch: { _, _ in
|
|
},
|
|
dismissMessageSearch: {
|
|
},
|
|
updateMessageSearch: { _ in
|
|
},
|
|
openSearchResults: {
|
|
},
|
|
navigateMessageSearch: { _ in
|
|
},
|
|
openCalendarSearch: {
|
|
},
|
|
toggleMembersSearch: { _ in
|
|
},
|
|
navigateToMessage: { _, _, _, _ in
|
|
},
|
|
navigateToChat: { _ in
|
|
},
|
|
navigateToProfile: { _ in
|
|
},
|
|
openPeerInfo: {
|
|
},
|
|
togglePeerNotifications: {
|
|
},
|
|
sendContextResult: { _, _, _, _ in
|
|
return false
|
|
},
|
|
sendBotCommand: { _, _ in
|
|
},
|
|
sendShortcut: { _ in
|
|
},
|
|
openEditShortcuts: {
|
|
},
|
|
sendBotStart: { _ in
|
|
},
|
|
botSwitchChatWithPayload: { _, _ in
|
|
},
|
|
beginMediaRecording: { _ in
|
|
},
|
|
finishMediaRecording: { _ in
|
|
},
|
|
stopMediaRecording: {
|
|
},
|
|
lockMediaRecording: {
|
|
},
|
|
resumeMediaRecording: {
|
|
},
|
|
deleteRecordedMedia: {
|
|
},
|
|
sendRecordedMedia: { _, _ in
|
|
},
|
|
displayRestrictedInfo: { _, _ in
|
|
},
|
|
displayVideoUnmuteTip: { _ in
|
|
},
|
|
switchMediaRecordingMode: {
|
|
},
|
|
setupMessageAutoremoveTimeout: {
|
|
},
|
|
sendSticker: { _, _, _, _, _, _ in
|
|
return false
|
|
},
|
|
unblockPeer: {
|
|
},
|
|
pinMessage: { _, _ in
|
|
},
|
|
unpinMessage: { _, _, _ in
|
|
},
|
|
unpinAllMessages: {
|
|
},
|
|
openPinnedList: { _ in
|
|
},
|
|
shareAccountContact: {
|
|
},
|
|
reportPeer: {
|
|
},
|
|
presentPeerContact: {
|
|
},
|
|
dismissReportPeer: {
|
|
},
|
|
deleteChat: {
|
|
},
|
|
beginCall: { _ in
|
|
},
|
|
toggleMessageStickerStarred: { _ in
|
|
},
|
|
presentController: { _, _ in
|
|
},
|
|
presentControllerInCurrent: { _, _ in
|
|
},
|
|
getNavigationController: {
|
|
return nil
|
|
},
|
|
presentGlobalOverlayController: { _, _ in
|
|
},
|
|
navigateFeed: {
|
|
},
|
|
openGrouping: {
|
|
},
|
|
toggleSilentPost: {
|
|
},
|
|
requestUnvoteInMessage: { _ in
|
|
},
|
|
requestStopPollInMessage: { _ in
|
|
},
|
|
updateInputLanguage: { _ in
|
|
},
|
|
unarchiveChat: {
|
|
},
|
|
openLinkEditing: {
|
|
},
|
|
displaySlowmodeTooltip: { _, _ in
|
|
},
|
|
displaySendMessageOptions: { [weak self] node, gesture in
|
|
guard let self, let component = self.component else {
|
|
return
|
|
}
|
|
|
|
component.sendContextAction?(node.view, gesture)
|
|
},
|
|
openScheduledMessages: {
|
|
},
|
|
openPeersNearby: {
|
|
},
|
|
displaySearchResultsTooltip: { _, _ in
|
|
},
|
|
unarchivePeer: {
|
|
},
|
|
scrollToTop: {
|
|
},
|
|
viewReplies: { _, _ in
|
|
},
|
|
activatePinnedListPreview: { _, _ in
|
|
},
|
|
joinGroupCall: { _ in
|
|
},
|
|
presentInviteMembers: {
|
|
},
|
|
presentGigagroupHelp: {
|
|
},
|
|
openMonoforum: {
|
|
},
|
|
editMessageMedia: { _, _ in
|
|
},
|
|
updateShowCommands: { _ in
|
|
},
|
|
updateShowSendAsPeers: { _ in
|
|
},
|
|
openInviteRequests: {
|
|
},
|
|
openSendAsPeer: { _, _ in
|
|
},
|
|
presentChatRequestAdminInfo: {
|
|
},
|
|
displayCopyProtectionTip: { _, _ in
|
|
},
|
|
openWebView: { _, _, _, _ in
|
|
},
|
|
updateShowWebView: { _ in
|
|
},
|
|
insertText: { _ in
|
|
},
|
|
backwardsDeleteText: {
|
|
},
|
|
restartTopic: {
|
|
},
|
|
toggleTranslation: { _ in
|
|
},
|
|
changeTranslationLanguage: { _ in
|
|
},
|
|
addDoNotTranslateLanguage: { _ in
|
|
},
|
|
hideTranslationPanel: {
|
|
},
|
|
openPremiumGift: {
|
|
},
|
|
openSuggestPost: { [weak self] _, _ in
|
|
guard let self, let component = self.component else {
|
|
return
|
|
}
|
|
for action in component.inlineActions {
|
|
if case .paidMessage = action.kind {
|
|
action.action()
|
|
break
|
|
}
|
|
}
|
|
},
|
|
openPremiumRequiredForMessaging: {
|
|
},
|
|
openStarsPurchase: { _ in
|
|
},
|
|
openMessagePayment: {
|
|
},
|
|
openBoostToUnrestrict: {
|
|
},
|
|
updateRecordingTrimRange: { _, _, _, _ in
|
|
},
|
|
dismissAllTooltips: {
|
|
},
|
|
editTodoMessage: { _, _, _ in
|
|
},
|
|
dismissUrlPreview: {
|
|
},
|
|
dismissForwardMessages: {
|
|
},
|
|
dismissSuggestPost: {
|
|
},
|
|
displayUndo: { _ in
|
|
},
|
|
sendEmoji: { _, _, _ in
|
|
},
|
|
updateHistoryFilter: { _ in
|
|
},
|
|
updateChatLocationThread: { _, _ in
|
|
},
|
|
toggleChatSidebarMode: {
|
|
},
|
|
updateDisplayHistoryFilterAsList: { _ in
|
|
},
|
|
requestLayout: { _ in
|
|
},
|
|
chatController: {
|
|
return nil
|
|
},
|
|
statuses: nil
|
|
)
|
|
}
|
|
|
|
var presentationInterfaceState = ChatPresentationInterfaceState(
|
|
chatWallpaper: .color(0),
|
|
theme: component.theme,
|
|
strings: component.strings,
|
|
dateTimeFormat: PresentationDateTimeFormat(),
|
|
nameDisplayOrder: .firstLast,
|
|
limitsConfiguration: component.context.currentLimitsConfiguration.with({ $0 }),
|
|
fontSize: .regular,
|
|
bubbleCorners: PresentationChatBubbleCorners(
|
|
mainRadius: 16.0,
|
|
auxiliaryRadius: 8.0,
|
|
mergeBubbleCorners: true
|
|
),
|
|
accountPeerId: component.context.account.peerId,
|
|
mode: .standard(.default),
|
|
chatLocation: .peer(id: component.chatPeerId),
|
|
subject: nil,
|
|
peerNearbyData: nil,
|
|
greetingData: nil,
|
|
pendingUnpinnedAllMessages: false,
|
|
activeGroupCallInfo: nil,
|
|
hasActiveGroupCall: false,
|
|
importState: nil,
|
|
threadData: nil,
|
|
isGeneralThreadClosed: false,
|
|
replyMessage: nil,
|
|
accountPeerColor: nil,
|
|
businessIntro: nil
|
|
)
|
|
|
|
var inputAccessoryItems: [ChatTextInputAccessoryItem] = []
|
|
for inlineAction in component.inlineActions {
|
|
switch inlineAction.kind {
|
|
case .paidMessage:
|
|
inputAccessoryItems.append(.suggestPost)
|
|
case let .inputMode(inputMode):
|
|
let mappedInputMode: ChatTextInputAccessoryItem.InputMode
|
|
switch inputMode {
|
|
case .emoji:
|
|
mappedInputMode = .emoji
|
|
case .stickers:
|
|
mappedInputMode = .stickers
|
|
case .text:
|
|
mappedInputMode = .keyboard
|
|
case .commands:
|
|
mappedInputMode = .bot
|
|
}
|
|
inputAccessoryItems.append(.input(isEnabled: true, inputMode: mappedInputMode))
|
|
}
|
|
}
|
|
presentationInterfaceState = presentationInterfaceState.updatedInputTextPanelState { _ in
|
|
return ChatTextInputPanelState(
|
|
accessoryItems: inputAccessoryItems,
|
|
contextPlaceholder: nil,
|
|
mediaRecordingState: nil
|
|
)
|
|
}
|
|
presentationInterfaceState = presentationInterfaceState.updatedSendPaidMessageStars(component.paidMessagePrice)
|
|
|
|
let panelNode: ChatTextInputPanelNode
|
|
if let current = self.panelNode {
|
|
panelNode = current
|
|
} else {
|
|
panelNode = ChatTextInputPanelNode(
|
|
context: component.context,
|
|
presentationInterfaceState: presentationInterfaceState,
|
|
presentationContext: ChatPresentationContext(
|
|
context: component.context,
|
|
backgroundNode: nil
|
|
),
|
|
presentController: { c in
|
|
|
|
}
|
|
)
|
|
self.panelNode = panelNode
|
|
self.addSubview(panelNode.view)
|
|
panelNode.interfaceInteraction = self.interfaceInteraction
|
|
panelNode.loadTextInputNodeIfNeeded()
|
|
|
|
panelNode.sendMessage = { [weak self] in
|
|
guard let self, let component = self.component else {
|
|
return
|
|
}
|
|
component.sendAction?()
|
|
}
|
|
panelNode.updateHeight = { [weak self] _ in
|
|
guard let self else {
|
|
return
|
|
}
|
|
if !self.isUpdating {
|
|
self.state?.updated(transition: .spring(duration: 0.4))
|
|
}
|
|
}
|
|
panelNode.displayAttachmentMenu = { [weak self] in
|
|
guard let self, let component = self.component else {
|
|
return
|
|
}
|
|
if let leftAction = component.leftAction {
|
|
leftAction.action()
|
|
}
|
|
}
|
|
}
|
|
|
|
if let textView = panelNode.textInputNode?.textView {
|
|
if component.hideKeyboard {
|
|
if textView.inputView == nil {
|
|
textView.inputView = EmptyInputView()
|
|
textView.reloadInputViews()
|
|
}
|
|
} else if textView.inputView != nil {
|
|
textView.inputView = nil
|
|
textView.reloadInputViews()
|
|
}
|
|
}
|
|
|
|
panelNode.customPlaceholder = component.placeholder
|
|
|
|
if let leftAction = component.leftAction {
|
|
switch leftAction.kind {
|
|
case .attach:
|
|
panelNode.customLeftAction = nil
|
|
case let .toggleExpanded(isVisible, isExpanded):
|
|
panelNode.customLeftAction = .toggleExpanded(isVisible: isVisible, isExpanded: isExpanded)
|
|
}
|
|
} else {
|
|
panelNode.customLeftAction = nil
|
|
}
|
|
|
|
if let rightAction = component.rightAction {
|
|
switch rightAction.kind {
|
|
case let .stars(count, isFilled):
|
|
panelNode.customRightAction = .stars(count: count, isFilled: isFilled, action: {
|
|
rightAction.action()
|
|
})
|
|
}
|
|
} else {
|
|
panelNode.customRightAction = nil
|
|
}
|
|
|
|
panelNode.customSendColor = component.sendColor
|
|
panelNode.customInputTextMaxLength = component.maxLength
|
|
|
|
if let resetInputState = component.externalState.resetInputState {
|
|
component.externalState.resetInputState = nil
|
|
let _ = resetInputState
|
|
panelNode.text = ""
|
|
}
|
|
|
|
let panelHeight = panelNode.updateLayout(
|
|
width: availableSize.width,
|
|
leftInset: component.insets.left,
|
|
rightInset: component.insets.right,
|
|
bottomInset: component.insets.bottom,
|
|
additionalSideInsets: UIEdgeInsets(),
|
|
maxHeight: component.maxHeight,
|
|
maxOverlayHeight: component.maxHeight,
|
|
isSecondary: false,
|
|
transition: transition.containedViewLayoutTransition,
|
|
interfaceState: presentationInterfaceState,
|
|
metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact, orientation: nil),
|
|
isMediaInputExpanded: false
|
|
)
|
|
|
|
let panelSize = CGSize(width: availableSize.width, height: panelHeight)
|
|
let panelFrame = CGRect(origin: CGPoint(), size: panelSize)
|
|
|
|
transition.setFrame(view: panelNode.view, frame: panelFrame)
|
|
|
|
return panelSize
|
|
}
|
|
}
|
|
|
|
public func makeView() -> View {
|
|
return View()
|
|
}
|
|
|
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
|
}
|
|
}
|