Untitled
unknown
plain_text
3 years ago
32 kB
4
Indexable
// // ScripDetailsViewModel.swift // UpstoxProBeta // // Created by Cezar Carvalho on 2020-08-19. // Copyright © 2020 Upstox. All rights reserved. // import Foundation protocol ScripDetailsViewModelInput { func viewDidLoad() func viewWillAppear() func viewWillDisappaear() func startDataListening() func stopDataListening() func updateData() func setHeaderVisibility(_ isVisible: Bool) func setTabIndex(_ index: Int, tabType: ScripDetailsTabsViewModel.Tab.TabType) func watchDidTap() func priceAlertsDidTap() func openOrderEntry(with orderSide: OrderSideModel, price: Double?, delegate: OrderEntryCollapseViewControllerDelegate) func enableSegmentDidTap() func buildStrategyDidTap() func pickStrategyDidTap() func gttDidTap(delegate: OrderEntryCollapseViewControllerDelegate) func doNavigateToStrategyStory() func doNavigateToPrediction(eventSource: LogEvent.StrategyOrderEvents.EventSource) func doNavigateToOptionChain() func doNavigateToTradeOptions() func scripDetailsScrollTableViewInsightsSection() } protocol ScripDetailsViewModelOutput { var tabsViewModel: ScripDetailsTabsViewModel { get } var useCustomNavigationBar: Bool { get } var title: String { get } var exchange: String { get } var expirationDate: Date? { get } var shortExchangeKeyword: String? { get } var navigationBarRightViewState: Observable<ScripDetailsNavigationBarRightView.State> { get } var watchState: Observable<DataLoadState<AnimatedSelection>> { get } var priceAlertsSelection: Observable<AnimatedSelection> { get } var primaryDataState: Observable<DataLoadState<ScripDetailsCoordinatorModels.PrimaryData>> { get } var orderEntryNeedsOpening: Observable<(side: OrderSideModel, price: Double?)?> { get } var bottomButtonTitleObservable: Observable<String> { get } var mode: ScripDetailsCoordinatorModels.Mode { get } func getTabIndex(of type: ScripDetailsTabsViewModel.Tab.TabType) -> Int var canShowMTFBanner: Observable<(canShow: Bool, scripSymbol: String)> { get } } protocol ScripDetailsViewModelDelegate: AnyObject { func reloadWatchlistData() } protocol ScripDetailsRoutingLogic: OpenWebURLRoutingLogic, OpenLeadModeRoutingLogic, ConditionalOrderOptionsRoutingLogic, ReactivationBottomSheetRoutingLogic { func routeToAddOrRemoveFromLists(delegate: AddRemoveListDelegate?) func routeToOrderEntry(with orderSide: OrderSideModel, price: Double?, collapsableDelegate: OrderEntryCollapseViewControllerDelegate?, selectedType: OrderEntryCategoryModel) func routeToPriceAlertDetails(with scripIdentifier: ScripIdentifier, delegate: PriceAlertsDetailsCoordinatorDelegate?) func routeToStrategyStory() func routeToPrediction(entryPoint: StrategyBuilderDataModel.OptionStrategyEntryPoint, eventSource: LogEvent.StrategyOrderEvents.EventSource) func routeToOptionChain(entryPoint: StrategyBuilderDataModel.OptionStrategyEntryPoint) func showConditionalOrderOptions(delegate: OrderEntryCollapseViewControllerDelegate) func routeToTutorialConfirmation() func routeToTradeOptions(delegate: TradeOptionsViewModelDelegate) } protocol ScripDetailsViewModelProtocol: ScripDetailsViewModelInput, ScripDetailsViewModelOutput, OpenLeadModeBottomSheetShowable { } final class ScripDetailsViewModel: ScripDetailsViewModelProtocol { private let bodService: BODService private let profileWorker: ProfileWorkerProtocol? private let insightsWorker: InsightsWorkerProtocol private let watchedStateComputer: ExchangeWatchedStateComputable private let scripDetailsFeedSubscription: ScripDetailsFeedSubscription private let router: ScripDetailsRoutingLogic private let priceAlertsBellViewModel: PriceAlertsBellViewModel private let scripStateAccessQueue = DispatchQueue(label: "ScripDetailsViewModel#ScripState") private let headerRightButtonAccessQueue = DispatchQueue(label: "ScripDetailsViewModel#HeaderRightButton") private let dataUpdateQueue = DispatchQueue(label: "ScripDetailsViewModel#DataUpdate") private let primaryDataChangesQueue = DispatchQueue(label: "ScripDetailsViewModel#PrimaryDataChangesQueue") private let scripDetailsInfo: ScripDetailsInfo private let isHeaderShowed: Bool private let isHeaderRightButtonUpdatable: Bool private let showFourFractionalsForPrice: Bool private let isHeaderRightVisible: Bool private let eventSource: LogEvent.OrdersEvents.EventSource private var isAllDataUpdating = false private var isActiveTabDataSubscribed = false private var subscriptionTimeoutDuringDataUpdate = false weak var delegate: ScripDetailsViewModelDelegate? private var isWaitingForWatchlistLoadingNeeded = true private var isWaitingForFirstScripStatsNeeded: Bool private var isSegmentEnabled: Bool? private let mtfBannerViewModel: MTFBannerViewModelProtocol private let appServices: AppServices private var _scripState: ScripState? private var scripState: ScripState? { get { return scripStateAccessQueue.sync { _scripState } } set { scripStateAccessQueue.sync { _scripState = newValue } } } private var _headerRightButtonConfiguration: ScripDetailsCoordinatorModels.PrimaryData.Header.RightButtonConfiguration? private var headerRightButtonConfiguration: ScripDetailsCoordinatorModels.PrimaryData.Header.RightButtonConfiguration? { get { return headerRightButtonAccessQueue.sync { _headerRightButtonConfiguration } } set { headerRightButtonAccessQueue.sync { _headerRightButtonConfiguration = newValue } } } private var shouldShowMTFBanner: Bool { return scripDetailsInfo.isEquity && verifyIfMTFEnabled(for: scripDetailsInfo.identifier.token) && mtfBannerViewModel.canShowMTFBanner } private func verifyIfMTFEnabled(for token: String) -> Bool { return appServices.bodService.bodDataSource.checkIfMTFEnabledScripsExist(token: token) } private var stateUpdateSubscription: ReactiveDataSubscription? private var statsSubscription: ReactiveDataSubscription? let useCustomNavigationBar: Bool let title: String let exchange: String let expirationDate: Date? let shortExchangeKeyword: String? let mode: ScripDetailsCoordinatorModels.Mode let navigationBarRightViewState: Observable<ScripDetailsNavigationBarRightView.State> = Observable(.hidden) let watchState: Observable<DataLoadState<AnimatedSelection>> = Observable(.loading) var priceAlertsSelection: Observable<AnimatedSelection> { priceAlertsBellViewModel.selection } let primaryDataState: Observable<DataLoadState<ScripDetailsCoordinatorModels.PrimaryData>> = Observable(.loading) let orderEntryNeedsOpening: Observable<(side: OrderSideModel, price: Double?)?> lazy var bottomButtonTitleObservable: Observable<String> = Observable(scripDetailsInfo.bottomTitle) var canShowMTFBanner: Observable<(canShow: Bool, scripSymbol: String)> = Observable((false, "")) let tabsViewModel: ScripDetailsTabsViewModel init(router: ScripDetailsRouter, dataInput: ScripDetailsCoordinatorModels.DataInput, injections: ScripDetailsCoordinatorModels.Injections, bannerViewModel: MTFBannerViewModelProtocol, appServices: AppServices) { self.appServices = appServices self.mtfBannerViewModel = bannerViewModel self.router = router.scripDetailsRoutingComposition self.scripDetailsInfo = dataInput.scripDetailsInfo self.isHeaderShowed = dataInput.mode == .scripDetails appServices.userDefaultsStorage.bottomSheet = dataInput.mode == .bottomSheet self.isHeaderRightButtonUpdatable = dataInput.mode == .scripDetails && dataInput.scripDetailsInfo.identifier.exchange.isOptionChainAllowed self.mode = dataInput.mode self.bodService = injections.bodService self.profileWorker = injections.profileWorker self.watchedStateComputer = injections.watchedStateComputer self.insightsWorker = injections.insightsWorker self.scripDetailsFeedSubscription = ScripDetailsFeedSubscription( scripIdentifier: dataInput.scripDetailsInfo.identifier, config: .init(instrumentType: dataInput.scripDetailsInfo.instrument.type, mode: dataInput.mode), feedWorker: injections.feedWorker ) let niftySymbol = String(scripDetailsInfo.symbol.prefix(6)) let bankNiftySymbol = String(scripDetailsInfo.symbol.prefix(9)) let nifty50Symbol = String(scripDetailsInfo.symbol.prefix(9)) let niftyBank = String(scripDetailsInfo.symbol.prefix(11)) appServices.userDefaultsStorage.scripIndexClicked = !(niftySymbol.caseInsensitiveCompare(OptionIndexSymbolModel.nifty.rawValue) == .orderedSame || bankNiftySymbol.caseInsensitiveCompare(OptionIndexSymbolModel.bankNifty.rawValue) == .orderedSame || nifty50Symbol.caseInsensitiveCompare(OptionIndexSymbolModel.nifty50.rawValue) == .orderedSame || niftyBank.caseInsensitiveCompare(OptionIndexSymbolModel.niftyBank.rawValue) == .orderedSame) self.isWaitingForFirstScripStatsNeeded = dataInput.mode == .scripDetails /// For Lead users the NSE_FO is by defalt Enabled if injections.userSession?.loggedUserType == .lead { isSegmentEnabled = true } self._headerRightButtonConfiguration = isHeaderRightButtonUpdatable ? nil : .hidden self.useCustomNavigationBar = dataInput.mode == .bottomSheet self.title = scripDetailsInfo.symbol.scripTitle(from: scripDetailsInfo.instrument.titleInfo) self.exchange = scripDetailsInfo.identifier.exchange.keywordValue self.expirationDate = { guard case .derivative(let info) = dataInput.scripDetailsInfo.instrument else { return nil } return info.expirationDate }() self.isHeaderRightVisible = dataInput.mode != .bottomSheet self.shortExchangeKeyword = scripDetailsInfo.symbol.scripShortExchangeKeyword(from: scripDetailsInfo.instrument.titleInfo) self.showFourFractionalsForPrice = scripDetailsInfo.isCurrencyDerivative self.priceAlertsBellViewModel = PriceAlertsBellViewModel(scripIdentifier: dataInput.scripDetailsInfo.identifier, priceAlertsWorker: injections.priceAlertsWorker) let orderEntryNeedsOpening = Observable<(side: OrderSideModel, price: Double?)?>(nil) self.tabsViewModel = ScripDetailsTabsViewModel(router: router, summaryInjections: injections.summary, positionsWorker: injections.positionsWorker, newsWorker: injections.summary.newsWorker, chartsInjections: injections.chartsInjections, scripDetailsFeedSubscription: scripDetailsFeedSubscription, dataInput: dataInput, selectedTabType: ScripDetailsTabsViewModel.Tab.TabType(rawValue: injections.userDefaultsStorage.lastVisitedScripDetailsTabBarType) ?? .summary) { side, price in orderEntryNeedsOpening.value = (side: side, price: price) } self.orderEntryNeedsOpening = orderEntryNeedsOpening eventSource = injections.eventSource setupInsights() setupFeedsSubscriptions() } deinit { stateUpdateSubscription?.unsubscribe() statsSubscription?.unsubscribe() } func viewDidLoad() { setupTabSubscription(userDefaultStorage: AppServices.shared.userDefaultsStorage) tabsViewModel.viewDidLoad() guard !appServices.userDefaultsStorage.conditionalOrdersTutorialWasShown else { return } router.routeToTutorialConfirmation() appServices.userDefaultsStorage.conditionalOrdersTutorialWasShown = true } func subscribeToMTFBannerMonitor() { appServices.mtfBannerMonitor.addObserver(self) } func viewWillAppear() { tabsViewModel.updateOptionStrategyBannerDisplay() } func viewWillDisappaear() { // this logic is important to reopen option details bottom sheet when user comes back after clicking on View chart or scrip name from that bottom sheet guard eventSource == .optionDetails else { return } appServices.userDefaultsStorage.lastVisitedScripDetailsTabBarType = ScripDetailsTabsViewModel.Tab.TabType.optionChain.rawValue } func startDataListening() { scripDetailsFeedSubscription.start() } func stopDataListening() { scripDetailsFeedSubscription.stop() } func updateData() { dataUpdateQueue.async { guard !self.isAllDataUpdating else { return } self.isAllDataUpdating = true DispatchQueue.main.sync { guard case .error = self.primaryDataState.value else { return } self.primaryDataState.value = .loading self.primaryDataChangesQueue.async { var subscriptionTypes: Set<ScripFeedDataType> = [] if self.scripState == nil { subscriptionTypes.insert(.state) } if self.isWaitingForFirstScripStatsNeeded { subscriptionTypes.insert(.stats) } guard !subscriptionTypes.isEmpty else { return } self.scripDetailsFeedSubscription.retry(for: subscriptionTypes) } } func updateLast() { self.updateWatchlistItemState { isSuccess in guard isSuccess else { return self.allDataUpdateFailed() } self.primaryDataChangesQueue.async { guard self.isWaitingForWatchlistLoadingNeeded else { return } self.isWaitingForWatchlistLoadingNeeded = false self.tryUpdatePrimaryData() } self.updateInstrumentSpecificDataIfNeeded() } } guard self.isHeaderRightButtonUpdatable else { return updateLast() } DispatchQueue.main.async { let allExpiries = self.bodService.bodDataSource .getAllOptionExpiries(symbol: self.scripDetailsInfo.symbol.optionChainSymbol, exchange: self.scripDetailsInfo.identifier.exchange) let headerRightButtonConfiguration: ScripDetailsCoordinatorModels.PrimaryData.Header.RightButtonConfiguration = allExpiries.isEmpty ? .hidden : .optionChain self.tabsViewModel.optionChainShowValueChanged(shouldShow: headerRightButtonConfiguration == .optionChain) self.primaryDataChangesQueue.async { guard self.headerRightButtonConfiguration != headerRightButtonConfiguration else { return } self.headerRightButtonConfiguration = headerRightButtonConfiguration self.tryUpdatePrimaryData() } self.dataUpdateQueue.async { updateLast() } } } } func setHeaderVisibility(_ isVisible: Bool) { guard case .loaded = primaryDataState.value else { return } tryUpdateNavigationBarRightBarButtonItemsState() } func getTabIndex(of type: ScripDetailsTabsViewModel.Tab.TabType) -> Int { let activeTabsData = tabsViewModel.activeTabsData.value return activeTabsData?.tabs.firstIndex(where: { $0.type == type }) ?? 0 } func setTabIndex(_ index: Int, tabType: ScripDetailsTabsViewModel.Tab.TabType) { appServices.userDefaultsStorage.lastVisitedScripDetailsTabBarType = tabType.rawValue tabsViewModel.changeSelectedIndex(to: UInt(index)) } func scripDetailsScrollTableViewInsightsSection() { tabsViewModel.tabsScrollTableViewInsightsSection() } func watchDidTap() { router.routeToAddOrRemoveFromLists(delegate: self) trackScriptDetailsCTATap(.follow) } func priceAlertsDidTap() { guard !shouldShowLeadModeBottomSheet(for: .priceAlerts, router: router ) else { return } router.routeToPriceAlertDetails(with: scripDetailsInfo.identifier, delegate: priceAlertsBellViewModel) LogEvent.PriceAlerts.priceAlertEntryPoint = .scriptPage trackScriptDetailsCTATap(.alert) } func openOrderEntry(with orderSide: OrderSideModel, price: Double?, delegate: OrderEntryCollapseViewControllerDelegate) { guard !shouldShowLeadModeBottomSheet(for: .scriptDetails, router: router), !shouldShowReactivationBottomSheet() else { return } router.routeToOrderEntry(with: orderSide, price: price, collapsableDelegate: delegate, selectedType: .regular) trackScriptDetailsCTATap(orderSide == .buy ? .buy : .sell) } func enableSegmentDidTap() { let clientInfo = AppServices.shared.cacheService.clientInfoCacheStore.getFromCache() let bottomSheetStyle = clientInfo?.getBottomSheetStyle(for: scripDetailsInfo.identifier.exchange) if let reactivationState = clientInfo?.reactivationState, let style = bottomSheetStyle { router.showReactivationBottomSheet(with: reactivationState, style: style) } else { router.routeToWebView(with: WebLinks.Profiles.Profile.Accounts.myTradingSegments) } trackScriptDetailsCTATap(.enableFutures) } func buildStrategyDidTap() { trackScriptDetailsCTATap(.buildStrategy) } func pickStrategyDidTap() { guard let activeTabs = tabsViewModel.activeTabsData.value?.tabs else { return } for (index, tab) in activeTabs.enumerated() { if tab.type == .strategy { tabsViewModel.changeSelectedIndex(to: UInt(index)) trackScriptDetailsCTATap(.pickStrategy) return } } } func gttDidTap(delegate: OrderEntryCollapseViewControllerDelegate) { guard !shouldShowLeadModeBottomSheet(for: .scriptDetails, router: router), !shouldShowReactivationBottomSheet() else { return } if scripDetailsInfo.isEquity { router.routeToOrderEntry(with: .buy, price: nil, collapsableDelegate: delegate, selectedType: .conditional) } else { router.showConditionalOrderOptions(delegate: delegate) } trackScriptDetailsCTATap(.gtt) } } // MARK: - AddRemoveListDelegate extension ScripDetailsViewModel: AddRemoveListDelegate { func addRemoveListDidCreateList() { updateWatchlistItemState() } func addRemoveListDidCompleteWithChanges() { scripAdded(source: .scripDetails) updateWatchlistItemState() delegate?.reloadWatchlistData() } } // MARK: - TradeOptionsViewModelDelegate extension ScripDetailsViewModel: TradeOptionsViewModelDelegate { func tradeOptionsViewModel(didSelectOption option: ScripDetailsCoordinatorModels.TradeOption) { switch option { case .pick: if appServices.userDefaultsStorage.optionStrategyStoryCompleted { router.routeToPrediction(entryPoint: .choose, eventSource: .scripNavigation) } else { router.routeToStrategyStory() } case .build: print("TODO") //TODO: add logic for build case .createGtt: router.routeToOrderEntry(with: .buy, price: nil, collapsableDelegate: nil, selectedType: .conditional) } } } // MARK: - SetupHelper private typealias SetupHelper = ScripDetailsViewModel private extension SetupHelper { func setupFeedsSubscriptions() { let stateUpdateSubscription = scripDetailsFeedSubscription .subscribe { [weak self] (result: Result<ScripState, ScripFeedError>) in guard let self = self else { return } switch result { case .success(let state): self.primaryDataChangesQueue.async { guard state != self.scripState else { return } self.scripState = state self.tryUpdatePrimaryData() } case .failure(let error): self.allDataUpdate(by: error) } } self.stateUpdateSubscription = stateUpdateSubscription guard isWaitingForFirstScripStatsNeeded else { return } self.primaryDataChangesQueue.async { let statsSubscription = self.scripDetailsFeedSubscription .subscribe { [weak self] (result: Result<ScripStats, ScripFeedError>) in guard let self = self else { return } switch result { case .success: self.primaryDataChangesQueue.async { self.statsSubscription?.unsubscribe() self.statsSubscription = nil self.isWaitingForFirstScripStatsNeeded = false self.tryUpdatePrimaryData() } case .failure(let error): self.allDataUpdate(by: error) } } self.statsSubscription = statsSubscription } } func setupTabSubscription(userDefaultStorage: UserDefaultsStorage) { tabsViewModel.activeTabsData.observe(on: self) { [weak self] activeTabsData in let selectedIndex = activeTabsData?.selectedIndex guard let self = self, let activeTabsData = activeTabsData, let selectedIndex = selectedIndex, selectedIndex >= 0, activeTabsData.tabs.count > selectedIndex else { return } let tabTitle = activeTabsData.tabs[Int(selectedIndex)].eventTabTitle if self.isActiveTabDataSubscribed { self.trackTabSwitched(tabViewed: tabTitle) } else { self.trackScriptDetailsViewed(tabViewed: tabTitle) } self.isActiveTabDataSubscribed = true } } } // MARK: - UpdateDataHelper private typealias UpdateDataHelper = ScripDetailsViewModel private extension UpdateDataHelper { func updateWatchlistItemState(completion: ((Bool) -> Void)? = nil) { DispatchQueue.main.async { self.watchState.setLoadingIfErrorOnly() } watchedStateComputer.getExchangeWatchedState(for: scripDetailsInfo.identifier) { [weak self] result in guard let self = self else { return } switch result { case .success(let exchangeWatchedState): DispatchQueue.main.async { switch self.watchState.value { case .loading, .error: self.watchState.value = .loaded(data: .init( selected: exchangeWatchedState, animated: .nonActive )) case .loaded(let data): self.watchState.value = .loaded(data: .init( selected: exchangeWatchedState, animated: data.selected != exchangeWatchedState ? .active : .nonActive )) } } completion?(true) case .failure: DispatchQueue.main.async { self.watchState.setErrorIfLoading() } completion?(false) } } } func allDataUpdateFailed() { self.dataUpdateQueue.async { DispatchQueue.main.sync { self.primaryDataState.setErrorIfLoading() } self.isAllDataUpdating = false self.subscriptionTimeoutDuringDataUpdate = false } } func allDataUpdateSuccessfully() { self.dataUpdateQueue.async { if self.subscriptionTimeoutDuringDataUpdate { self.subscriptionTimeoutDuringDataUpdate = false DispatchQueue.main.sync { self.primaryDataState.setErrorIfLoading() } } self.isAllDataUpdating = false } } func allDataUpdate(by error: ScripFeedError) { self.dataUpdateQueue.async { switch error { case .timeout: guard !self.isAllDataUpdating else { self.subscriptionTimeoutDuringDataUpdate = true return } DispatchQueue.main.sync { self.primaryDataState.setErrorIfLoading() } } } } func updateInstrumentSpecificDataIfNeeded() { guard let profileWorker = profileWorker else { return allDataUpdateSuccessfully() } profileWorker.getClientInfo(cachePolicy: .returnCacheDataElseLoad) { [weak self] result in guard let self = self else { return } var isSegmentEnabled: Bool switch result { case .success(let clientInfo): switch self.scripDetailsInfo.instrument { case .index: isSegmentEnabled = clientInfo.exchanges.contains(Exchange.nseFO.rawValue) case .derivative, .equity: isSegmentEnabled = clientInfo.exchanges.contains(self.scripDetailsInfo.identifier.exchange.rawValue) } self.primaryDataChangesQueue.async { guard self.isSegmentEnabled != isSegmentEnabled else { return } self.isSegmentEnabled = isSegmentEnabled self.tryUpdatePrimaryData() } self.allDataUpdateSuccessfully() case .failure: self.allDataUpdateFailed() } } } /// should be called on main thread func tryUpdateNavigationBarRightBarButtonItemsState() { let navigationBarRightViewState: ScripDetailsNavigationBarRightView.State = .scripState guard self.navigationBarRightViewState.value != navigationBarRightViewState, isHeaderRightVisible == true else { return } self.navigationBarRightViewState.value = navigationBarRightViewState } /// should be called on primary data queue func tryUpdatePrimaryData() { guard !isWaitingForWatchlistLoadingNeeded, !isWaitingForFirstScripStatsNeeded, let scripState = scripState, let headerRightButtonConfiguration = headerRightButtonConfiguration else { return } var bottomButtonsConfiguration: ScripDetailsCoordinatorModels.BottomButtonsConfiguration let equityCompanyName: String? switch scripDetailsInfo.instrument { case .index: equityCompanyName = nil let isSegmentDisabled = !(isSegmentEnabled ?? false) bottomButtonsConfiguration = scripDetailsInfo.isIndexNiftyBankNifty ? .indicesNiftyAndBankNifty(disabled: isSegmentDisabled) : .otherIndices case .equity(let equityData): equityCompanyName = equityData.issuerName bottomButtonsConfiguration = .equity case .derivative: guard let isSegmentEnabled = isSegmentEnabled else { return } if isSegmentEnabled { bottomButtonsConfiguration = scripDetailsInfo.isFNONiftyBankNifty ? .fnoNiftyAndBankNifty(disabled: !isSegmentEnabled) : .otherFNO(disabled: !isSegmentEnabled) } else { bottomButtonsConfiguration = scripDetailsInfo.isFNONiftyBankNifty ? .fnoNiftyAndBankNifty(disabled: !isSegmentEnabled): .enableFuturesAndOptions(segment: scripDetailsInfo.identifier.exchange) } equityCompanyName = nil } if mode == .bottomSheet { guard let isSegmentEnabled = isSegmentEnabled else { return } bottomButtonsConfiguration = !isSegmentEnabled ? .enableFuturesAndOptions(segment: scripDetailsInfo.identifier.exchange) : .bottomSheetOptionChain } let primaryData = ScripDetailsCoordinatorModels.PrimaryData( scripState: scripState, header: isHeaderShowed ? .showed(equityCompanyName: equityCompanyName, rightButtonConfiguration: headerRightButtonConfiguration) : .hidden, bottomButtonsConfiguration: bottomButtonsConfiguration, showFourFractionalsForPrice: showFourFractionalsForPrice, showPriceCurrencySymbol: !scripDetailsInfo.identifier.exchange.isIndex ) DispatchQueue.main.async { self.tryUpdateNavigationBarRightBarButtonItemsState() self.primaryDataState.value = .loaded(data: primaryData) } } } // MARK: - ScripDetailsFeedSubscription.Config private extension ScripDetailsFeedSubscription.Config { init(instrumentType: ScripInstrumentType, mode: ScripDetailsCoordinatorModels.Mode) { switch instrumentType { case .index: self = .index case .equity: self = .equity(includeDepth: mode == .scripDetails) case .derivative(let derivativeType): self = .derivative(includeDepth: mode == .scripDetails, includeGreeks: derivativeType == .option) } } } // MARK: - StrategyNavigationHelper typealias StrategyNavigationHelper = ScripDetailsViewModel extension StrategyNavigationHelper { func doNavigateToStrategyStory() { guard !shouldShowLeadModeBottomSheet(for: .scriptDetails, router: router) else { return } router.routeToStrategyStory() } func doNavigateToPrediction(eventSource: LogEvent.StrategyOrderEvents.EventSource) { guard !shouldShowLeadModeBottomSheet(for: .scriptDetails, router: router) else { return } router.routeToPrediction(entryPoint: .choose, eventSource: eventSource) } func doNavigateToOptionChain() { guard !shouldShowLeadModeBottomSheet(for: .scriptDetails, router: router) else { return } router.routeToOptionChain(entryPoint: .create) } func doNavigateToTradeOptions() { guard !shouldShowLeadModeBottomSheet(for: .scriptDetails, router: router) else { return } appServices.userDefaultsStorage.moreButtonClicked = true router.routeToTradeOptions(delegate: self) } } // MARK: - ScripDetailsInfo private extension ScripDetailsInfo { var bottomTitle: String { return isEquity ? "create_gtt_order".localized : "more_options".localized } } // MARK: - Insights private typealias Insights = ScripDetailsViewModel private extension Insights { func setupInsights() { guard scripDetailsInfo.instrument.type == ScripInstrumentType.equity else { tryUpdatePrimaryData() return } insightsWorker.getInsights(symbol: self.scripDetailsInfo.symbol) { [weak self] result in guard let self = self else { return } switch result { case .success(let insightsData): self.primaryDataChangesQueue.async { guard !(insightsData.description.isEmpty) else { self.tryUpdatePrimaryData() return } self.tryUpdatePrimaryData() self.tabsViewModel.updateInsightsDataInTab(insightsString: insightsData.description) } self.allDataUpdateSuccessfully() case .failure: self.tryUpdatePrimaryData() } } } } // MARK: Analytics private typealias AnalyticHelper = ScripDetailsViewModel private extension AnalyticHelper { func trackScriptDetailsViewed(tabViewed: LogEvent.ScriptDetails.TabTitle) { let logData = LogEvent.ScriptDetails.ScriptDetailsViewedLogData(scripName: title, tabName: tabViewed, source: LogEvent.ScriptDetails.scriptDetailSourceName) AppServices.shared.analyticsManager.trackEvent(LogEvent.ScriptDetails.scriptDetailsViewed(logData: logData)) } func trackTabSwitched(tabViewed: LogEvent.ScriptDetails.TabTitle) { let logData = LogEvent.ScriptDetails.ScriptDetailsTabSwitchLogData(tabName: tabViewed) AppServices.shared.analyticsManager.trackEvent(LogEvent.ScriptDetails.scriptDetailsTabSwitched(logData: logData)) } func trackScriptDetailsCTATap(_ buttonTitle: LogEvent.ScriptDetails.CTATitle) { let logData = LogEvent.ScriptDetails.ScriptDetailsCTATappedLogData(buttonName: buttonTitle) AppServices.shared.analyticsManager.trackEvent(LogEvent.ScriptDetails.scriptDetailsCTATapped(logData: logData)) } func scripAdded(source: LogEvent.MyList.AddScripSource) { let logData = LogEvent.MyList.WatchlistAddScripLogData(source: source) appServices.analyticsManager.trackEvent(LogEvent.MyList.watchlistAddScrip(logData: logData)) } func trackMTFActivated() { appServices.analyticsManager.trackEvent(LogEvent.MTFActivation.scripDetail) } } // MARK: - MTFBannerMonitorListener extension ScripDetailsViewModel: MTFBannerMonitorListener { func removeMTFBanner() { canShowMTFBanner.value = (false, scripDetailsInfo.symbol) } } // MARK: - Helper private typealias Helper = ScripDetailsViewModel private extension Helper { func shouldShowReactivationBottomSheet() -> Bool { let clientInfo = appServices.cacheService.clientInfoCacheStore.getFromCache() guard let reactivationState = clientInfo?.reactivationState, let style = clientInfo?.getBottomSheetStyle(for: scripDetailsInfo.identifier.exchange) else { return false } router.showReactivationBottomSheet(with: reactivationState, style: style) return true } }
Editor is loading...