Untitled
unknown
plain_text
3 years ago
32 kB
9
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...