import Foundation import UIKit import Photos import Display import AsyncDisplayKit import SwiftSignalKit import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences import PresentationDataUtils import AccountContext import ComponentFlow import ViewControllerComponent import MultilineTextComponent import BalancedTextComponent import BackButtonComponent import ListSectionComponent import ListActionItemComponent import BundleIconComponent final class BusinessSetupScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext init( context: AccountContext ) { self.context = context } static func ==(lhs: BusinessSetupScreenComponent, rhs: BusinessSetupScreenComponent) -> Bool { if lhs.context !== rhs.context { return false } return true } private final class ScrollView: UIScrollView { override func touchesShouldCancel(in view: UIView) -> Bool { return true } } final class View: UIView, UIScrollViewDelegate { private let topOverscrollLayer = SimpleLayer() private let scrollView: ScrollView private let navigationTitle = ComponentView() private let title = ComponentView() private let subtitle = ComponentView() private let actionsSection = ComponentView() private var isUpdating: Bool = false private var component: BusinessSetupScreenComponent? private(set) weak var state: EmptyComponentState? private var environment: EnvironmentType? override init(frame: CGRect) { self.scrollView = ScrollView() self.scrollView.showsVerticalScrollIndicator = true self.scrollView.showsHorizontalScrollIndicator = false self.scrollView.scrollsToTop = false self.scrollView.delaysContentTouches = false self.scrollView.canCancelContentTouches = true self.scrollView.contentInsetAdjustmentBehavior = .never if #available(iOS 13.0, *) { self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false } self.scrollView.alwaysBounceVertical = true super.init(frame: frame) self.scrollView.delegate = self self.addSubview(self.scrollView) self.scrollView.layer.addSublayer(self.topOverscrollLayer) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } deinit { } func scrollToTop() { self.scrollView.setContentOffset(CGPoint(), animated: true) } func attemptNavigation(complete: @escaping () -> Void) -> Bool { return true } func scrollViewDidScroll(_ scrollView: UIScrollView) { self.updateScrolling(transition: .immediate) } var scrolledUp = true private func updateScrolling(transition: Transition) { guard let environment = self.environment else { return } let navigationRevealOffsetY: CGFloat = -(environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) * 0.5) + (self.title.view?.frame.midY ?? 0.0) let navigationAlphaDistance: CGFloat = 16.0 let navigationAlpha: CGFloat = max(0.0, min(1.0, (self.scrollView.contentOffset.y - navigationRevealOffsetY) / navigationAlphaDistance)) if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar { transition.setAlpha(layer: navigationBar.backgroundNode.layer, alpha: navigationAlpha) transition.setAlpha(layer: navigationBar.stripeNode.layer, alpha: navigationAlpha) } var scrolledUp = false if navigationAlpha < 0.5 { scrolledUp = true } else if navigationAlpha > 0.5 { scrolledUp = false } if self.scrolledUp != scrolledUp { self.scrolledUp = scrolledUp if !self.isUpdating { self.state?.updated() } } if let navigationTitleView = self.navigationTitle.view { transition.setAlpha(view: navigationTitleView, alpha: navigationAlpha) } } func update(component: BusinessSetupScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { self.isUpdating = true defer { self.isUpdating = false } let environment = environment[EnvironmentType.self].value let themeUpdated = self.environment?.theme !== environment.theme self.environment = environment self.component = component self.state = state if themeUpdated { self.backgroundColor = environment.theme.list.blocksBackgroundColor } let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } //TODO:localize let navigationTitleSize = self.navigationTitle.update( transition: transition, component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString(string: "Telegram Business", font: Font.semibold(17.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)), horizontalAlignment: .center )), environment: {}, containerSize: CGSize(width: availableSize.width, height: 100.0) ) let navigationTitleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - navigationTitleSize.width) / 2.0), y: environment.statusBarHeight + floor((environment.navigationHeight - environment.statusBarHeight - navigationTitleSize.height) / 2.0)), size: navigationTitleSize) if let navigationTitleView = self.navigationTitle.view { if navigationTitleView.superview == nil { if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar { navigationBar.view.addSubview(navigationTitleView) } } transition.setFrame(view: navigationTitleView, frame: navigationTitleFrame) } let bottomContentInset: CGFloat = 24.0 let sideInset: CGFloat = 16.0 + environment.safeInsets.left let sectionSpacing: CGFloat = 32.0 let _ = bottomContentInset let _ = sectionSpacing var contentHeight: CGFloat = 0.0 contentHeight += environment.navigationHeight contentHeight += 16.0 //TODO:localize let titleSize = self.title.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString(string: "Telegram Business", font: Font.bold(29.0), textColor: environment.theme.list.itemPrimaryTextColor)), horizontalAlignment: .center, maximumNumberOfLines: 1 )), environment: {}, containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) ) let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: contentHeight), size: titleSize) if let titleView = self.title.view { if titleView.superview == nil { self.scrollView.addSubview(titleView) } transition.setPosition(view: titleView, position: titleFrame.center) titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size) } contentHeight += titleSize.height contentHeight += 17.0 //TODO:localize let subtitleSize = self.subtitle.update( transition: .immediate, component: AnyComponent(BalancedTextComponent( text: .plain(NSAttributedString(string: "You have now unlocked these additional business features.", font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor)), horizontalAlignment: .center, maximumNumberOfLines: 0, lineSpacing: 0.25 )), environment: {}, containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) ) let subtitleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - subtitleSize.width) * 0.5), y: contentHeight), size: subtitleSize) if let subtitleView = self.subtitle.view { if subtitleView.superview == nil { self.scrollView.addSubview(subtitleView) } transition.setPosition(view: subtitleView, position: subtitleFrame.center) subtitleView.bounds = CGRect(origin: CGPoint(), size: subtitleFrame.size) } contentHeight += subtitleSize.height contentHeight += 21.0 struct Item { var icon: String var title: String var subtitle: String var action: () -> Void } var items: [Item] = [] //TODO:localize items.append(Item( icon: "Settings/Menu/AddAccount", title: "Location", subtitle: "Display the location of your business on your account.", action: { [weak self] in guard let self else { return } self.environment?.controller()?.push(component.context.sharedContext.makeBusinessLocationSetupScreen(context: component.context)) } )) items.append(Item( icon: "Settings/Menu/DataVoice", title: "Opening Hours", subtitle: "Show to your customers when you are open for business.", action: { [weak self] in guard let self else { return } self.environment?.controller()?.push(component.context.sharedContext.makeBusinessHoursSetupScreen(context: component.context)) } )) items.append(Item( icon: "Settings/Menu/Photos", title: "Quick Replies", subtitle: "Set up shortcuts with rich text and media to respond to messages faster.", action: { [weak self] in guard let self, let component = self.component, let environment = self.environment else { return } environment.controller()?.push(component.context.sharedContext.makeQuickReplySetupScreen(context: component.context)) } )) items.append(Item( icon: "Settings/Menu/Stories", title: "Greeting Messages", subtitle: "Create greetings that will be automatically sent to new customers.", action: { [weak self] in guard let self, let component = self.component, let environment = self.environment else { return } environment.controller()?.push(component.context.sharedContext.makeGreetingMessageSetupScreen(context: component.context, isAwayMode: false)) } )) items.append(Item( icon: "Settings/Menu/Trending", title: "Away Messages", subtitle: "Define messages that are automatically sent when you are off.", action: { [weak self] in guard let self, let component = self.component, let environment = self.environment else { return } environment.controller()?.push(component.context.sharedContext.makeGreetingMessageSetupScreen(context: component.context, isAwayMode: true)) } )) items.append(Item( icon: "Settings/Menu/DataStickers", title: "Chatbots", subtitle: "Add any third-party chatbots that will process customer interactions.", action: { [weak self] in guard let self, let component = self.component, let environment = self.environment else { return } environment.controller()?.push(component.context.sharedContext.makeChatbotSetupScreen(context: component.context)) } )) var actionsSectionItems: [AnyComponentWithIdentity] = [] for item in items { actionsSectionItems.append(AnyComponentWithIdentity(id: actionsSectionItems.count, component: AnyComponent(ListActionItemComponent( theme: environment.theme, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: item.title, font: Font.regular(presentationData.listsFontSize.baseDisplaySize), textColor: environment.theme.list.itemPrimaryTextColor )), maximumNumberOfLines: 0 ))), AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: item.subtitle, font: Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0)), textColor: environment.theme.list.itemSecondaryTextColor )), maximumNumberOfLines: 0, lineSpacing: 0.18 ))) ], alignment: .left, spacing: 2.0)), leftIcon: AnyComponentWithIdentity(id: 0, component: AnyComponent(BundleIconComponent( name: item.icon, tintColor: nil ))), action: { _ in item.action() } )))) } let actionsSectionSize = self.actionsSection.update( transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, header: nil, footer: nil, items: actionsSectionItems )), environment: {}, containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) ) let actionsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: actionsSectionSize) if let actionsSectionView = self.actionsSection.view { if actionsSectionView.superview == nil { self.scrollView.addSubview(actionsSectionView) } transition.setFrame(view: actionsSectionView, frame: actionsSectionFrame) } contentHeight += actionsSectionSize.height contentHeight += bottomContentInset contentHeight += environment.safeInsets.bottom let previousBounds = self.scrollView.bounds let contentSize = CGSize(width: availableSize.width, height: contentHeight) if self.scrollView.frame != CGRect(origin: CGPoint(), size: availableSize) { self.scrollView.frame = CGRect(origin: CGPoint(), size: availableSize) } if self.scrollView.contentSize != contentSize { self.scrollView.contentSize = contentSize } let scrollInsets = UIEdgeInsets(top: environment.navigationHeight, left: 0.0, bottom: 0.0, right: 0.0) if self.scrollView.scrollIndicatorInsets != scrollInsets { self.scrollView.scrollIndicatorInsets = scrollInsets } if !previousBounds.isEmpty, !transition.animation.isImmediate { let bounds = self.scrollView.bounds if bounds.maxY != previousBounds.maxY { let offsetY = previousBounds.maxY - bounds.maxY transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: offsetY), to: CGPoint(), additive: true) } } self.topOverscrollLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: -3000.0), size: CGSize(width: availableSize.width, height: 3000.0)) self.updateScrolling(transition: transition) return availableSize } } func makeView() -> View { return View() } func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } } public final class BusinessSetupScreen: ViewControllerComponentContainer { private let context: AccountContext public init(context: AccountContext) { self.context = context super.init(context: context, component: BusinessSetupScreenComponent( context: context ), navigationBarAppearance: .default, theme: .default, updatedPresentationData: nil) let presentationData = context.sharedContext.currentPresentationData.with { $0 } self.title = "" self.navigationItem.backBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) self.scrollToTop = { [weak self] in guard let self, let componentView = self.node.hostView.componentView as? BusinessSetupScreenComponent.View else { return } componentView.scrollToTop() } self.attemptNavigation = { [weak self] complete in guard let self, let componentView = self.node.hostView.componentView as? BusinessSetupScreenComponent.View else { return true } return componentView.attemptNavigation(complete: complete) } } required public init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } deinit { } @objc private func cancelPressed() { self.dismiss() } override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) } }