import Foundation import UIKit import Display import ComponentFlow import SwiftSignalKit import TelegramCore import AvatarNode import AccountContext import MessageInputPanelComponent import BundleIconComponent private final class AvatarComponent: Component { let context: AccountContext let peer: EnginePeer init(context: AccountContext, peer: EnginePeer) { self.context = context self.peer = peer } static func ==(lhs: AvatarComponent, rhs: AvatarComponent) -> Bool { if lhs.context !== rhs.context { return false } if lhs.peer != rhs.peer { return false } return true } final class View: UIView { private let avatarNode: AvatarNode private var component: AvatarComponent? private weak var state: EmptyComponentState? override init(frame: CGRect) { self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 18.0)) super.init(frame: frame) self.addSubnode(self.avatarNode) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func update(component: AvatarComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { self.component = component self.state = state let size = CGSize(width: 32.0, height: 32.0) self.avatarNode.frame = CGRect(origin: CGPoint(), size: size) self.avatarNode.setPeer( context: component.context, theme: component.context.sharedContext.currentPresentationData.with({ $0 }).theme, peer: component.peer, synchronousLoad: true ) return size } } func makeView() -> View { return View(frame: CGRect()) } func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } } final class StoryPreviewComponent: Component { typealias EnvironmentType = Empty let context: AccountContext let caption: String init( context: AccountContext, caption: String ) { self.context = context self.caption = caption } static func ==(lhs: StoryPreviewComponent, rhs: StoryPreviewComponent) -> Bool { if lhs.context !== rhs.context { return false } if lhs.caption != rhs.caption { return false } return true } final class State: ComponentState { private let context: AccountContext private var peerDisposable: Disposable? fileprivate var accountPeer: EnginePeer? init(context: AccountContext) { self.context = context super.init() self.peerDisposable = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) |> deliverOnMainQueue).start(next: { [weak self] peer in if let self { self.accountPeer = peer self.updated() } }) } deinit { self.peerDisposable?.dispose() } } func makeState() -> State { return State( context: self.context ) } public final class View: UIView { private let line = ComponentView() private let title = ComponentView() private let avatar = ComponentView() private let cancelButton = ComponentView() private let inputPanel = ComponentView() private let inputPanelExternalState = MessageInputPanelComponent.ExternalState() private let scrubber = ComponentView() private var component: StoryPreviewComponent? private weak var state: State? override init(frame: CGRect) { super.init(frame: frame) self.backgroundColor = .clear } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func update(component: StoryPreviewComponent, availableSize: CGSize, state: State, environment: Environment, transition: ComponentTransition) -> CGSize { self.component = component self.state = state let lineSize = self.line.update( transition: transition, component: AnyComponent(Rectangle(color: UIColor(white: 1.0, alpha: 0.5))), environment: {}, containerSize: CGSize(width: availableSize.width - 8.0 * 2.0, height: 2.0) ) let lineFrame = CGRect( origin: CGPoint(x: 8.0, y: 8.0), size: lineSize ) if let lineView = self.line.view { if lineView.superview == nil { lineView.layer.cornerRadius = 1.0 self.addSubview(lineView) } transition.setPosition(view: lineView, position: lineFrame.center) transition.setBounds(view: lineView, bounds: CGRect(origin: .zero, size: lineFrame.size)) } let cancelButtonSize = self.cancelButton.update( transition: transition, component: AnyComponent(BundleIconComponent( name: "Stories/Close", tintColor: UIColor.white )), environment: {}, containerSize: CGSize(width: 44.0, height: 44.0) ) let cancelButtonFrame = CGRect( origin: CGPoint(x: availableSize.width - 40.0, y: 19.0), size: cancelButtonSize ) if let cancelButtonView = self.cancelButton.view { if cancelButtonView.superview == nil { self.addSubview(cancelButtonView) } transition.setPosition(view: cancelButtonView, position: cancelButtonFrame.center) transition.setBounds(view: cancelButtonView, bounds: CGRect(origin: .zero, size: cancelButtonFrame.size)) } if let accountPeer = state.accountPeer { let avatarSize = self.avatar.update( transition: transition, component: AnyComponent(AvatarComponent( context: component.context, peer: accountPeer )), environment: {}, containerSize: CGSize(width: 32.0, height: 32.0) ) let avatarFrame = CGRect( origin: CGPoint(x: 12.0, y: 18.0), size: avatarSize ) if let avatarView = self.avatar.view { if avatarView.superview == nil { self.addSubview(avatarView) } transition.setPosition(view: avatarView, position: avatarFrame.center) transition.setBounds(view: avatarView, bounds: CGRect(origin: .zero, size: avatarFrame.size)) } } let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } let titleSize = self.title.update( transition: transition, component: AnyComponent(Text( text: presentationData.strings.Story_HeaderYourStory, font: Font.medium(14.0), color: .white )), environment: {}, containerSize: CGSize(width: 180.0, height: 44.0) ) let titleFrame = CGRect( origin: CGPoint(x: 53.0, y: floorToScreenPixels(33.0 - titleSize.height / 2.0)), size: titleSize ) if let titleView = self.title.view { if titleView.superview == nil { self.addSubview(titleView) } transition.setPosition(view: titleView, position: titleFrame.center) transition.setBounds(view: titleView, bounds: CGRect(origin: .zero, size: titleFrame.size)) } let inputPanelSize = self.inputPanel.update( transition: transition, component: AnyComponent(MessageInputPanelComponent( externalState: self.inputPanelExternalState, context: component.context, theme: presentationData.theme, strings: presentationData.strings, style: .story, placeholder: .plain(presentationData.strings.Story_InputPlaceholderReplyPrivately), sendPaidMessageStars: nil, maxLength: nil, queryTypes: [], alwaysDarkWhenHasText: false, resetInputContents: nil, nextInputMode: { _ in return .stickers }, areVoiceMessagesAvailable: false, presentController: { _ in }, presentInGlobalOverlay: { _ in }, sendMessageAction: { }, sendMessageOptionsAction: nil, sendStickerAction: { _ in }, setMediaRecordingActive: { _, _, _, _ in }, lockMediaRecording: nil, stopAndPreviewMediaRecording: nil, discardMediaRecordingPreview: nil, attachmentAction: { }, attachmentButtonMode: .attach, myReaction: nil, likeAction: nil, likeOptionsAction: nil, inputModeAction: nil, timeoutAction: nil, forwardAction: {}, moreAction: { _, _ in }, presentCaptionPositionTooltip: nil, presentVoiceMessagesUnavailableTooltip: nil, presentTextLengthLimitTooltip: nil, presentTextFormattingTooltip: nil, paste: { _ in }, audioRecorder: nil, videoRecordingStatus: nil, isRecordingLocked: false, hasRecordedVideo: false, recordedAudioPreview: nil, hasRecordedVideoPreview: false, wasRecordingDismissed: false, timeoutValue: nil, timeoutSelected: false, displayGradient: false, bottomInset: 0.0, isFormattingLocked: false, hideKeyboard: false, customInputView: nil, forceIsEditing: false, disabledPlaceholder: nil, header: nil, isChannel: false, storyItem: nil, chatLocation: nil )), environment: {}, containerSize: CGSize(width: availableSize.width, height: 200.0) ) let inputPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - inputPanelSize.height - 3.0), size: inputPanelSize) if let inputPanelView = self.inputPanel.view { if inputPanelView.superview == nil { self.addSubview(inputPanelView) } transition.setFrame(view: inputPanelView, frame: inputPanelFrame) } return availableSize } } func makeView() -> View { return View() } public func update(view: View, availableSize: CGSize, state: State, environment: Environment, transition: ComponentTransition) -> CGSize { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } }