package com.suno.android.common_mvi import com.suno.android.common_analytics.managers.AnalyticsManager import com.suno.android.common_core_utils.SunoLogger import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json interface MviProcessor { val state: StateFlow val effects: SharedFlow val logger: SunoLogger fun sendEvent( event: Event, ) fun emitEffect( effect: Effect, ) // Use this to track one-shot effects that we don't need handling in the UI fun analyticEffect( trackingEffect: TrackingEffect, ) fun updateState( reducer: (State) -> State, ) fun logEvent( event: Event, ) fun logStateDebug( state: State, ) fun logEffect( effect: Effect, ) } class DefaultMviProcessor( override val logger: SunoLogger, initialState: State, private val coroutineScope: CoroutineScope, private val reducer: suspend (State, Event, suspend (Effect) -> Unit) -> State, private val analyticsManager: AnalyticsManager, private val isNewLoggingGateEnabled: Boolean, ) : MviProcessor { private val _state = MutableStateFlow(initialState) override val state = _state.asStateFlow() private val _effects = MutableSharedFlow() override val effects = _effects.asSharedFlow() init { state.onEach(::logStateDebug).launchIn(coroutineScope) effects.onEach(::logEffect).launchIn(coroutineScope) } override fun sendEvent( event: Event, ) { coroutineScope.launch { logEvent(event) _state.value = reducer(_state.value, event) { effect -> _effects.emit(effect) } } } override fun emitEffect( effect: Effect, ) { coroutineScope.launch { _effects.emit(effect) } } override fun analyticEffect( trackingEffect: TrackingEffect, ) { coroutineScope.launch { (trackingEffect as? Effect)?.let { logEffect(effect = trackingEffect) } } } override fun updateState( reducer: (State) -> State, ) { _state.value = reducer(_state.value) } override fun logEvent( event: Event, ) { logger.d { "MVI Event: $event" } val className = event::class.qualifiedName val defaultPackageDotEventName: String = className?.split(".")?.takeLast(2)?.joinToString(".") ?: className.toString() val eventName = event.trackingName ?: defaultPackageDotEventName val analyticContext = event.metadata?.let { Json.encodeToString(it) } // New logging if (isNewLoggingGateEnabled) { event.trackingName?.let { name -> analyticsManager.trackMviEvent( eventName = name, source = event.screen, method = event.method, elementType = event.elementType, elementId = event.elementId, context = analyticContext, ) } } // Old logging if (event.keepEventForAnalytics) { analyticsManager.trackMviEvent( eventName = eventName, source = event.screen, method = event.method, elementType = event.elementType, elementId = event.elementId, context = analyticContext, ) } } override fun logStateDebug( state: State, ) { logger.d { "MVI State: $state" } } override fun logEffect( effect: Effect, ) { logger.d { "MVI Effect: $effect" } val className = effect::class.qualifiedName val defaultPackageDotEventName: String = className?.split(".")?.takeLast(2)?.joinToString(".") ?: className.toString() val effectName = effect.trackingName ?: defaultPackageDotEventName val analyticContext = effect.metadata?.let { Json.encodeToString(it) } // New logging if (isNewLoggingGateEnabled) { effect.trackingName?.let { name -> analyticsManager.trackMviEvent( eventName = name, source = effect.screen, elementType = effect.elementType, elementId = effect.elementId, context = analyticContext, ) } } // Old logging if (effect.keepEffectForAnalytics) { analyticsManager.trackMviEffect( effectName = effectName, source = effect.screen, elementType = effect.elementType, elementId = effect.elementId, context = analyticContext, ) } } }