package com.blaze.rtnblazesdk.base import BlazeReactWidgetLayout import android.content.Context import android.util.AttributeSet import android.view.View import androidx.core.view.doOnPreDraw import com.blaze.blazesdk.data_source.BlazeDataSourceType import com.blaze.blazesdk.delegates.BlazeWidgetDelegate import com.blaze.blazesdk.delegates.models.BlazeCTAActionType import com.blaze.blazesdk.delegates.models.BlazePlayerEvent import com.blaze.blazesdk.delegates.models.BlazePlayerType import com.blaze.blazesdk.extentions.blazeDeepCopy import com.blaze.blazesdk.features.shared.models.ui_shared.BlazeLinkActionHandleType import com.blaze.blazesdk.prefetch.models.BlazeCachingLevel import com.blaze.blazesdk.shared.BlazeSDK import com.blaze.blazesdk.shared.results.BlazeResult import com.blaze.blazesdk.style.widgets.BlazeWidgetItemCustomMapping import com.blaze.blazesdk.style.widgets.BlazeWidgetItemStyleOverrides import com.blaze.blazesdk.style.widgets.BlazeWidgetLayout import com.blaze.rtnblazesdk.BlazeSdkModule import com.blaze.rtnblazesdk.BlazeSdkModuleRepo import com.blaze.rtnblazesdk.customization.BlazeReactWidgetItemCustomMapping import com.blaze.rtnblazesdk.customization.BlazeReactWidgetItemStyleOverrides import com.blaze.rtnblazesdk.customization.mergedWith import com.blaze.rtnblazesdk.events.BaseEvent import com.blaze.rtnblazesdk.events.WidgetCTAClickEvent import com.blaze.rtnblazesdk.events.WidgetDataLoadCompletedEvent import com.blaze.rtnblazesdk.events.WidgetDataLoadStartedEvent import com.blaze.rtnblazesdk.events.WidgetItemClickedEvent import com.blaze.rtnblazesdk.events.WidgetOnPlayerEventTriggered import com.blaze.rtnblazesdk.events.WidgetOnTriggerPlayerBodyTextLinkEvent import com.blaze.rtnblazesdk.events.WidgetPlayerDidAppearEvent import com.blaze.rtnblazesdk.events.WidgetPlayerDismissedEvent import com.blaze.rtnblazesdk.utils.extractCachingLevel import com.blaze.rtnblazesdk.utils.extractDataSource import com.blaze.rtnblazesdk.utils.getBooleanOrNull import com.blaze.rtnblazesdk.utils.isNotEmpty import com.blaze.rtnblazesdk.utils.parsing.fromJsonString import com.blaze.rtnblazesdk.utils.parsing.toObject import com.facebook.react.bridge.ReactContext import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap import com.facebook.react.uimanager.ReactStylesDiffMap import com.facebook.react.uimanager.UIManagerHelper import com.facebook.react.uimanager.events.EventDispatcher abstract class BaseWidgetView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : BlazeReactSizeFixingFrameLayout(context, attrs, defStyleAttr) { protected var widgetView: View? = null private var dataSourceType: BlazeDataSourceType? = null var reactCachingLevel: String? = null var perItemStyleOverridesReadableMap: ReadableMap? = null var presetWidgetLayoutString: String? = null var appOverridesCTAHandling: Boolean = false var shouldOrderWidgetByReadStatus: Boolean = true var reactWidgetLayoutStyleMap: ReadableMap? = null var reactWidgetPlayerStyleMap: ReadableMap? = null var nativeWidgetLayout: BlazeWidgetLayout? = null val cachingLevel: BlazeCachingLevel get() = reactCachingLevel?.extractCachingLevel() ?: BlazeSdkModuleRepo.cachingLevel ?: BlazeCachingLevel.DEFAULT protected val widgetDelegate = object : BlazeWidgetDelegate { override fun onDataLoadStarted(playerType: BlazePlayerType, sourceId: String?) { dispatchEvent( WidgetDataLoadStartedEvent( UIManagerHelper.getSurfaceId(context), id, widgetId = sourceId ) ) } override fun onDataLoadComplete( playerType: BlazePlayerType, sourceId: String?, itemsCount: Int, result: BlazeResult ) { dispatchEvent( WidgetDataLoadCompletedEvent( UIManagerHelper.getSurfaceId(context), id, widgetId = sourceId, itemsCount = itemsCount, result = result ) ) } override fun onPlayerDidAppear(playerType: BlazePlayerType, sourceId: String?) { dispatchEvent( WidgetPlayerDidAppearEvent( UIManagerHelper.getSurfaceId(context), id, widgetId = sourceId ) ) } override fun onPlayerDidDismiss(playerType: BlazePlayerType, sourceId: String?) { dispatchEvent( WidgetPlayerDismissedEvent( UIManagerHelper.getSurfaceId(context), id, widgetId = sourceId ) ) } override fun onTriggerCTA( playerType: BlazePlayerType, sourceId: String?, actionType: BlazeCTAActionType, actionParam: String ): Boolean { dispatchEvent( WidgetCTAClickEvent( UIManagerHelper.getSurfaceId(context), id, widgetId = sourceId, actionType = actionType, actionParam = actionParam ) ) return appOverridesCTAHandling } override fun onTriggerPlayerBodyTextLink( playerType: BlazePlayerType, sourceId: String?, actionParam: String ): BlazeLinkActionHandleType { dispatchEvent( WidgetOnTriggerPlayerBodyTextLinkEvent( UIManagerHelper.getSurfaceId(context), id, widgetId = sourceId, actionParam = actionParam ) ) return BlazeLinkActionHandleType.DEEPLINK } override fun onPlayerEventTriggered( playerType: BlazePlayerType, sourceId: String?, event: BlazePlayerEvent ) { dispatchEvent( WidgetOnPlayerEventTriggered( UIManagerHelper.getSurfaceId(context), id, widgetId = sourceId, event = event ) ) } override fun onItemClicked(sourceId: String?, itemId: String, itemTitle: String) { dispatchEvent( WidgetItemClickedEvent( UIManagerHelper.getSurfaceId(context), id, widgetId = sourceId, widgetItemId = itemId, widgetItemTitle = itemTitle ) ) } } abstract fun callInitWidget(dataSource: BlazeDataSourceType) abstract fun createNewWidgetView(): View abstract fun reloadData(isSilentRefresh: Boolean) abstract fun updateWidgetDataSource(sourceType: BlazeDataSourceType, isSilentRefresh: Boolean) abstract fun updateOverrideStyles( perItemStyleOverrides: Map, shouldUpdateUi: Boolean ) abstract fun updateWidgetsUi() abstract fun play() override fun onAttachedToWindow() { super.onAttachedToWindow() createWidgetAndAddToParent() } override fun onDetachedFromWindow() { super.onDetachedFromWindow() removeWidgetView() } private fun createWidgetAndAddToParent() { this.layoutParams = LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT ) widgetView = createNewWidgetView().also { view -> view.layoutParams = LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT ) this.addView(view) view.doOnPreDraw { dataSourceType?.let { sourceType -> callInitWidget(sourceType) } } } } private fun removeWidgetView() { widgetView?.let { removeView(widgetView) } widgetView = null } fun getIsSilentRefreshAndReloadData(args: ReadableArray) { if (args.isNotEmpty()) { val isSilentRefresh = args.getBooleanOrNull(ARG_INDEX_IS_SILENT_REFRESH) ?: false this.reloadData(isSilentRefresh) } } fun updateDataSourceFromArgs(args: ReadableArray) { if (args.isNotEmpty()) { val dataSourceMap = args.getMap(ARG_INDEX_DATA_SOURCE_MAP) dataSourceMap.extractDataSource()?.let { sourceType -> dataSourceType = sourceType val isSilentRefresh = args.getBooleanOrNull(ARG_INDEX_UPDATE_DATA_SOURCE_IS_SILENT_REFRESH) ?: false updateWidgetDataSource(sourceType = sourceType, isSilentRefresh = isSilentRefresh) } } } fun updateOverrideStylesFromArgs(args: ReadableArray) { if (args.isNotEmpty()) { val overrideStylesString = args.getMap(ARG_INDEX_OVERRIDE_STYLE_MAP) val widgetLayout = nativeWidgetLayout ?: return val perItemStylesOverridesMap = applyWidgetItemStyleOverrides( reactPerItemStyleOverrides = overrideStylesString, widgetLayout = widgetLayout, context = context ) val shouldUpdateUi = args.getBooleanOrNull(ARG_INDEX_OVERRIDE_STYLE_SHOULD_UPDATE_UI) ?: true perItemStylesOverridesMap?.let { updateOverrideStyles( perItemStyleOverrides = it, shouldUpdateUi = shouldUpdateUi ) } } } fun onPropsUpdate(props: ReactStylesDiffMap) { // The widget will be non-null when the props are changing while the widget already exists and initialized. // So make sure to update it again then reloadData with shimmer. dataSourceType?.let { callInitWidget(it) reloadData(false) } } protected fun dispatchEvent(e: BaseEvent<*>) { try { val eventDispatcher: EventDispatcher? = UIManagerHelper.getEventDispatcherForReactTag(context as ReactContext, id) eventDispatcher?.dispatchEvent(e) } catch (ex: Exception) { //NO OP catch } } protected fun applyWidgetLayoutCustomization( reactWidgetLayout: ReadableMap?, widgetLayout: BlazeWidgetLayout, context: Context ): BlazeWidgetLayout { val rawLayout = reactWidgetLayout ?: return widgetLayout val customization = rawLayout.toObject() return widgetLayout.mergedWith(customization = customization,context) } protected fun applyWidgetItemStyleOverrides( reactPerItemStyleOverrides: ReadableMap?, widgetLayout: BlazeWidgetLayout, context: Context ): Map? { val rawLayout = reactPerItemStyleOverrides ?: return null val customizationMap = rawLayout.toHashMap().mapNotNull { (key, value) -> val keyObj = key.fromJsonString() val valueObj = value.toObject() if (keyObj != null && valueObj != null) { keyObj to valueObj } else { null } }.toMap() return customizationMap.map { (itemCustomMapping, itemStylesOverrides) -> val nativeItemMapping = BlazeWidgetItemCustomMapping( key = itemCustomMapping.key, value = itemCustomMapping.value ) val nativeItemOverrides = BlazeWidgetItemStyleOverrides( statusIndicator = widgetLayout.widgetItemStyle.statusIndicator.blazeDeepCopy(), imageBorder = widgetLayout.widgetItemStyle.image.border.blazeDeepCopy(), badge = widgetLayout.widgetItemStyle.badge.blazeDeepCopy(), ).mergedWith(itemStylesOverrides, context) nativeItemMapping to nativeItemOverrides }.toMap() } fun setDataSourceTypeFromMap(dataSourceMap: ReadableMap?) { dataSourceType = dataSourceMap?.extractDataSource() } companion object { private const val ARG_INDEX_IS_SILENT_REFRESH = 0 private const val ARG_INDEX_DATA_SOURCE_MAP = 0 private const val ARG_INDEX_UPDATE_DATA_SOURCE_IS_SILENT_REFRESH = 1 private const val ARG_INDEX_OVERRIDE_STYLE_MAP = 0 private const val ARG_INDEX_OVERRIDE_STYLE_SHOULD_UPDATE_UI = 1 } }