diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index c87320a73c..4a5779ec1b 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -63,6 +63,11 @@ public protocol AttachmentMediaPickerContext { var selectionCount: Signal { get } var caption: Signal { get } + var loadingProgress: Signal { get } + var mainButtonState: Signal { get } + + func mainButtonAction() + func setCaption(_ caption: NSAttributedString) func send(silently: Bool, mode: AttachmentMediaPickerSendMode) func schedule() @@ -139,6 +144,9 @@ public class AttachmentController: ViewController { private let captionDisposable = MetaDisposable() private let mediaSelectionCountDisposable = MetaDisposable() + private let loadingProgressDisposable = MetaDisposable() + private let mainButtonStateDisposable = MetaDisposable() + private var selectionCount: Int = 0 fileprivate var mediaPickerContext: AttachmentMediaPickerContext? { @@ -156,9 +164,30 @@ public class AttachmentController: ViewController { strongSelf.updateSelectionCount(count) } })) + self.loadingProgressDisposable.set((mediaPickerContext.loadingProgress + |> deliverOnMainQueue).start(next: { [weak self] progress in + if let strongSelf = self { + strongSelf.panel.updateLoadingProgress(progress) + if let layout = strongSelf.validLayout { + print(progress ?? 0) + strongSelf.containerLayoutUpdated(layout, transition: .immediate) + } + } + })) + self.mainButtonStateDisposable.set((mediaPickerContext.mainButtonState + |> deliverOnMainQueue).start(next: { [weak self] mainButtonState in + if let strongSelf = self { + strongSelf.panel.updateMainButtonState(mainButtonState) + if let layout = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring)) + } + } + })) } else { self.updateSelectionCount(0) self.mediaSelectionCountDisposable.set(nil) + self.loadingProgressDisposable.set(nil) + self.mainButtonStateDisposable.set(nil) } } } @@ -254,6 +283,12 @@ public class AttachmentController: ViewController { } } + self.panel.mainButtonPressed = { [weak self] in + if let strongSelf = self { + strongSelf.mediaPickerContext?.mainButtonAction() + } + } + self.panel.requestLayout = { [weak self] in if let strongSelf = self, let layout = strongSelf.validLayout { strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.2, curve: .easeInOut)) @@ -553,13 +588,28 @@ public class AttachmentController: ViewController { self.wrapperNode.view.mask = nil } + var containerInsets = containerLayout.intrinsicInsets + var hasPanel = false + let hasButton = self.panel.isButtonVisible + if let controller = self.controller, controller.buttons.count > 1 { + hasPanel = true + } + let isEffecitvelyCollapsedUpdated = (self.selectionCount > 0) != (self.panel.isSelecting) - let panelHeight = self.panel.update(layout: containerLayout, buttons: self.controller?.buttons ?? [], isSelecting: self.selectionCount > 0, transition: transition) + let panelHeight = self.panel.update(layout: containerLayout, buttons: self.controller?.buttons ?? [], isSelecting: self.selectionCount > 0, transition: !hasPanel && hasButton ? .immediate : transition) + if hasPanel || hasButton { + containerInsets.bottom = panelHeight + } + var panelTransition = transition if isEffecitvelyCollapsedUpdated { panelTransition = .animated(duration: 0.25, curve: .easeInOut) } - panelTransition.updateFrame(node: self.panel, frame: CGRect(origin: CGPoint(x: 0.0, y: containerRect.height - panelHeight), size: CGSize(width: containerRect.width, height: panelHeight))) + var panelY = containerRect.height - panelHeight + if !hasPanel && !hasButton { + panelY = containerRect.height + } + panelTransition.updateFrame(node: self.panel, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY), size: CGSize(width: containerRect.width, height: panelHeight))) var shadowFrame = containerRect.insetBy(dx: -60.0, dy: -60.0) shadowFrame.size.height -= 12.0 @@ -579,22 +629,13 @@ public class AttachmentController: ViewController { let controllers = self.currentControllers containerTransition.updateFrame(node: self.container, frame: CGRect(origin: CGPoint(), size: containerRect.size)) - var containerInsets = containerLayout.intrinsicInsets - var hasPanel = false - if let controller = self.controller, controller.buttons.count > 1 { - hasPanel = true - containerInsets.bottom = panelHeight - } - let containerLayout = containerLayout.withUpdatedIntrinsicInsets(containerInsets) self.container.update(layout: containerLayout, controllers: controllers, coveredByModalTransition: 0.0, transition: self.switchingController ? .immediate : transition) if self.container.supernode == nil, !controllers.isEmpty && self.container.isReady { self.wrapperNode.addSubnode(self.container) - if hasPanel { - self.container.addSubnode(self.panel) - } + self.container.addSubnode(self.panel) self.animateIn() } diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index 9e8e5bdb0d..d02314d6f3 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -284,6 +284,153 @@ private final class AttachButtonComponent: CombinedComponent { } } +private final class LoadingProgressNode: ASDisplayNode { + var color: UIColor { + didSet { + self.foregroundNode.backgroundColor = self.color + } + } + + private let foregroundNode: ASDisplayNode + + init(color: UIColor) { + self.color = color + + self.foregroundNode = ASDisplayNode() + self.foregroundNode.backgroundColor = color + + super.init() + + self.addSubnode(self.foregroundNode) + } + + private var _progress: CGFloat = 0.0 + func updateProgress(_ progress: CGFloat, animated: Bool = false) { + if self._progress == progress && animated { + return + } + + var animated = animated + if (progress < self._progress && animated) { + animated = false + } + + let size = self.bounds.size + + self._progress = progress + + let transition: ContainedViewLayoutTransition + if animated && progress > 0.0 { + transition = .animated(duration: 0.7, curve: .spring) + } else { + transition = .immediate + } + + let alpaTransition: ContainedViewLayoutTransition + if animated { + alpaTransition = .animated(duration: 0.3, curve: .easeInOut) + } else { + alpaTransition = .immediate + } + + transition.updateFrame(node: self.foregroundNode, frame: CGRect(x: -2.0, y: 0.0, width: (size.width + 4.0) * progress, height: size.height)) + + let alpha: CGFloat = progress < 0.001 || progress > 0.999 ? 0.0 : 1.0 + alpaTransition.updateAlpha(node: self.foregroundNode, alpha: alpha) + } + + override func layout() { + super.layout() + + self.foregroundNode.cornerRadius = self.frame.height / 2.0 + } +} + +public struct AttachmentMainButtonState { + let text: String? + let backgroundColor: UIColor + let textColor: UIColor + let isEnabled: Bool + let isVisible: Bool + + public init( + text: String?, + backgroundColor: UIColor, + textColor: UIColor, + isEnabled: Bool, + isVisible: Bool + ) { + self.text = text + self.backgroundColor = backgroundColor + self.textColor = textColor + self.isEnabled = isEnabled + self.isVisible = isVisible + } + + static var initial: AttachmentMainButtonState { + return AttachmentMainButtonState(text: nil, backgroundColor: .clear, textColor: .clear, isEnabled: false, isVisible: false) + } +} + +private final class MainButtonNode: HighlightTrackingButtonNode { + private var state: AttachmentMainButtonState + + private let backgroundNode: ASDisplayNode + private let textNode: ImmediateTextNode + + override init(pointerStyle: PointerStyle? = nil) { + self.state = AttachmentMainButtonState.initial + + self.backgroundNode = ASDisplayNode() + self.backgroundNode.allowsGroupOpacity = true + self.backgroundNode.isUserInteractionEnabled = false + + self.textNode = ImmediateTextNode() + self.textNode.textAlignment = .center + + super.init(pointerStyle: pointerStyle) + + self.addSubnode(self.backgroundNode) + self.backgroundNode.addSubnode(self.textNode) + + self.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self, strongSelf.state.isEnabled { + if highlighted { + strongSelf.backgroundNode.layer.removeAnimation(forKey: "opacity") + strongSelf.backgroundNode.alpha = 0.65 + } else { + strongSelf.backgroundNode.alpha = 1.0 + strongSelf.backgroundNode.layer.animateAlpha(from: 0.65, to: 1.0, duration: 0.2) + } + } + } + } + + func updateLayout(layout: ContainerViewLayout, state: AttachmentMainButtonState, transition: ContainedViewLayoutTransition) -> CGFloat { + self.state = state + + self.isUserInteractionEnabled = state.isVisible + self.isEnabled = state.isEnabled + transition.updateAlpha(node: self, alpha: state.isEnabled ? 1.0 : 0.4) + + let buttonHeight = 50.0 + if let text = state.text { + self.textNode.attributedText = NSAttributedString(string: text, font: Font.semibold(16.0), textColor: state.textColor) + + let textSize = self.textNode.updateLayout(layout.size) + self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - textSize.width) / 2.0), y: floorToScreenPixels((buttonHeight - textSize.height) / 2.0)), size: textSize) + + self.backgroundNode.backgroundColor = state.backgroundColor + } + + let totalButtonHeight = buttonHeight + layout.intrinsicInsets.bottom + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: totalButtonHeight))) + transition.updateSublayerTransformOffset(layer: self.layer, offset: CGPoint(x: 0.0, y: state.isVisible ? 0.0 : totalButtonHeight)) + + return totalButtonHeight + } +} + final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { private let context: AccountContext private var presentationData: PresentationData @@ -301,11 +448,17 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { private var buttonViews: [Int: ComponentHostView] = [:] private var textInputPanelNode: AttachmentTextInputPanelNode? + private var progressNode: LoadingProgressNode? + private var mainButtonNode: MainButtonNode + + private var loadingProgress: CGFloat? + private var mainButtonState: AttachmentMainButtonState = .initial private var buttons: [AttachmentButtonType] = [] private var selectedIndex: Int = 0 private(set) var isSelecting: Bool = false - + private(set) var isButtonVisible: Bool = false + private var validLayout: ContainerViewLayout? private var scrollLayout: (width: CGFloat, contentSize: CGSize)? @@ -317,6 +470,8 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { var present: (ViewController) -> Void = { _ in } var presentInGlobalOverlay: (ViewController) -> Void = { _ in } + var mainButtonPressed: () -> Void = { } + init(context: AccountContext, chatLocation: ChatLocation, updatedPresentationData: (initial: PresentationData, signal: Signal)?) { self.context = context self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } @@ -332,6 +487,8 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { self.separatorNode = ASDisplayNode() self.separatorNode.backgroundColor = self.presentationData.theme.rootController.tabBar.separatorColor + self.mainButtonNode = MainButtonNode() + super.init() self.addSubnode(self.containerNode) @@ -339,6 +496,10 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { self.containerNode.addSubnode(self.separatorNode) self.containerNode.addSubnode(self.scrollNode) + self.addSubnode(self.mainButtonNode) + + self.mainButtonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + self.interfaceInteraction = ChatPanelInterfaceInteraction(setupReplyMessage: { _, _ in }, setupEditMessage: { _, _ in }, beginMessageSelection: { _, _ in @@ -543,6 +704,10 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { self.scrollNode.view.showsVerticalScrollIndicator = false } + @objc private func buttonPressed() { + self.mainButtonPressed() + } + func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) { transition.updateAlpha(node: self.separatorNode, alpha: alpha) transition.updateAlpha(node: self.backgroundNode, alpha: alpha) @@ -554,7 +719,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { } self.updateChatPresentationInterfaceState(animated: false, { $0.updatedInterfaceState { $0.withUpdatedComposeInputState(ChatTextInputState(inputText: caption))} }) } - + private func updateChatPresentationInterfaceState(animated: Bool = true, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState, completion: @escaping (ContainedViewLayoutTransition) -> Void = { _ in }) { self.updateChatPresentationInterfaceState(transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate, f, completion: completion) } @@ -669,7 +834,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { } self.scrollLayout = (layout.size.width, contentSize) - transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.isSelecting ? -buttonSize.height : 0.0), size: CGSize(width: layout.size.width, height: buttonSize.height))) + transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.isSelecting || self.isButtonVisible ? -buttonSize.height : 0.0), size: CGSize(width: layout.size.width, height: buttonSize.height))) self.scrollNode.view.contentSize = contentSize return true @@ -707,10 +872,25 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { } } + func updateLoadingProgress(_ progress: CGFloat?) { + self.loadingProgress = progress + } + + func updateMainButtonState(_ mainButtonState: AttachmentMainButtonState?) { + var currentButtonState = self.mainButtonState + if mainButtonState == nil { + currentButtonState = AttachmentMainButtonState(text: currentButtonState.text, backgroundColor: currentButtonState.backgroundColor, textColor: currentButtonState.textColor, isEnabled: currentButtonState.isEnabled, isVisible: false) + } + self.mainButtonState = mainButtonState ?? currentButtonState + } + func update(layout: ContainerViewLayout, buttons: [AttachmentButtonType], isSelecting: Bool, transition: ContainedViewLayoutTransition) -> CGFloat { self.validLayout = layout self.buttons = buttons + let isButtonVisibleUpdated = self.isButtonVisible != self.mainButtonState.isVisible + self.isButtonVisible = self.mainButtonState.isVisible + let isSelectingUpdated = self.isSelecting != isSelecting self.isSelecting = isSelecting @@ -723,6 +903,8 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { insets.bottom = layout.intrinsicInsets.bottom } + let isButtonVisible = self.mainButtonState.isVisible + if isSelecting { self.loadTextNodeIfNeeded() } else { @@ -758,12 +940,12 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { containerFrame = bounds } let containerBounds = CGRect(origin: CGPoint(), size: containerFrame.size) - if isSelectingUpdated { + if isSelectingUpdated || isButtonVisibleUpdated { containerTransition = .animated(duration: 0.25, curve: .easeInOut) } else { containerTransition = transition } - containerTransition.updateAlpha(node: self.scrollNode, alpha: isSelecting ? 0.0 : 1.0) + containerTransition.updateAlpha(node: self.scrollNode, alpha: isSelecting || isButtonVisible ? 0.0 : 1.0) if isSelectingUpdated { if isSelecting { @@ -788,10 +970,33 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { self.backgroundNode.update(size: containerBounds.size, transition: transition) containerTransition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: UIScreenPixel))) - let _ = self.updateScrollLayoutIfNeeded(force: isSelectingUpdated, transition: containerTransition) + let _ = self.updateScrollLayoutIfNeeded(force: isSelectingUpdated || isButtonVisibleUpdated, transition: containerTransition) self.updateViews(transition: .immediate) + if let progress = self.loadingProgress { + let loadingProgressNode: LoadingProgressNode + if let current = self.progressNode { + loadingProgressNode = current + } else { + loadingProgressNode = LoadingProgressNode(color: self.presentationData.theme.rootController.tabBar.selectedIconColor) + self.addSubnode(loadingProgressNode) + self.progressNode = loadingProgressNode + } + let loadingProgressHeight: CGFloat = 2.0 + transition.updateFrame(node: loadingProgressNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: loadingProgressHeight))) + + loadingProgressNode.updateProgress(progress, animated: true) + } else if let progressNode = self.progressNode { + self.progressNode = nil + progressNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak progressNode] _ in + progressNode?.removeFromSupernode() + }) + } + + let mainButtonHeight = self.mainButtonNode.updateLayout(layout: layout, state: self.mainButtonState, transition: transition) + transition.updateFrame(node: self.mainButtonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: mainButtonHeight))) + return containerFrame.height } diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift index 3c8f176ea4..1ff32a749f 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift @@ -95,6 +95,14 @@ public class LegacyAssetPickerContext: AttachmentMediaPickerContext { } } } + + public var loadingProgress: Signal { + return .single(nil) + } + + public var mainButtonState: Signal { + return .single(nil) + } public init(controller: TGMediaAssetsController) { self.controller = controller @@ -111,6 +119,10 @@ public class LegacyAssetPickerContext: AttachmentMediaPickerContext { public func schedule() { self.controller?.schedule(false) } + + public func mainButtonAction() { + + } } public func legacyAssetPicker(context: AccountContext, presentationData: PresentationData, editingMedia: Bool, fileMode: Bool, peer: Peer?, saveEditedPhotos: Bool, allowGrouping: Bool, selectionLimit: Int) -> Signal<(LegacyComponentsContext) -> TGMediaAssetsController, Void> { diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index f0a0a43c58..e5905fad80 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -1473,6 +1473,14 @@ final class MediaPickerContext: AttachmentMediaPickerContext { } } + public var loadingProgress: Signal { + return .single(nil) + } + + public var mainButtonState: Signal { + return .single(nil) + } + init(interaction: MediaPickerInteraction) { self.interaction = interaction } @@ -1488,6 +1496,10 @@ final class MediaPickerContext: AttachmentMediaPickerContext { func schedule() { self.interaction?.schedule() } + + func mainButtonAction() { + + } } private final class MediaPickerContextReferenceContentSource: ContextReferenceContentSource { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 87ad3a83ef..cc5e42395f 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -10904,7 +10904,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() } } - completion(controller, nil) + completion(controller, controller.mediaPickerContext) strongSelf.controllerNavigationDisposable.set(nil) default: break diff --git a/submodules/TelegramUI/Sources/ContactSelectionController.swift b/submodules/TelegramUI/Sources/ContactSelectionController.swift index 14d770c89f..069849b9ac 100644 --- a/submodules/TelegramUI/Sources/ContactSelectionController.swift +++ b/submodules/TelegramUI/Sources/ContactSelectionController.swift @@ -426,6 +426,14 @@ final class ContactsPickerContext: AttachmentMediaPickerContext { var caption: Signal { return .single(nil) } + + public var loadingProgress: Signal { + return .single(nil) + } + + public var mainButtonState: Signal { + return .single(nil) + } init(controller: ContactSelectionControllerImpl) { self.controller = controller @@ -444,4 +452,7 @@ final class ContactsPickerContext: AttachmentMediaPickerContext { self.controller?.contactsNode.requestMultipleAction?(false, time) }) } + + func mainButtonAction() { + } } diff --git a/submodules/WebSearchUI/Sources/WebSearchController.swift b/submodules/WebSearchUI/Sources/WebSearchController.swift index 257188e1da..84d09bd01a 100644 --- a/submodules/WebSearchUI/Sources/WebSearchController.swift +++ b/submodules/WebSearchUI/Sources/WebSearchController.swift @@ -593,6 +593,14 @@ public class WebSearchPickerContext: AttachmentMediaPickerContext { } } + public var loadingProgress: Signal { + return .single(nil) + } + + public var mainButtonState: Signal { + return .single(nil) + } + init(interaction: WebSearchControllerInteraction) { self.interaction = interaction } @@ -608,4 +616,8 @@ public class WebSearchPickerContext: AttachmentMediaPickerContext { public func schedule() { self.interaction?.schedule() } + + public func mainButtonAction() { + + } } diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 62538ca73a..1116bd367b 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -33,134 +33,6 @@ public func generateWebAppThemeParams(_ presentationTheme: PresentationTheme) -> ] } -private final class LoadingProgressNode: ASDisplayNode { - var color: UIColor { - didSet { - self.foregroundNode.backgroundColor = self.color - } - } - - private let foregroundNode: ASDisplayNode - - init(color: UIColor) { - self.color = color - - self.foregroundNode = ASDisplayNode() - self.foregroundNode.backgroundColor = color - - super.init() - - self.addSubnode(self.foregroundNode) - } - - private var _progress: CGFloat = 0.0 - func updateProgress(_ progress: CGFloat, animated: Bool = false) { - if self._progress == progress && animated { - return - } - - var animated = animated - if (progress < self._progress && animated) { - animated = false - } - - let size = self.bounds.size - - self._progress = progress - - let transition: ContainedViewLayoutTransition - if animated && progress > 0.0 { - transition = .animated(duration: 0.7, curve: .spring) - } else { - transition = .immediate - } - - let alpaTransition: ContainedViewLayoutTransition - if animated { - alpaTransition = .animated(duration: 0.3, curve: .easeInOut) - } else { - alpaTransition = .immediate - } - - transition.updateFrame(node: self.foregroundNode, frame: CGRect(x: -2.0, y: 0.0, width: (size.width + 4.0) * progress, height: size.height)) - - let alpha: CGFloat = progress < 0.001 || progress > 0.999 ? 0.0 : 1.0 - alpaTransition.updateAlpha(node: self.foregroundNode, alpha: alpha) - } - - override func layout() { - super.layout() - - self.foregroundNode.cornerRadius = self.frame.height / 2.0 - } -} - -private final class MainButtonNode: HighlightTrackingButtonNode { - struct State { - let text: String? - let backgroundColor: UIColor - let textColor: UIColor - let isEnabled: Bool - let isVisible: Bool - } - private var state: State - - private let backgroundNode: ASDisplayNode - private let textNode: ImmediateTextNode - - override init(pointerStyle: PointerStyle? = nil) { - self.state = State(text: nil, backgroundColor: .clear, textColor: .clear, isEnabled: false, isVisible: false) - - self.backgroundNode = ASDisplayNode() - self.backgroundNode.allowsGroupOpacity = true - self.backgroundNode.isUserInteractionEnabled = false - - self.textNode = ImmediateTextNode() - self.textNode.textAlignment = .center - - super.init(pointerStyle: pointerStyle) - - self.addSubnode(self.backgroundNode) - self.backgroundNode.addSubnode(self.titleNode) - - self.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self, strongSelf.state.isEnabled { - if highlighted { - strongSelf.backgroundNode.layer.removeAnimation(forKey: "opacity") - strongSelf.backgroundNode.alpha = 0.65 - } else { - strongSelf.backgroundNode.alpha = 1.0 - strongSelf.backgroundNode.layer.animateAlpha(from: 0.65, to: 1.0, duration: 0.2) - } - } - } - } - - func updateLayout(layout: ContainerViewLayout, state: State, transition: ContainedViewLayoutTransition) -> CGFloat { - self.state = state - - self.isUserInteractionEnabled = state.isVisible - self.isEnabled = state.isEnabled - transition.updateAlpha(node: self, alpha: state.isEnabled ? 1.0 : 0.4) - - let buttonHeight = 50.0 - if let text = state.text { - self.textNode.attributedText = NSAttributedString(string: text, font: Font.semibold(16.0), textColor: state.textColor) - - let textSize = self.textNode.updateLayout(layout.size) - self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - textSize.width) / 2.0), y: floorToScreenPixels((buttonHeight - textSize.height) / 2.0)), size: textSize) - - self.backgroundNode.backgroundColor = state.backgroundColor - } - - let totalButtonHeight = buttonHeight + layout.intrinsicInsets.bottom - transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: totalButtonHeight))) - transition.updateSublayerTransformOffset(layer: self.layer, offset: CGPoint(x: 0.0, y: state.isVisible ? 0.0 : totalButtonHeight)) - - return totalButtonHeight - } -} - public final class WebAppController: ViewController, AttachmentContainable { public var requestAttachmentMenuExpansion: () -> Void = { } public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } @@ -168,16 +40,15 @@ public final class WebAppController: ViewController, AttachmentContainable { public var cancelPanGesture: () -> Void = { } public var isContainerPanning: () -> Bool = { return false } - private class Node: ViewControllerTracingNode, WKNavigationDelegate, WKUIDelegate, UIScrollViewDelegate { + fileprivate class Node: ViewControllerTracingNode, WKNavigationDelegate, WKUIDelegate, UIScrollViewDelegate { private weak var controller: WebAppController? fileprivate var webView: WebAppWebView? - private var placeholderIcon: UIImage? private var placeholderNode: ShimmerEffectNode? - private let loadingProgressNode: LoadingProgressNode - private let mainButtonNode: MainButtonNode - private var mainButtonState: MainButtonNode.State? + + fileprivate let loadingProgressPromise = Promise(nil) + fileprivate let mainButtonStatePromise = Promise(nil) private let context: AccountContext var presentationData: PresentationData @@ -193,9 +64,6 @@ public final class WebAppController: ViewController, AttachmentContainable { self.presentationData = controller.presentationData self.present = present - self.loadingProgressNode = LoadingProgressNode(color: presentationData.theme.rootController.tabBar.selectedIconColor) - self.mainButtonNode = MainButtonNode() - super.init() if self.presentationData.theme.list.plainBackgroundColor.rgb == 0x000000 { @@ -219,9 +87,7 @@ public final class WebAppController: ViewController, AttachmentContainable { let placeholderNode = ShimmerEffectNode() self.addSubnode(placeholderNode) self.placeholderNode = placeholderNode - self.addSubnode(self.loadingProgressNode) - self.addSubnode(self.mainButtonNode) - + if let iconFile = controller.iconFile { let _ = freeMediaFileInteractiveFetched(account: self.context.account, fileReference: .standalone(media: iconFile)).start() self.iconDisposable = (svgIconImageFile(account: self.context.account, fileReference: .standalone(media: iconFile)) @@ -299,6 +165,10 @@ public final class WebAppController: ViewController, AttachmentContainable { self.view.addSubview(webView) } + @objc fileprivate func mainButtonPressed() { + self.webView?.sendEvent(name: "main_button_pressed", data: nil) + } + private func updatePlaceholder() { guard let image = self.placeholderIcon else { return @@ -360,7 +230,7 @@ public final class WebAppController: ViewController, AttachmentContainable { private var validLayout: (ContainerViewLayout, CGFloat)? func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { - let previous = self.validLayout?.0 + let previousLayout = self.validLayout?.0 self.validLayout = (layout, navigationBarHeight) if let webView = self.webView { @@ -384,24 +254,16 @@ public final class WebAppController: ViewController, AttachmentContainable { let placeholderFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - iconSize.width) / 2.0), y: floorToScreenPixels((height - iconSize.height) / 2.0)), size: iconSize) transition.updateFrame(node: placeholderNode, frame: placeholderFrame) placeholderNode.updateAbsoluteRect(placeholderFrame, within: layout.size) - - let loadingProgressHeight: CGFloat = 2.0 - transition.updateFrame(node: self.loadingProgressNode, frame: CGRect(origin: CGPoint(x: 0.0, y: height - loadingProgressHeight), size: CGSize(width: layout.size.width, height: loadingProgressHeight))) } - if let previous = previous, (previous.inputHeight ?? 0.0).isZero, let inputHeight = layout.inputHeight, inputHeight > 44.0 { + if let previousLayout = previousLayout, (previousLayout.inputHeight ?? 0.0).isZero, let inputHeight = layout.inputHeight, inputHeight > 44.0 { self.controller?.requestAttachmentMenuExpansion() } - - if let mainButtonState = self.mainButtonState { - let mainButtonHeight = self.mainButtonNode.updateLayout(layout: layout, state: mainButtonState, transition: transition) - transition.updateFrame(node: self.mainButtonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - mainButtonHeight - layout.additionalInsets.bottom), size: CGSize(width: layout.size.width, height: mainButtonHeight))) - } } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if keyPath == "estimatedProgress", let webView = self.webView { - self.loadingProgressNode.updateProgress(webView.estimatedProgress, animated: true) + self.loadingProgressPromise.set(.single(CGFloat(webView.estimatedProgress))) } } @@ -420,12 +282,15 @@ public final class WebAppController: ViewController, AttachmentContainable { self.handleSendData(data: eventData) } case "web_app_setup_main_button": - if let eventData = body["eventData"] as? String { - print(eventData) - -// self.mainButtonState = MainButtonNode.State(text: <#T##String?#>, backgroundColor: <#T##UIColor#>, textColor: <#T##UIColor#>, isEnabled: <#T##Bool#>, isVisible: <#T##Bool#>) - if let (layout, navigationBarHeight) = self.validLayout { - self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.2, curve: .easeInOut)) + if let eventData = (body["eventData"] as? String)?.data(using: .utf8), let json = try? JSONSerialization.jsonObject(with: eventData, options: []) as? [String: Any] { + if let backgroundColorString = json["color"] as? String, let backgroundColor = UIColor(hexString: backgroundColorString), + let textColorString = json["text_color"] as? String, let textColor = UIColor(hexString: textColorString), + let text = json["text"] as? String, + let isEnabled = json["is_active"] as? Bool, + let isVisible = json["is_visible"] as? Bool + { + let state = AttachmentMainButtonState(text: text, backgroundColor: backgroundColor, textColor: textColor, isEnabled: isEnabled, isVisible: isVisible) + self.mainButtonStatePromise.set(.single(state)) } } case "web_app_close": @@ -482,7 +347,7 @@ public final class WebAppController: ViewController, AttachmentContainable { } } - private var controllerNode: Node { + fileprivate var controllerNode: Node { return self.displayNode as! Node } @@ -660,8 +525,50 @@ public final class WebAppController: ViewController, AttachmentContainable { } set(value) { } } + + public var mediaPickerContext: AttachmentMediaPickerContext? { + return WebAppPickerContext(controller: self) + } } +final class WebAppPickerContext: AttachmentMediaPickerContext { + private weak var controller: WebAppController? + + var selectionCount: Signal { + return .single(0) + } + + var caption: Signal { + return .single(nil) + } + + public var loadingProgress: Signal { + return self.controller?.controllerNode.loadingProgressPromise.get() ?? .single(nil) + } + + public var mainButtonState: Signal { + return self.controller?.controllerNode.mainButtonStatePromise.get() ?? .single(nil) + } + + init(controller: WebAppController) { + self.controller = controller + } + + func setCaption(_ caption: NSAttributedString) { + } + + func send(silently: Bool, mode: AttachmentMediaPickerSendMode) { + } + + func schedule() { + } + + func mainButtonAction() { + self.controller?.controllerNode.mainButtonPressed() + } +} + + private final class WebAppContextReferenceContentSource: ContextReferenceContentSource { private let controller: ViewController private let sourceNode: ContextReferenceContentNode @@ -682,7 +589,7 @@ public func standaloneWebAppController(context: AccountContext, updatedPresentat let webAppController = WebAppController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, botId: botId, botName: botName, url: url, queryId: queryId, buttonText: buttonText, keepAliveSignal: keepAliveSignal, replyToMessageId: nil, iconFile: nil) webAppController.openUrl = openUrl webAppController.completion = completion - present(webAppController, nil) + present(webAppController, webAppController.mediaPickerContext) } return controller } diff --git a/submodules/WebUI/Sources/WebAppWebView.swift b/submodules/WebUI/Sources/WebAppWebView.swift index e1c9b0fd94..e5801fa41f 100644 --- a/submodules/WebUI/Sources/WebAppWebView.swift +++ b/submodules/WebUI/Sources/WebAppWebView.swift @@ -97,8 +97,8 @@ final class WebAppWebView: WKWebView { } } - func sendEvent(name: String, data: String) { - let script = "window.TelegramGameProxy.receiveEvent(\"\(name)\", \(data))" + func sendEvent(name: String, data: String?) { + let script = "window.TelegramGameProxy.receiveEvent(\"\(name)\", \(data ?? "null"))" self.evaluateJavaScript(script, completionHandler: { _, _ in }) }