/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.fresco.vito.draweesupport

import android.content.Context
import android.content.res.TypedArray
import android.graphics.drawable.Drawable
import android.os.Build
import android.util.AttributeSet
import android.view.View
import com.facebook.drawee.R
import com.facebook.drawee.drawable.AutoRotateDrawable
import com.facebook.drawee.drawable.ScalingUtils.ScaleType
import com.facebook.drawee.generic.GenericDraweeHierarchyInflater
import com.facebook.fresco.vito.options.BorderOptions
import com.facebook.fresco.vito.options.ImageOptions
import com.facebook.fresco.vito.options.RoundingOptions

object VitoViewInflater {

  @JvmStatic
  fun inflateImageOptionsBuilder(
      context: Context,
      attributeSet: AttributeSet?
  ): ImageOptions.Builder {
    if (attributeSet == null) {
      return ImageOptions.create()
    }
    val attrs = context.obtainStyledAttributes(attributeSet, R.styleable.GenericDraweeHierarchy)
    try {
      return ImageOptions.create().apply {
        val indexCount = attrs.indexCount
        for (i in 0 until indexCount) {
          when (val attr = attrs.getIndex(i)) {
            R.styleable.GenericDraweeHierarchy_actualImageScaleType ->
                scale(getScaleType(attrs, attr))
            R.styleable.GenericDraweeHierarchy_placeholderImage ->
                placeholder(GenericDraweeHierarchyInflater.getDrawable(context, attrs, attr))
            R.styleable.GenericDraweeHierarchy_progressBarImage -> {
              val progressBarDrawable = getDrawable(context, attrs, attr)
              if (progressBarDrawable != null) {
                // wrap progress bar if auto-rotating requested
                val progressBarAutoRotateInterval =
                    attrs.getInteger(
                        R.styleable.GenericDraweeHierarchy_progressBarAutoRotateInterval, 0)
                progress(
                    if (progressBarAutoRotateInterval > 0) {
                      AutoRotateDrawable(progressBarDrawable, progressBarAutoRotateInterval)
                    } else {
                      progressBarDrawable
                    })
              }
            }
            R.styleable.GenericDraweeHierarchy_fadeDuration -> fadeDurationMs(attrs.getInt(attr, 0))
            R.styleable.GenericDraweeHierarchy_placeholderImageScaleType ->
                placeholderScaleType(getScaleType(attrs, attr))
            R.styleable.GenericDraweeHierarchy_failureImage ->
                errorDrawable(getDrawable(context, attrs, attr))
            R.styleable.GenericDraweeHierarchy_failureImageScaleType ->
                errorScaleType(getScaleType(attrs, attr))
            R.styleable.GenericDraweeHierarchy_progressBarImageScaleType ->
                progressScaleType(getScaleType(attrs, attr))
            R.styleable.GenericDraweeHierarchy_overlayImage ->
                overlay(getDrawable(context, attrs, attr))
            R.styleable.GenericDraweeHierarchy_roundAsCircle ->
                round(RoundingOptions.asCircle(antiAliasing = true, forceRoundAtDecode = true))
            R.styleable.GenericDraweeHierarchy_roundedCornerRadius -> {
              // Set rounded corner radii if requested
              val roundedCornerRadius = attrs.getDimensionPixelSize(attr, 0)
              if (roundedCornerRadius > 0) {
                val radius = roundedCornerRadius.toFloat()
                var roundTopLeft =
                    attrs.getBoolean(R.styleable.GenericDraweeHierarchy_roundTopLeft, true)
                var roundTopRight =
                    attrs.getBoolean(R.styleable.GenericDraweeHierarchy_roundTopRight, true)
                var roundBottomLeft =
                    attrs.getBoolean(R.styleable.GenericDraweeHierarchy_roundBottomLeft, true)
                var roundBottomRight =
                    attrs.getBoolean(R.styleable.GenericDraweeHierarchy_roundBottomRight, true)
                val roundTopStart =
                    attrs.getBoolean(R.styleable.GenericDraweeHierarchy_roundTopStart, true)
                val roundTopEnd =
                    attrs.getBoolean(R.styleable.GenericDraweeHierarchy_roundTopEnd, true)
                val roundBottomStart =
                    attrs.getBoolean(R.styleable.GenericDraweeHierarchy_roundBottomStart, true)
                val roundBottomEnd =
                    attrs.getBoolean(R.styleable.GenericDraweeHierarchy_roundBottomEnd, true)

                if (Build.VERSION.SDK_INT >= 17 &&
                    context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL) {
                  roundTopLeft = roundTopLeft && roundTopEnd
                  roundTopRight = roundTopRight && roundTopStart
                  roundBottomRight = roundBottomRight && roundBottomStart
                  roundBottomLeft = roundBottomLeft && roundBottomEnd
                } else {
                  roundTopLeft = roundTopLeft && roundTopStart
                  roundTopRight = roundTopRight && roundTopEnd
                  roundBottomRight = roundBottomRight && roundBottomEnd
                  roundBottomLeft = roundBottomLeft && roundBottomStart
                }
                round(
                    RoundingOptions.forCornerRadii(
                        if (roundTopLeft) radius else 0f,
                        if (roundTopRight) radius else 0f,
                        if (roundBottomRight) radius else 0f,
                        if (roundBottomLeft) radius else 0f))
              }
            }
            R.styleable.GenericDraweeHierarchy_roundingBorderWidth -> {
              val borderWidth = attrs.getDimensionPixelSize(attr, 0).toFloat()
              if (borderWidth != 0f) {
                borders(
                    BorderOptions.create(
                        attrs.getColor(R.styleable.GenericDraweeHierarchy_roundingBorderColor, 0),
                        borderWidth,
                        attrs
                            .getDimensionPixelSize(
                                R.styleable.GenericDraweeHierarchy_roundingBorderPadding, 0)
                            .toFloat()))
              }
            }
            R.styleable.GenericDraweeHierarchy_viewAspectRatio,
            R.styleable.GenericDraweeHierarchy_retryImage,
            R.styleable.GenericDraweeHierarchy_retryImageScaleType,
            R.styleable.GenericDraweeHierarchy_roundWithOverlayColor,
            R.styleable.GenericDraweeHierarchy_pressedStateOverlayImage,
            R.styleable.GenericDraweeHierarchy_backgroundImage ->
                throw UnsupportedOperationException("Not supported for Vito")
          }
        }
      }
    } finally {
      attrs.recycle()
    }
  }

  private fun getScaleType(attrs: TypedArray, attrId: Int): ScaleType? =
      GenericDraweeHierarchyInflater.getScaleTypeFromXml(attrs, attrId)

  private fun getDrawable(context: Context, attrs: TypedArray, attrId: Int): Drawable? =
      GenericDraweeHierarchyInflater.getDrawable(context, attrs, attrId)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.fresco.vito.draweesupport

import android.graphics.drawable.Animatable
import android.graphics.drawable.Drawable
import com.facebook.drawee.backends.pipeline.info.ImageOrigin
import com.facebook.drawee.controller.ControllerListener
import com.facebook.imagepipeline.image.ImageInfo
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
import org.mockito.kotlin.eq
import org.mockito.kotlin.isNull
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.verifyZeroInteractions

class ControllerListenerWrapperTest {

  private lateinit var controllerListener: ControllerListener<ImageInfo>
  private lateinit var controllerListenerWrapper: ControllerListenerWrapper

  @Before
  fun setup() {
    controllerListener = mock()
    controllerListenerWrapper = ControllerListenerWrapper(controllerListener)
  }

  @Test
  fun testOnSubmit() {
    controllerListenerWrapper.onSubmit(ID.toLong(), CALLER_CONTEXT)
    verify(controllerListener).onSubmit(eq(STRING_ID), eq(CALLER_CONTEXT))
    verifyNoMoreInteractions(controllerListener)
  }

  @Test
  fun testOnPlaceholderSet() {
    val placeholder = mock<Drawable>()
    controllerListenerWrapper.onPlaceholderSet(ID.toLong(), placeholder)
    verifyZeroInteractions(controllerListener)
  }

  @Test
  fun testOnIntermediateImageFailed() {
    val throwable = Throwable("Message")
    controllerListenerWrapper.onIntermediateImageFailed(ID.toLong(), throwable)
    verify(controllerListener).onIntermediateImageFailed(eq(STRING_ID), eq(throwable))
    verifyNoMoreInteractions(controllerListener)
  }

  @Test
  fun testOnFailure() {
    val throwable = Throwable("Message")
    val errorDrawable = mock<Drawable>()
    controllerListenerWrapper.onFailure(ID.toLong(), errorDrawable, throwable)
    verify(controllerListener).onFailure(eq(STRING_ID), eq(throwable))
    verifyNoMoreInteractions(controllerListener)
  }

  @Test
  fun testOnRelease() {
    controllerListenerWrapper.onRelease(ID.toLong())
    verify(controllerListener).onRelease(eq(STRING_ID))
    verifyNoMoreInteractions(controllerListener)
  }

  @Test
  fun testOnIntermediateImageSet() {
    val imageInfo = mock<ImageInfo>()
    controllerListenerWrapper.onIntermediateImageSet(ID.toLong(), imageInfo)
    verify(controllerListener).onIntermediateImageSet(eq(STRING_ID), eq(imageInfo))
    verifyNoMoreInteractions(controllerListener)
  }

  @Test
  fun testOnFinalImageSetNormalDrawable() {
    val drawable = mock<Drawable>()
    val imageInfo = mock<ImageInfo>()
    controllerListenerWrapper.onFinalImageSet(ID.toLong(), ImageOrigin.DISK, imageInfo, drawable)
    verify(controllerListener).onFinalImageSet(eq(STRING_ID), eq(imageInfo), isNull())
    verifyNoMoreInteractions(controllerListener)
  }

  @Test
  fun testOnFinalImageSetAnimatedDrawable() {
    val drawable = mock<AnimatedDrawable>()
    val imageInfo = mock<ImageInfo>()
    controllerListenerWrapper.onFinalImageSet(ID.toLong(), ImageOrigin.DISK, imageInfo, drawable)
    verify(controllerListener).onFinalImageSet(eq(STRING_ID), eq(imageInfo), eq(drawable))
    verifyNoMoreInteractions(controllerListener)
  }

  @Test
  fun testCreateWrapper() {
    val wrapper = ControllerListenerWrapper.create(controllerListener)
    assertThat(wrapper).isNotNull
  }

  @Test
  fun testCreateNullWrapper() {
    val wrapper = ControllerListenerWrapper.create(null)
    assertThat(wrapper).isNull()
  }

  companion object {
    private const val ID = 123
    private const val STRING_ID = "v123"
    private const val CALLER_CONTEXT = "caller-context"
  }

  abstract class AnimatedDrawable : Drawable(), Animatable
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.fresco.vito.init

import com.facebook.callercontext.CallerContextVerifier
import com.facebook.common.executors.UiThreadImmediateExecutorService
import com.facebook.common.internal.Supplier
import com.facebook.common.internal.Suppliers
import com.facebook.fresco.vito.core.DefaultFrescoVitoConfig
import com.facebook.fresco.vito.core.FrescoVitoConfig
import com.facebook.fresco.vito.core.ImagePipelineUtils
import com.facebook.fresco.vito.core.VitoImagePerfListener
import com.facebook.fresco.vito.core.impl.BaseVitoImagePerfListener
import com.facebook.fresco.vito.core.impl.DefaultImageDecodeOptionsProviderImpl
import com.facebook.fresco.vito.core.impl.ImagePipelineUtilsImpl
import com.facebook.fresco.vito.core.impl.ImagePipelineUtilsImpl.CircularBitmapRounding
import com.facebook.fresco.vito.nativecode.NativeCircularBitmapRounding
import com.facebook.fresco.vito.provider.FrescoVitoProvider
import com.facebook.fresco.vito.provider.impl.DefaultFrescoVitoProvider
import com.facebook.fresco.vito.provider.impl.NoOpCallerContextVerifier
import com.facebook.imagepipeline.core.ImagePipeline
import com.facebook.imagepipeline.core.ImagePipelineFactory
import java.util.concurrent.Executor

class FrescoVito {

  companion object {
    @JvmStatic private var isInitialized = false

    /**
     * Initialize Fresco Vito.
     *
     * Note: Fresco has to be initialized already, via Fresco.initialize(...)
     *
     * @param resources resources for the application
     * @param imagePipeline the image pipeline used for image loading
     * @param debugOverlayEnabledSupplier debug overlay toggle
     */
    @Synchronized
    @JvmOverloads
    @JvmStatic
    fun initialize(
        imagePipeline: ImagePipeline? = null,
        lightweightBackgroundThreadExecutor: Executor? = null,
        uiThreadExecutor: Executor? = null,
        debugOverlayEnabledSupplier: Supplier<Boolean?>? = null,
        useNativeCode: Supplier<Boolean> = Suppliers.BOOLEAN_TRUE,
        vitoConfig: FrescoVitoConfig = DefaultFrescoVitoConfig(),
        callerContextVerifier: CallerContextVerifier = NoOpCallerContextVerifier,
        vitoImagePerfListener: VitoImagePerfListener = BaseVitoImagePerfListener()
    ) {
      if (isInitialized) {
        return
      }
      val imagePipeline = imagePipeline ?: ImagePipelineFactory.getInstance().imagePipeline
      val lightweightBackgroundThreadExecutor =
          lightweightBackgroundThreadExecutor
              ?: imagePipeline.config.executorSupplier.forLightweightBackgroundTasks()
      val uiThreadExecutor = uiThreadExecutor ?: UiThreadImmediateExecutorService.getInstance()
      initialize(
          DefaultFrescoVitoProvider(
              vitoConfig,
              imagePipeline,
              createImagePipelineUtils(useNativeCode),
              lightweightBackgroundThreadExecutor,
              uiThreadExecutor,
              debugOverlayEnabledSupplier,
              callerContextVerifier,
              vitoImagePerfListener))
    }

    /**
     * Initialize Fresco Vito. Note: Fresco has to be initialized already, via
     * Fresco.initialize(...)
     *
     * @param providerImplementation the provider implementation to be used
     */
    @Synchronized
    fun initialize(providerImplementation: FrescoVitoProvider.Implementation) {
      if (isInitialized) {
        return
      }
      FrescoVitoProvider.setImplementation(providerImplementation)
      isInitialized = true
    }

    fun createImagePipelineUtils(
        useNativeRounding: Supplier<Boolean>,
        useFastNativeRounding: Supplier<Boolean> = Suppliers.BOOLEAN_FALSE
    ): ImagePipelineUtils {
      val circularBitmapRounding: CircularBitmapRounding? =
          if (useNativeRounding.get()) NativeCircularBitmapRounding(useFastNativeRounding) else null
      return ImagePipelineUtilsImpl(DefaultImageDecodeOptionsProviderImpl(circularBitmapRounding))
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.fresco.vito.ktx

import android.net.Uri
import com.facebook.fresco.vito.source.ImageSource
import com.facebook.fresco.vito.source.ImageSourceProvider

object ImageSourceExtensions {

  fun Uri.asImageSource(): ImageSource = ImageSourceProvider.forUri(this)

  fun String.asImageSource(): ImageSource = ImageSourceProvider.forUri(this)

  fun ImageSource.withLowResImage(lowResImageSource: ImageSource): ImageSource =
      ImageSourceProvider.increasingQuality(lowResImageSource, this)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.fresco.vito.ktx

import android.net.Uri
import android.view.View
import com.facebook.fresco.vito.listener.ImageListener
import com.facebook.fresco.vito.options.ImageOptions
import com.facebook.fresco.vito.view.VitoView

object ViewExtensions {

  fun View.show(uri: Uri) {
    VitoView.show(uri, this)
  }

  fun View.show(uri: Uri, callerContext: Any) {
    VitoView.show(uri, callerContext, this)
  }

  fun View.show(uri: Uri, imageOptions: ImageOptions) {
    VitoView.show(uri, imageOptions, this)
  }

  fun View.show(
      uri: Uri,
      imageOptions: ImageOptions,
      imageListener: ImageListener,
      callerContext: Any? = null
  ) {
    VitoView.show(uri, imageOptions, callerContext, imageListener, this)
  }

  fun View.show(uri: Uri, imageOptions: ImageOptions, callerContext: Any) {
    VitoView.show(uri, imageOptions, callerContext, this)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.fresco.vito.litho

import com.facebook.litho.WorkingRange

/**
 * A working range targeting components that are above the viewport (i.e., components the user has
 * scrolled past).
 */
class AboveViewportWorkingRange : WorkingRange {

  private fun isInRange(position: Int, firstVisibleIndex: Int): Boolean =
      position < firstVisibleIndex

  override fun shouldEnterRange(
      position: Int,
      firstVisibleIndex: Int,
      lastVisibleIndex: Int,
      firstFullyVisibleIndex: Int,
      lastFullyVisibleIndex: Int
  ): Boolean = isInRange(position, firstVisibleIndex)

  override fun shouldExitRange(
      position: Int,
      firstVisibleIndex: Int,
      lastVisibleIndex: Int,
      firstFullyVisibleIndex: Int,
      lastFullyVisibleIndex: Int
  ): Boolean = !isInRange(position, firstVisibleIndex)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.fresco.vito.litho

import com.facebook.litho.WorkingRange

/**
 * A working range targeting components that are below the viewport (i.e., components the user will
 * eventually reach if they scroll long enough).
 */
class BelowViewportWorkingRange(minDistance: Int, maxDistance: Int) : WorkingRange {

  private val distanceRange: IntRange = minDistance..maxDistance

  private fun isInRange(position: Int, lastVisibleIndex: Int): Boolean {
    val delta = position - lastVisibleIndex
    return delta in distanceRange
  }

  override fun shouldEnterRange(
      position: Int,
      firstVisibleIndex: Int,
      lastVisibleIndex: Int,
      firstFullyVisibleIndex: Int,
      lastFullyVisibleIndex: Int
  ): Boolean = isInRange(position, lastVisibleIndex)

  override fun shouldExitRange(
      position: Int,
      firstVisibleIndex: Int,
      lastVisibleIndex: Int,
      firstFullyVisibleIndex: Int,
      lastFullyVisibleIndex: Int
  ): Boolean = !isInRange(position, lastVisibleIndex)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.fresco.vito.litho

import android.app.Activity
import android.content.Context
import android.content.pm.ActivityInfo
import android.graphics.Rect
import android.net.Uri
import android.os.Build
import android.view.View
import androidx.core.util.ObjectsCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import com.facebook.common.callercontext.ContextChain
import com.facebook.datasource.DataSource
import com.facebook.fresco.ui.common.OnFadeListener
import com.facebook.fresco.vito.core.FrescoDrawableInterface
import com.facebook.fresco.vito.core.VitoImageRequest
import com.facebook.fresco.vito.listener.ImageListener
import com.facebook.fresco.vito.options.ImageOptions
import com.facebook.fresco.vito.provider.FrescoVitoProvider
import com.facebook.fresco.vito.source.ImageSource
import com.facebook.fresco.vito.source.ImageSourceProvider
import com.facebook.imagepipeline.listener.RequestListener
import com.facebook.litho.AccessibilityRole
import com.facebook.litho.BoundaryWorkingRange
import com.facebook.litho.ComponentContext
import com.facebook.litho.ComponentLayout
import com.facebook.litho.ContextUtils
import com.facebook.litho.Diff
import com.facebook.litho.Output
import com.facebook.litho.Size
import com.facebook.litho.StateValue
import com.facebook.litho.annotations.CachedValue
import com.facebook.litho.annotations.FromBoundsDefined
import com.facebook.litho.annotations.FromPrepare
import com.facebook.litho.annotations.MountSpec
import com.facebook.litho.annotations.MountingType
import com.facebook.litho.annotations.OnBind
import com.facebook.litho.annotations.OnBoundsDefined
import com.facebook.litho.annotations.OnCalculateCachedValue
import com.facebook.litho.annotations.OnCreateInitialState
import com.facebook.litho.annotations.OnCreateMountContent
import com.facebook.litho.annotations.OnEnteredRange
import com.facebook.litho.annotations.OnExitedRange
import com.facebook.litho.annotations.OnMeasure
import com.facebook.litho.annotations.OnMount
import com.facebook.litho.annotations.OnPopulateAccessibilityNode
import com.facebook.litho.annotations.OnPrepare
import com.facebook.litho.annotations.OnRegisterRanges
import com.facebook.litho.annotations.OnUnbind
import com.facebook.litho.annotations.OnUnmount
import com.facebook.litho.annotations.Prop
import com.facebook.litho.annotations.PropDefault
import com.facebook.litho.annotations.ResType
import com.facebook.litho.annotations.ShouldUpdate
import com.facebook.litho.annotations.State
import com.facebook.litho.annotations.TreeProp
import com.facebook.litho.utils.MeasureUtils
import java.util.concurrent.atomic.AtomicReference

/** Simple Fresco Vito component for Litho */
@MountSpec(isPureRender = true, canPreallocate = true, poolSize = 15)
object FrescoVitoImage2Spec {

  @PropDefault const val imageAspectRatio = 1f

  @PropDefault val prefetch = Prefetch.AUTO

  @PropDefault const val mutateDrawables = true

  @JvmStatic
  @OnCreateMountContent(mountingType = MountingType.DRAWABLE)
  fun onCreateMountContent(c: Context?): FrescoDrawableInterface =
      FrescoVitoProvider.getController().createDrawable()

  @JvmStatic
  @OnCreateInitialState
  fun onCreateInitialState(
      context: ComponentContext,
      workingRangePrefetchData: StateValue<AtomicReference<DataSource<Void>>>
  ) {
    if (FrescoVitoProvider.hasBeenInitialized() &&
        FrescoVitoProvider.getConfig().prefetchConfig.prefetchWithWorkingRange()) {
      workingRangePrefetchData.set(AtomicReference())
    }
  }

  @JvmStatic
  @OnMeasure
  fun onMeasure(
      c: ComponentContext,
      layout: ComponentLayout,
      widthSpec: Int,
      heightSpec: Int,
      size: Size,
      @Prop(optional = true, resType = ResType.FLOAT) imageAspectRatio: Float
  ) {
    MeasureUtils.measureWithAspectRatio(widthSpec, heightSpec, imageAspectRatio, size)
  }

  @JvmStatic
  @OnCalculateCachedValue(name = "requestCachedValue")
  fun onCalculateImageRequest(
      c: ComponentContext,
      @Prop(optional = true) uri: Uri?,
      @Prop(optional = true) imageSource: ImageSource?,
      @Prop(optional = true) imageOptions: ImageOptions?
  ): VitoImageRequest? =
      if (imageOptions?.experimentalDynamicSize == true) {
        null
      } else {
        createVitoImageRequest(c, uri, imageSource, imageOptions, null)
      }

  private fun createVitoImageRequest(
      c: ComponentContext,
      uri: Uri?,
      imageSource: ImageSource?,
      imageOptions: ImageOptions?,
      viewportRect: Rect?
  ): VitoImageRequest =
      FrescoVitoProvider.getImagePipeline()
          .createImageRequest(
              c.resources,
              imageSource ?: ImageSourceProvider.forUri(uri),
              imageOptions,
              viewportRect)

  @JvmStatic
  @OnPrepare
  fun onPrepare(
      c: ComponentContext,
      @Prop(optional = true) callerContext: Any?,
      @Prop(optional = true) prefetch: Prefetch?,
      @Prop(optional = true) prefetchRequestListener: RequestListener?,
      @CachedValue requestCachedValue: VitoImageRequest?,
      prefetchDataSource: Output<DataSource<Void>>
  ) {
    if (requestCachedValue == null) {
      return
    }
    val config = FrescoVitoProvider.getConfig().prefetchConfig
    if (shouldPrefetchInOnPrepare(prefetch)) {
      prefetchDataSource.set(
          FrescoVitoProvider.getPrefetcher()
              .prefetch(
                  config.prefetchTargetOnPrepare(),
                  requestCachedValue,
                  callerContext,
                  prefetchRequestListener,
                  "OnPrepare"))
    }
  }

  @JvmStatic
  @OnMount
  fun onMount(
      c: ComponentContext,
      frescoDrawable: FrescoDrawableInterface,
      @Prop(optional = true) imageListener: ImageListener?,
      @Prop(optional = true) callerContext: Any?,
      @Prop(optional = true) onFadeListener: OnFadeListener?,
      @Prop(optional = true) mutateDrawables: Boolean,
      @CachedValue requestCachedValue: VitoImageRequest?,
      @FromBoundsDefined requestFromBoundsDefined: VitoImageRequest?,
      @FromPrepare prefetchDataSource: DataSource<Void?>?,
      @FromBoundsDefined viewportDimensions: Rect,
      @State workingRangePrefetchData: AtomicReference<DataSource<Void>>?,
      @TreeProp contextChain: ContextChain?
  ) {
    val request = requestCachedValue ?: requestFromBoundsDefined
    frescoDrawable.setMutateDrawables(mutateDrawables)
    if (FrescoVitoProvider.getConfig().useBindOnly()) {
      return
    }

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
        FrescoVitoProvider.getConfig().enableWindowWideColorGamut()) {
      val activity: Activity? = ContextUtils.findActivityInContext(c.androidContext)
      activity?.window?.colorMode = ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT
    }

    FrescoVitoProvider.getController()
        .fetch(
            frescoDrawable = frescoDrawable,
            imageRequest = request!!,
            callerContext = callerContext,
            contextChain = contextChain,
            listener = imageListener,
            onFadeListener = onFadeListener,
            viewportDimensions = viewportDimensions)
    frescoDrawable.imagePerfListener.onImageMount(frescoDrawable)
    prefetchDataSource?.close()
    if (FrescoVitoProvider.getConfig().prefetchConfig.cancelPrefetchWhenFetched()) {
      cancelWorkingRangePrefetch(workingRangePrefetchData)
    }
  }

  @JvmStatic
  @OnBind
  fun onBind(
      c: ComponentContext,
      frescoDrawable: FrescoDrawableInterface,
      @Prop(optional = true) imageListener: ImageListener?,
      @Prop(optional = true) onFadeListener: OnFadeListener?,
      @Prop(optional = true) callerContext: Any?,
      @TreeProp contextChain: ContextChain?,
      @CachedValue requestCachedValue: VitoImageRequest?,
      @FromBoundsDefined requestFromBoundsDefined: VitoImageRequest?,
      @FromPrepare prefetchDataSource: DataSource<Void?>?,
      @FromBoundsDefined viewportDimensions: Rect,
      @State workingRangePrefetchData: AtomicReference<DataSource<Void>>?
  ) {
    val request = requestCachedValue ?: requestFromBoundsDefined
    // We fetch in both mount and bind in case an unbind event triggered a delayed release.
    // We'll only trigger an actual fetch if needed. Most of the time, this will be a no-op.
    FrescoVitoProvider.getController()
        .fetch(
            frescoDrawable = frescoDrawable,
            imageRequest = request!!,
            callerContext = callerContext,
            contextChain = contextChain,
            listener = imageListener,
            onFadeListener = onFadeListener,
            viewportDimensions = viewportDimensions)
    frescoDrawable.imagePerfListener.onImageBind(frescoDrawable)
    prefetchDataSource?.close()
    if (FrescoVitoProvider.getConfig().prefetchConfig.cancelPrefetchWhenFetched()) {
      cancelWorkingRangePrefetch(workingRangePrefetchData)
    }
  }

  @JvmStatic
  @OnUnbind
  fun onUnbind(
      c: ComponentContext,
      frescoDrawable: FrescoDrawableInterface,
      @FromPrepare prefetchDataSource: DataSource<Void>?
  ) {
    frescoDrawable.imagePerfListener.onImageUnbind(frescoDrawable)
    if (FrescoVitoProvider.getConfig().useBindOnly()) {
      FrescoVitoProvider.getController().releaseImmediately(frescoDrawable)
    } else {
      FrescoVitoProvider.getController().releaseDelayed(frescoDrawable)
    }
    prefetchDataSource?.close()
  }

  @JvmStatic
  @OnUnmount
  fun onUnmount(
      c: ComponentContext,
      frescoDrawable: FrescoDrawableInterface,
      @FromPrepare prefetchDataSource: DataSource<Void>?
  ) {
    frescoDrawable.imagePerfListener.onImageUnmount(frescoDrawable)
    if (FrescoVitoProvider.getConfig().useBindOnly()) {
      return
    }
    FrescoVitoProvider.getController().release(frescoDrawable)
    prefetchDataSource?.close()
  }

  @JvmStatic
  @ShouldUpdate(onMount = true)
  fun shouldUpdate(
      @Prop(optional = true) uri: Diff<Uri>,
      @Prop(optional = true) imageSource: Diff<ImageSource>,
      @Prop(optional = true) imageOptions: Diff<ImageOptions>,
      @Prop(optional = true, resType = ResType.FLOAT) imageAspectRatio: Diff<Float>,
      @Prop(optional = true) imageListener: Diff<ImageListener>
  ): Boolean =
      !ObjectsCompat.equals(uri.previous, uri.next) ||
          !ObjectsCompat.equals(imageSource.previous, imageSource.next) ||
          !ObjectsCompat.equals(imageOptions.previous, imageOptions.next) ||
          !ObjectsCompat.equals(imageAspectRatio.previous, imageAspectRatio.next) ||
          !ObjectsCompat.equals(imageListener.previous, imageListener.next)

  @JvmStatic
  @OnPopulateAccessibilityNode
  fun onPopulateAccessibilityNode(
      c: ComponentContext,
      host: View,
      node: AccessibilityNodeInfoCompat
  ) {
    node.className = AccessibilityRole.IMAGE
  }

  @JvmStatic
  @OnBoundsDefined
  fun onBoundsDefined(
      c: ComponentContext,
      layout: ComponentLayout,
      viewportDimensions: Output<Rect>,
      requestFromBoundsDefined: Output<VitoImageRequest>,
      @Prop(optional = true) uri: Uri?,
      @Prop(optional = true) imageSource: ImageSource?,
      @Prop(optional = true) imageOptions: ImageOptions?
  ) {
    val width = layout.width
    val height = layout.height
    var paddingX = 0
    var paddingY = 0
    if (layout.isPaddingSet) {
      paddingX = layout.paddingLeft + layout.paddingRight
      paddingY = layout.paddingTop + layout.paddingBottom
    }
    val viewportRect = Rect(0, 0, width - paddingX, height - paddingY)
    viewportDimensions.set(viewportRect)
    if (imageOptions != null && imageOptions.experimentalDynamicSize) {
      requestFromBoundsDefined.set(
          createVitoImageRequest(c, uri, imageSource, imageOptions, viewportRect))
    }
  }

  @JvmStatic
  @OnEnteredRange(name = "imagePrefetch")
  fun onEnteredWorkingRange(
      c: ComponentContext,
      @Prop(optional = true) prefetch: Prefetch?,
      @Prop(optional = true) callerContext: Any?,
      @CachedValue requestCachedValue: VitoImageRequest?,
      @FromPrepare prefetchDataSource: DataSource<Void>?,
      @State workingRangePrefetchData: AtomicReference<DataSource<Void>>?
  ) {
    if (requestCachedValue == null || workingRangePrefetchData == null) {
      return
    }
    cancelWorkingRangePrefetch(workingRangePrefetchData)
    val prefetchConfig = FrescoVitoProvider.getConfig().prefetchConfig
    if (shouldPrefetchWithWorkingRange(prefetch)) {
      workingRangePrefetchData.set(
          FrescoVitoProvider.getPrefetcher()
              .prefetch(
                  prefetchConfig.prefetchTargetWorkingRange(),
                  requestCachedValue,
                  callerContext,
                  null,
                  "OnEnteredRange"))
      if (prefetchDataSource != null &&
          prefetchConfig.cancelOnPreparePrefetchWhenWorkingRangePrefetch()) {
        prefetchDataSource.close()
      }
    }
  }

  @JvmStatic
  @OnExitedRange(name = "imagePrefetch")
  fun onExitedWorkingRange(
      c: ComponentContext,
      @State workingRangePrefetchData: AtomicReference<DataSource<Void>>?
  ) {
    cancelWorkingRangePrefetch(workingRangePrefetchData)
  }

  @JvmStatic
  @OnEnteredRange(name = "below3")
  fun onEnteredBelow3WorkingRange(
      c: ComponentContext,
      @Prop(optional = true) callerContext: Any?,
      @CachedValue requestCachedValue: VitoImageRequest?
  ) {
    if (requestCachedValue == null) {
      return
    }
    FrescoVitoProvider.getPrefetcher()
        .setDistanceToViewport(3, callerContext, getUri(requestCachedValue), "FrescoVitoImage2")
  }

  @JvmStatic
  @OnEnteredRange(name = "below2")
  fun onEnteredBelow2WorkingRange(
      c: ComponentContext,
      @Prop(optional = true) callerContext: Any?,
      @CachedValue requestCachedValue: VitoImageRequest?
  ) {
    if (requestCachedValue == null) {
      return
    }
    FrescoVitoProvider.getPrefetcher()
        .setDistanceToViewport(2, callerContext, getUri(requestCachedValue), "FrescoVitoImage2")
  }

  @JvmStatic
  @OnEnteredRange(name = "below1")
  fun onEnteredBelowWorkingRange(
      c: ComponentContext,
      @Prop(optional = true) callerContext: Any?,
      @CachedValue requestCachedValue: VitoImageRequest?
  ) {
    if (requestCachedValue == null) {
      return
    }
    FrescoVitoProvider.getPrefetcher()
        .setDistanceToViewport(1, callerContext, getUri(requestCachedValue), "FrescoVitoImage2")
  }

  @JvmStatic
  @OnEnteredRange(name = "visible")
  fun onEnteredVisibleWorkingRange(
      c: ComponentContext,
      @Prop(optional = true) callerContext: Any?,
      @CachedValue requestCachedValue: VitoImageRequest?
  ) {
    if (requestCachedValue == null) {
      return
    }
    FrescoVitoProvider.getPrefetcher()
        .setDistanceToViewport(0, callerContext, getUri(requestCachedValue), "FrescoVitoImage2")
  }

  @JvmStatic
  @OnEnteredRange(name = "above")
  fun onEnteredAboveWorkingRange(
      c: ComponentContext,
      @Prop(optional = true) callerContext: Any?,
      @CachedValue requestCachedValue: VitoImageRequest?
  ) {
    if (requestCachedValue == null) {
      return
    }
    FrescoVitoProvider.getPrefetcher()
        .setDistanceToViewport(-1, callerContext, getUri(requestCachedValue), "FrescoVitoImage2")
  }

  private fun getUri(requestCachedValue: VitoImageRequest): Uri? =
      requestCachedValue.finalImageRequest?.sourceUri

  @JvmStatic
  @OnRegisterRanges
  fun registerWorkingRanges(c: ComponentContext, @Prop(optional = true) prefetch: Prefetch?) {
    if (FrescoVitoProvider.hasBeenInitialized()) {
      val prefetchConfig = FrescoVitoProvider.getConfig().prefetchConfig
      if (shouldPrefetchWithWorkingRange(prefetch)) {
        FrescoVitoImage2.registerImagePrefetchWorkingRange(
            c, BoundaryWorkingRange(prefetchConfig.prefetchWorkingRangeSize()))
      }
      if (prefetchConfig.prioritizeWithWorkingRange()) {
        FrescoVitoImage2.registerBelow3WorkingRange(c, BelowViewportWorkingRange(3, Int.MAX_VALUE))
        FrescoVitoImage2.registerBelow2WorkingRange(c, BelowViewportWorkingRange(2, 2))
        FrescoVitoImage2.registerBelow1WorkingRange(c, BelowViewportWorkingRange(1, 1))
        FrescoVitoImage2.registerVisibleWorkingRange(c, InViewportWorkingRange())
        FrescoVitoImage2.registerAboveWorkingRange(c, AboveViewportWorkingRange())
      }
    }
  }

  @JvmStatic
  fun cancelWorkingRangePrefetch(prefetchData: AtomicReference<DataSource<Void>>?) {
    if (prefetchData == null) {
      return
    }
    val dataSource = prefetchData.get()
    dataSource?.close()
    prefetchData.set(null)
  }

  @JvmStatic
  fun shouldPrefetchInOnPrepare(prefetch: Prefetch?): Boolean =
      when (prefetch ?: Prefetch.AUTO) {
        Prefetch.YES -> true
        Prefetch.NO -> false
        else -> FrescoVitoProvider.getConfig().prefetchConfig.prefetchInOnPrepare()
      }

  @JvmStatic
  fun shouldPrefetchWithWorkingRange(prefetch: Prefetch?): Boolean =
      when (prefetch ?: Prefetch.AUTO) {
        Prefetch.YES -> true
        Prefetch.NO -> false
        else -> FrescoVitoProvider.getConfig().prefetchConfig.prefetchWithWorkingRange()
      }

  enum class Prefetch {
    AUTO,
    YES,
    NO;

    companion object {
      @JvmStatic
      fun parsePrefetch(value: Long): Prefetch {
        if (value == 2L) {
          return NO
        }
        return if (value == 1L) {
          YES
        } else {
          AUTO
        }
      }
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.fresco.vito.litho

import android.graphics.drawable.Drawable
import android.view.View
import android.widget.ImageView
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import com.facebook.drawee.drawable.ScaleTypeDrawable
import com.facebook.drawee.drawable.ScalingUtils
import com.facebook.fresco.ui.common.OnFadeListener
import com.facebook.fresco.vito.listener.BaseImageListener
import com.facebook.fresco.vito.listener.ForwardingImageListener
import com.facebook.fresco.vito.listener.ImageListener
import com.facebook.fresco.vito.litho.FrescoVitoImage2Spec.Prefetch
import com.facebook.fresco.vito.options.ImageOptions
import com.facebook.fresco.vito.source.ImageSource
import com.facebook.imagepipeline.listener.RequestListener
import com.facebook.litho.AccessibilityRole
import com.facebook.litho.ClickEvent
import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.EventHandler
import com.facebook.litho.LongClickEvent
import com.facebook.litho.StateValue
import com.facebook.litho.TouchEvent
import com.facebook.litho.annotations.LayoutSpec
import com.facebook.litho.annotations.OnCreateInitialState
import com.facebook.litho.annotations.OnCreateLayout
import com.facebook.litho.annotations.OnEvent
import com.facebook.litho.annotations.OnPopulateAccessibilityNode
import com.facebook.litho.annotations.OnUpdateState
import com.facebook.litho.annotations.Prop
import com.facebook.litho.annotations.PropDefault
import com.facebook.litho.annotations.ResType
import com.facebook.litho.annotations.State
import com.facebook.litho.widget.Image

@LayoutSpec
object FrescoVitoTapToRetryImageSpec {

  @PropDefault const val maxTapCount = Int.MAX_VALUE

  @PropDefault const val imageAspectRatio = FrescoVitoImage2Spec.imageAspectRatio

  @PropDefault val prefetch = FrescoVitoImage2Spec.prefetch

  @JvmStatic
  @OnCreateInitialState
  fun onCreateInitialState(
      c: ComponentContext,
      isTapToRetry: StateValue<Boolean>,
      tapCount: StateValue<Int>,
      useFallbackImageSource: StateValue<Boolean>,
      @Prop(optional = true) isInitialTapToLoad: Boolean
  ) {
    isTapToRetry.set(isInitialTapToLoad)
    tapCount.set(if (isInitialTapToLoad) 1 else 0)
    useFallbackImageSource.set(false)
  }

  @JvmStatic
  @OnCreateLayout
  fun onCreateLayout(
      c: ComponentContext,
      @Prop imageSource: ImageSource?,
      @Prop(optional = true) fallbackImageSource: ImageSource?,
      @Prop(optional = true) callerContext: Any?,
      @Prop(optional = true, resType = ResType.FLOAT) imageAspectRatio: Float,
      @Prop(optional = true) imageClickHandler: EventHandler<ClickEvent>?,
      @Prop(optional = true) imageLongClickHandler: EventHandler<LongClickEvent>?,
      @Prop(optional = true) imageTouchHandler: EventHandler<TouchEvent>?,
      @Prop(optional = true) imageListener: ImageListener?,
      @Prop(optional = true) imageOptions: ImageOptions?,
      @Prop(optional = true) maxTapCount: Int,
      @Prop(optional = true) onFadeListener: OnFadeListener?,
      @Prop(optional = true) prefetch: Prefetch?,
      @Prop(optional = true) prefetchRequestListener: RequestListener?,
      @Prop(resType = ResType.DRAWABLE, optional = true) retryImage: Drawable?,
      @Prop(optional = true) retryImageScaleType: ScalingUtils.ScaleType?,
      @State isTapToRetry: Boolean,
      @State useFallbackImageSource: Boolean,
      @State tapCount: Int
  ): Component {
    if (isTapToRetry) {
      val scaledRetryDrawable =
          if (retryImageScaleType == null || retryImage == null) retryImage
          else ScaleTypeDrawable(retryImage, retryImageScaleType)
      return Image.create(c)
          .drawable(scaledRetryDrawable)
          .scaleType(ImageView.ScaleType.FIT_XY)
          .clickHandler(FrescoVitoTapToRetryImage.onRetryClickEvent(c))
          .aspectRatio(imageAspectRatio)
          .build()
    }
    val internalListener: ImageListener =
        object : BaseImageListener() {
          override fun onFailure(id: Long, error: Drawable?, throwable: Throwable?) {
            if (tapCount < maxTapCount) {
              FrescoVitoTapToRetryImage.onImageFailure(c)
            }
          }
        }
    val useFallback = fallbackImageSource != null && useFallbackImageSource
    return FrescoVitoImage2.create(c)
        .callerContext(callerContext)
        .imageAspectRatio(imageAspectRatio)
        .imageListener(ForwardingImageListener.create(internalListener, imageListener))
        .imageOptions(imageOptions)
        .imageSource(if (useFallback) fallbackImageSource else imageSource)
        .onFadeListener(onFadeListener)
        .prefetch(prefetch)
        .clickHandler(imageClickHandler)
        .longClickHandler(imageLongClickHandler)
        .touchHandler(imageTouchHandler)
        .prefetchRequestListener(prefetchRequestListener)
        .build()
  }

  @JvmStatic
  @OnPopulateAccessibilityNode
  fun onPopulateAccessibilityNode(
      c: ComponentContext,
      host: View,
      node: AccessibilityNodeInfoCompat
  ) {
    node.className = AccessibilityRole.IMAGE
  }

  @JvmStatic
  @OnUpdateState
  fun onImageFailure(
      isTapToRetry: StateValue<Boolean>,
      tapCount: StateValue<Int>,
      useFallbackImageSource: StateValue<Boolean>
  ) {
    val oldTapCount = tapCount.get()
    val newTapCount = if (oldTapCount == null) 1 else oldTapCount + 1
    tapCount.set(newTapCount)
    isTapToRetry.set(true)
    useFallbackImageSource.set(true)
  }

  @JvmStatic
  @OnUpdateState
  fun onTapToRetry(isTapToRetry: StateValue<Boolean>) {
    isTapToRetry.set(false)
  }

  @JvmStatic
  @OnEvent(ClickEvent::class)
  fun onRetryClickEvent(c: ComponentContext) {
    FrescoVitoTapToRetryImage.onTapToRetry(c)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.fresco.vito.litho

import com.facebook.litho.WorkingRange

/**
 * Working range targeting components that are in the viewport (i.e., components that are visible).
 */
class InViewportWorkingRange : WorkingRange {

  @Suppress("ConvertTwoComparisonsToRangeCheck")
  private fun isInRange(position: Int, firstVisibleIndex: Int, lastVisibleIndex: Int): Boolean =
      position >= firstVisibleIndex && position <= lastVisibleIndex

  override fun shouldEnterRange(
      position: Int,
      firstVisibleIndex: Int,
      lastVisibleIndex: Int,
      firstFullyVisibleIndex: Int,
      lastFullyVisibleIndex: Int
  ): Boolean = isInRange(position, firstVisibleIndex, lastVisibleIndex)

  override fun shouldExitRange(
      position: Int,
      firstVisibleIndex: Int,
      lastVisibleIndex: Int,
      firstFullyVisibleIndex: Int,
      lastFullyVisibleIndex: Int
  ): Boolean = !isInRange(position, firstVisibleIndex, lastVisibleIndex)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.fresco.vito.litho.slideshow

import android.content.Context
import android.content.res.Resources
import android.net.Uri
import android.os.Handler
import android.os.Looper
import com.facebook.common.callercontext.ContextChain
import com.facebook.fresco.vito.core.FrescoDrawableInterface
import com.facebook.fresco.vito.options.ImageOptions
import com.facebook.fresco.vito.provider.FrescoVitoProvider
import com.facebook.fresco.vito.source.ImageSourceProvider
import com.facebook.litho.ComponentContext
import com.facebook.litho.StateValue
import com.facebook.litho.annotations.MountSpec
import com.facebook.litho.annotations.OnCreateInitialState
import com.facebook.litho.annotations.OnCreateMountContent
import com.facebook.litho.annotations.OnMount
import com.facebook.litho.annotations.OnUnmount
import com.facebook.litho.annotations.Prop
import com.facebook.litho.annotations.PropDefault
import com.facebook.litho.annotations.State
import com.facebook.litho.annotations.TreeProp
import java.util.Timer
import java.util.TimerTask

@MountSpec(isPureRender = true)
object FrescoVitoSlideshowComponentSpec {

  @PropDefault const val isPlaying = true

  @JvmStatic
  @OnCreateInitialState
  internal fun createInitialState(
      c: ComponentContext,
      slideshowIndex: StateValue<Int?>,
      timer: StateValue<Timer?>,
      currentlyPlaying: StateValue<Boolean?>
  ) {
    slideshowIndex.set(0)
    timer.set(Timer("Fresco Vito slideshow timer"))
    currentlyPlaying.set(false)
  }

  @JvmStatic
  @OnCreateMountContent
  fun onCreateMountContent(c: Context?): FrescoVitoSlideshowDrawable<*> =
      FrescoVitoSlideshowDrawable(
          FrescoVitoProvider.getController().createDrawable(),
          FrescoVitoProvider.getController().createDrawable(),
          FrescoVitoProvider.getController().createDrawable())

  @JvmStatic
  @OnMount
  fun onMount(
      c: ComponentContext,
      slideshowDrawable: FrescoVitoSlideshowDrawable<*>,
      @Prop(varArg = "uri") uris: List<Uri?>,
      @Prop photoTransitionMs: Int,
      @Prop fadeTransitionMs: Int,
      @Prop(optional = true) isPlaying: Boolean,
      @Prop(optional = true) imageOptions: ImageOptions?,
      @Prop(optional = true) callerContext: Any?,
      @TreeProp contextChain: ContextChain?,
      @State(canUpdateLazily = true) slideshowIndex: Int,
      @State(canUpdateLazily = true) timer: Timer,
      @State(canUpdateLazily = true) currentlyPlaying: Boolean
  ) {
    // Reset mount content
    val controller = FrescoVitoProvider.getController()
    controller.releaseImmediately(slideshowDrawable.previousImage)
    controller.releaseImmediately(slideshowDrawable.currentImage)
    controller.releaseImmediately(slideshowDrawable.nextImage)
    slideshowDrawable.reset()

    // Configure mount content
    slideshowDrawable.transitionDuration = fadeTransitionMs

    // Load current image
    fetchNextImage(
        c.resources,
        slideshowDrawable,
        uris[slideshowIndex],
        imageOptions,
        callerContext,
        contextChain)
    // Immediately show current image
    slideshowDrawable.fadeToNext()
    slideshowDrawable.finishTransitionImmediately()
    val listSize = uris.size
    if (isPlaying && !currentlyPlaying) {
      // Load next image immediately
      val nextImageIndex = (slideshowIndex + 1) % listSize
      fetchNextImage(
          c.resources,
          slideshowDrawable,
          uris[nextImageIndex],
          imageOptions,
          callerContext,
          contextChain)

      // Set up task for animating to next image
      val animation: Runnable =
          object : Runnable {
            var currentIndex = nextImageIndex

            override fun run() {
              val nextIndex = (currentIndex + 1) % listSize
              animateToNextImage(
                  c.resources,
                  slideshowDrawable,
                  uris,
                  imageOptions,
                  callerContext,
                  contextChain,
                  nextIndex)
              currentIndex = nextIndex
              FrescoVitoSlideshowComponent.lazyUpdateSlideshowIndex(c, currentIndex)
            }
          }
      val handler = Handler(Looper.getMainLooper())
      val timerTask: TimerTask =
          object : TimerTask() {
            override fun run() {
              handler.post(animation)
            }
          }
      slideshowDrawable.timerTask = timerTask
      timer.scheduleAtFixedRate(
          timerTask, photoTransitionMs.toLong(), (photoTransitionMs + fadeTransitionMs).toLong())
    } else if (!isPlaying && currentlyPlaying) {
      val animateTask = slideshowDrawable.timerTask
      animateTask?.cancel()
      FrescoVitoSlideshowComponent.lazyUpdateCurrentlyPlaying(c, false)
    }
  }

  @JvmStatic
  @OnUnmount
  internal fun onUnmount(c: ComponentContext, slideshowDrawable: FrescoVitoSlideshowDrawable<*>) {
    val controller = FrescoVitoProvider.getController()
    controller.releaseImmediately(slideshowDrawable.previousImage)
    controller.releaseImmediately(slideshowDrawable.currentImage)
    controller.releaseImmediately(slideshowDrawable.nextImage)
    slideshowDrawable.reset()
    FrescoVitoSlideshowComponent.lazyUpdateCurrentlyPlaying(c, false)
  }

  private fun animateToNextImage(
      resources: Resources,
      slideshowDrawable: FrescoVitoSlideshowDrawable<*>,
      uris: List<Uri?>,
      options: ImageOptions?,
      callerContext: Any?,
      contextChain: ContextChain?,
      nextIndex: Int
  ) {
    // Do not transition until both current and next images are available
    if (isStillLoading(slideshowDrawable.currentImage) ||
        isStillLoading(slideshowDrawable.nextImage)) {
      return
    }
    // Both images are available -> we can fade
    slideshowDrawable.fadeToNext()
    // Fetch the next image ahead of time
    fetchNextImage(
        resources, slideshowDrawable, uris[nextIndex], options, callerContext, contextChain)
  }

  private fun isStillLoading(frescoDrawable: FrescoDrawableInterface): Boolean =
      frescoDrawable.isFetchSubmitted && !frescoDrawable.hasImage()

  private fun fetchNextImage(
      resources: Resources,
      slideshowDrawable: FrescoVitoSlideshowDrawable<*>,
      uri: Uri?,
      options: ImageOptions?,
      callerContext: Any?,
      contextChain: ContextChain?
  ) {
    FrescoVitoProvider.getController()
        .fetch(
            frescoDrawable = slideshowDrawable.nextImage,
            imageRequest =
                FrescoVitoProvider.getImagePipeline()
                    .createImageRequest(resources, ImageSourceProvider.forUri(uri), options),
            callerContext = callerContext,
            contextChain = contextChain,
            listener = null,
            onFadeListener = null,
            viewportDimensions = null)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.fresco.vito.litho.slideshow

import android.graphics.drawable.Drawable
import com.facebook.drawee.drawable.FadeDrawable
import com.facebook.fresco.vito.core.FrescoDrawableInterface
import java.util.TimerTask

class FrescoVitoSlideshowDrawable<T>(drawable1: T, drawable2: T, drawable3: T) :
    FadeDrawable(arrayOf<Drawable>(drawable1, drawable2, drawable3)) where
T : Drawable,
T : FrescoDrawableInterface {

  private var currentLayer = 0
  var timerTask: TimerTask? = null
  val currentImage: FrescoDrawableInterface
    get() = checkNotNull(getDrawable(currentLayer) as FrescoDrawableInterface?)

  val nextImage: FrescoDrawableInterface
    get() = checkNotNull(getDrawable(nextLayerIndex) as FrescoDrawableInterface?)

  val previousImage: FrescoDrawableInterface
    get() = checkNotNull(getDrawable(previousLayerIndex) as FrescoDrawableInterface?)

  private val nextLayerIndex: Int
    get() = (currentLayer + 1) % numberOfLayers

  private val previousLayerIndex: Int
    get() = (currentLayer - 1 + numberOfLayers) % numberOfLayers

  fun fadeToNext() {
    val prev = previousLayerIndex
    val next = nextLayerIndex
    beginBatchMode()
    fadeUpToLayer(next)
    hideLayerImmediately(prev)
    endBatchMode()
    currentLayer = next
  }

  override fun reset() {
    timerTask?.cancel()
    timerTask = null
    super.reset()
    currentLayer = 0
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.fresco.vito.nativecode

import android.graphics.Bitmap
import com.facebook.imagepipeline.nativecode.NativeRoundingFilter
import com.facebook.imagepipeline.transformation.BitmapTransformation
import com.facebook.imagepipeline.transformation.CircularTransformation

data class CircularBitmapTransformation(
    val isAntiAliased: Boolean = true,
    private val useFastNativeRounding: Boolean = false
) : BitmapTransformation, CircularTransformation {

  override fun transform(bitmap: Bitmap) {
    if (useFastNativeRounding) {
      NativeRoundingFilter.toCircleFast(bitmap, isAntiAliased)
    } else {
      NativeRoundingFilter.toCircle(bitmap, isAntiAliased)
    }
  }

  override fun modifiesTransparency(): Boolean = true
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.fresco.vito.nativecode

import com.facebook.common.internal.Supplier
import com.facebook.fresco.vito.core.impl.ImagePipelineUtilsImpl.CircularBitmapRounding
import com.facebook.imagepipeline.common.ImageDecodeOptions

class NativeCircularBitmapRounding(private val useFastNativeRounding: Supplier<Boolean>) :
    CircularBitmapRounding {
  private val circularImageDecodeOptions: ImageDecodeOptions by lazy {
    ImageDecodeOptions.newBuilder()
        .setBitmapTransformation(CircularBitmapTransformation(false, useFastNativeRounding.get()))
        .build()
  }
  private val circularImageDecodeOptionsAntiAliased: ImageDecodeOptions by lazy {
    ImageDecodeOptions.newBuilder()
        .setBitmapTransformation(CircularBitmapTransformation(true, useFastNativeRounding.get()))
        .build()
  }

  override fun getDecodeOptions(antiAliased: Boolean): ImageDecodeOptions =
      if (antiAliased) circularImageDecodeOptionsAntiAliased else circularImageDecodeOptions
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.fresco.vito.options

import androidx.annotation.ColorInt

data class BorderOptions(
    @JvmField @field:ColorInt @param:ColorInt val color: Int,
    @JvmField val width: Float,
    @JvmField val padding: Float = 0f,
    @JvmField val scaleDownInsideBorders: Boolean = false
) {
  companion object {
    /**
     * Create border options with padding and scaleDownInsideBorders. Note that currently padding is
     * not supported with RoundingOptions.asCircle().
     *
     * @param color The color of the border
     * @param width The width of the border, in pixels
     * @param padding Padding between the border and the image, in pixels
     * @param scaleDownInsideBorders true if scaled down inside border, false otherwise
     * @return BorderOptions
     */
    @JvmStatic
    @JvmOverloads
    fun create(
        @ColorInt color: Int,
        width: Float,
        padding: Float = 0f,
        scaleDownInsideBorders: Boolean = false
    ): BorderOptions {
      return BorderOptions(color, width, padding, scaleDownInsideBorders)
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.fresco.vito.options

import android.graphics.Bitmap
import android.graphics.PointF
import com.facebook.common.internal.Objects
import com.facebook.drawee.drawable.ScalingUtils
import com.facebook.imagepipeline.common.ImageDecodeOptions
import com.facebook.imagepipeline.common.ResizeOptions
import com.facebook.imagepipeline.common.RotationOptions
import com.facebook.imagepipeline.request.Postprocessor

open class DecodedImageOptions(builder: Builder<*>) : EncodedImageOptions(builder) {
  val resizeOptions: ResizeOptions? = builder.resizeOptions
  val rotationOptions: RotationOptions? = builder.rotationOptions
  val postprocessor: Postprocessor? = builder.postprocessor
  val imageDecodeOptions: ImageDecodeOptions? = builder.imageDecodeOptions
  val roundingOptions: RoundingOptions? = builder.roundingOptions
  val borderOptions: BorderOptions? = builder.borderOptions
  val actualImageScaleType: ScalingUtils.ScaleType = builder.actualImageScaleType
  val actualImageFocusPoint: PointF? = builder.actualFocusPoint
  val mLocalThumbnailPreviewsEnabled: Boolean = builder.localThumbnailPreviewsEnabled
  val loadThumbnailOnly: Boolean = builder.loadThumbnailOnly
  val bitmapConfig: Bitmap.Config? = builder.bitmapConfig
  val isProgressiveDecodingEnabled: Boolean? = builder.progressiveDecodingEnabled

  fun areLocalThumbnailPreviewsEnabled(): Boolean = mLocalThumbnailPreviewsEnabled

  override fun equals(other: Any?): Boolean {
    if (this === other) {
      return true
    }
    if (other == null || javaClass != other.javaClass) return false
    return equalDecodedOptions(other as DecodedImageOptions)
  }

  protected fun equalDecodedOptions(other: DecodedImageOptions): Boolean {
    return if (!Objects.equal(resizeOptions, other.resizeOptions) ||
        !Objects.equal(rotationOptions, other.rotationOptions) ||
        !Objects.equal(postprocessor, other.postprocessor) ||
        !Objects.equal(imageDecodeOptions, other.imageDecodeOptions) ||
        !Objects.equal(roundingOptions, other.roundingOptions) ||
        !Objects.equal(borderOptions, other.borderOptions) ||
        !Objects.equal(actualImageScaleType, other.actualImageScaleType) ||
        !Objects.equal(actualImageFocusPoint, other.actualImageFocusPoint) ||
        mLocalThumbnailPreviewsEnabled != other.mLocalThumbnailPreviewsEnabled ||
        loadThumbnailOnly != other.loadThumbnailOnly ||
        isProgressiveDecodingEnabled !== other.isProgressiveDecodingEnabled ||
        !Objects.equal(bitmapConfig, other.bitmapConfig)) {
      false
    } else equalEncodedOptions(other)
  }

  override fun hashCode(): Int {
    var result = super.hashCode()
    result = 31 * result + (resizeOptions?.hashCode() ?: 0)
    result = 31 * result + (rotationOptions?.hashCode() ?: 0)
    result = 31 * result + (postprocessor?.hashCode() ?: 0)
    result = 31 * result + (imageDecodeOptions?.hashCode() ?: 0)
    result = 31 * result + (roundingOptions?.hashCode() ?: 0)
    result = 31 * result + (borderOptions?.hashCode() ?: 0)
    result = 31 * result + actualImageScaleType.hashCode()
    result = 31 * result + (actualImageFocusPoint?.hashCode() ?: 0)
    result = 31 * result + if (mLocalThumbnailPreviewsEnabled) 1 else 0
    result = 31 * result + if (loadThumbnailOnly) 1 else 0
    result = 31 * result + (bitmapConfig?.hashCode() ?: 0)
    result = (31 * result + (isProgressiveDecodingEnabled?.hashCode() ?: 0))
    return result
  }

  override fun toString(): String = "DecodedImageOptions{" + toStringHelper().toString() + "}"

  override fun toStringHelper(): Objects.ToStringHelper =
      super.toStringHelper()
          .add("resizeOptions", resizeOptions)
          .add("rotationOptions", resizeOptions)
          .add("postprocessor", postprocessor)
          .add("imageDecodeOptions", imageDecodeOptions)
          .add("roundingOptions", roundingOptions)
          .add("borderOptions", borderOptions)
          .add("actualImageScaleType", actualImageScaleType)
          .add("actualImageFocusPoint", actualImageFocusPoint)
          .add("localThumbnailPreviewsEnabled", mLocalThumbnailPreviewsEnabled)
          .add("loadThumbnailOnly", loadThumbnailOnly)
          .add("bitmapConfig", bitmapConfig)
          .add("progressiveRenderingEnabled", isProgressiveDecodingEnabled)

  open class Builder<T : Builder<T>> : EncodedImageOptions.Builder<T> {
    internal var resizeOptions: ResizeOptions? = null
    internal var rotationOptions: RotationOptions? = null
    internal var postprocessor: Postprocessor? = null
    internal var imageDecodeOptions: ImageDecodeOptions? = null
    internal var roundingOptions: RoundingOptions? = null
    internal var borderOptions: BorderOptions? = null
    internal var actualImageScaleType: ScalingUtils.ScaleType = ScalingUtils.ScaleType.CENTER_CROP
    internal var actualFocusPoint: PointF? = null
    internal var localThumbnailPreviewsEnabled = false
    internal var loadThumbnailOnly = false
    internal var bitmapConfig: Bitmap.Config? = null
    internal var progressiveDecodingEnabled: Boolean? = null

    constructor() : super()

    constructor(defaultOptions: ImageOptions) : super(defaultOptions) {
      resizeOptions = defaultOptions.resizeOptions
      rotationOptions = defaultOptions.rotationOptions
      postprocessor = defaultOptions.postprocessor
      imageDecodeOptions = defaultOptions.imageDecodeOptions
      roundingOptions = defaultOptions.roundingOptions
      borderOptions = defaultOptions.borderOptions
      actualImageScaleType = defaultOptions.actualImageScaleType
      actualFocusPoint = defaultOptions.actualImageFocusPoint
      localThumbnailPreviewsEnabled = defaultOptions.areLocalThumbnailPreviewsEnabled()
      loadThumbnailOnly = defaultOptions.loadThumbnailOnly
      bitmapConfig = defaultOptions.bitmapConfig
      progressiveDecodingEnabled = defaultOptions.isProgressiveDecodingEnabled
    }

    fun resize(resizeOptions: ResizeOptions?): T = modify { this.resizeOptions = resizeOptions }

    fun rotate(rotationOptions: RotationOptions?): T = modify {
      this.rotationOptions = rotationOptions
    }

    fun postprocess(postprocessor: Postprocessor?): T = modify {
      this.postprocessor = postprocessor
    }

    fun imageDecodeOptions(imageDecodeOptions: ImageDecodeOptions?): T = modify {
      this.imageDecodeOptions = imageDecodeOptions
    }

    /**
     * Set the rounding options to be used or null if the image should not be rounded.
     *
     * @param roundingOptions the rounding options to use
     * @return the builder
     */
    fun round(roundingOptions: RoundingOptions?): T = modify {
      this.roundingOptions = roundingOptions
    }

    fun borders(borderOptions: BorderOptions?): T = modify { this.borderOptions = borderOptions }

    fun scale(actualImageScaleType: ScalingUtils.ScaleType?): T = modify {
      this.actualImageScaleType =
          actualImageScaleType ?: ImageOptions.defaults().actualImageScaleType
    }

    fun focusPoint(focusPoint: PointF?): T = modify { actualFocusPoint = focusPoint }

    /**
     * Display local thumbnail previews, for example EXIF thumbnails.
     *
     * @param localThumbnailPreviewsEnabled true if thumbnails should be displayed
     * @return the builder
     */
    fun localThumbnailPreviewsEnabled(localThumbnailPreviewsEnabled: Boolean): T = modify {
      this.localThumbnailPreviewsEnabled = localThumbnailPreviewsEnabled
    }

    fun loadThumbnailOnly(loadThumbnailOnly: Boolean): T = modify {
      this.loadThumbnailOnly = loadThumbnailOnly
    }

    fun bitmapConfig(bitmapConfig: Bitmap.Config?): T = modify { this.bitmapConfig = bitmapConfig }

    fun progressiveRendering(progressiveDecodingEnabled: Boolean?): T = modify {
      this.progressiveDecodingEnabled = progressiveDecodingEnabled
    }

    override fun build(): DecodedImageOptions = DecodedImageOptions(this)

    private inline fun modify(block: Builder<T>.() -> Unit): T {
      block()
      return getThis()
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.fresco.vito.options

import com.facebook.common.internal.Objects
import com.facebook.imagepipeline.common.Priority
import com.facebook.imagepipeline.request.ImageRequest.CacheChoice

open class EncodedImageOptions(builder: Builder<*>) {
  val priority: Priority? = builder.priority
  val cacheChoice: CacheChoice? = builder.cacheChoice

  override fun equals(other: Any?): Boolean {
    if (this === other) {
      return true
    }
    if (other == null || javaClass != other.javaClass) {
      return false
    }
    return equalEncodedOptions(other as EncodedImageOptions)
  }

  protected fun equalEncodedOptions(other: EncodedImageOptions): Boolean {
    return Objects.equal(priority, other.priority) && Objects.equal(cacheChoice, other.cacheChoice)
  }

  override fun hashCode(): Int {
    val result = priority?.hashCode() ?: 0
    return 31 * result + (cacheChoice?.hashCode() ?: 0)
  }

  override fun toString(): String = toStringHelper().toString()

  protected open fun toStringHelper(): Objects.ToStringHelper =
      Objects.toStringHelper(this).add("priority", priority).add("cacheChoice", cacheChoice)

  open class Builder<T : Builder<T>> {
    internal var priority: Priority? = null
    internal var cacheChoice: CacheChoice? = null

    protected constructor()

    protected constructor(defaultOptions: EncodedImageOptions) {
      priority = defaultOptions.priority
      cacheChoice = defaultOptions.cacheChoice
    }

    fun priority(priority: Priority?): T = modify { this.priority = priority }

    fun cacheChoice(cacheChoice: CacheChoice?): T = modify { this.cacheChoice = cacheChoice }

    open fun build(): EncodedImageOptions = EncodedImageOptions(this)

    protected fun getThis(): T = this as T

    private inline fun modify(block: Builder<T>.() -> Unit): T {
      block()
      return getThis()
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.fresco.vito.options

import android.graphics.ColorFilter
import android.graphics.PointF
import android.graphics.drawable.Drawable
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
import com.facebook.common.internal.Objects
import com.facebook.drawee.drawable.ScalingUtils
import com.facebook.imagepipeline.common.Priority

class ImageOptions(builder: Builder) : DecodedImageOptions(builder) {

  // Placeholder
  @get:ColorInt @ColorInt val placeholderColor: Int? = builder._placeholderColor
  @get:DrawableRes @DrawableRes val placeholderRes: Int = builder._placeholderRes
  val placeholderDrawable: Drawable? = builder._placeholderDrawable
  val placeholderScaleType: ScalingUtils.ScaleType? = builder._placeholderScaleType
  val placeholderFocusPoint: PointF? = builder._placeholderFocusPoint
  val placeholderApplyRoundingOptions: Boolean = builder._placeholderApplyRoundingOptions

  // Progress bar
  @get:DrawableRes @DrawableRes val progressRes: Int = builder._progressRes
  val progressDrawable: Drawable? = builder._progressDrawable
  val progressScaleType: ScalingUtils.ScaleType? = builder._progressScaleType

  // Error
  @get:ColorInt @ColorInt val errorColor: Int? = builder._errorColor
  @get:DrawableRes @DrawableRes val errorRes: Int = builder._errorRes
  val errorScaleType: ScalingUtils.ScaleType? = builder._errorScaleType
  val errorFocusPoint: PointF? = builder._errorFocusPoint
  val errorDrawable: Drawable? = builder._errorDrawable
  val errorApplyRoundingOptions: Boolean = builder._errorApplyRoundingOptions

  // Actual image
  val actualImageColorFilter: ColorFilter? = builder._actualImageColorFilter

  // Overlay
  @get:DrawableRes @DrawableRes val overlayRes: Int = builder._overlayRes
  val overlayDrawable: Drawable? = builder._overlayDrawable
  private val _resizeToViewport: Boolean = builder._resizeToViewport
  val fadeDurationMs: Int = builder._fadeDurationMs
  private val _autoPlay: Boolean = builder._autoPlay
  private val _autoStop: Boolean = builder._autoStop
  val isPerfMediaRemountInstrumentationFix: Boolean = builder._perfMediaRemountInstrumentationFix
  val customDrawableFactory: ImageOptionsDrawableFactory? = builder._customDrawableFactory
  val experimentalDynamicSize: Boolean = builder._experimentalDynamicSize

  fun extend(): Builder = extend(this)

  fun shouldAutoPlay(): Boolean = _autoPlay

  fun shouldAutoStop(): Boolean = _autoStop

  fun shouldResizeToViewport(): Boolean = _resizeToViewport

  fun equalsForActualImage(other: ImageOptions): Boolean {
    if (this === other) {
      return true
    }
    if (isPerfMediaRemountInstrumentationFix) {
      if (overlayRes != other.overlayRes ||
          !Objects.equal(overlayDrawable, other.overlayDrawable) ||
          !Objects.equal(actualImageColorFilter, other.actualImageColorFilter) ||
          _resizeToViewport != other._resizeToViewport ||
          _autoPlay != other._autoPlay ||
          _autoStop != other._autoStop ||
          !Objects.equal(customDrawableFactory, other.customDrawableFactory) ||
          isPerfMediaRemountInstrumentationFix != other.isPerfMediaRemountInstrumentationFix) {
        return false
      }
    } else {
      if (overlayRes != other.overlayRes ||
          !Objects.equal(overlayDrawable, other.overlayDrawable) ||
          !Objects.equal(actualImageColorFilter, other.actualImageColorFilter) ||
          _resizeToViewport != other._resizeToViewport ||
          !Objects.equal(customDrawableFactory, other.customDrawableFactory)) {
        return false
      }
    }
    return equalDecodedOptions(other)
  }

  override fun equals(otherObject: Any?): Boolean {
    if (this === otherObject) return true
    if (otherObject == null || javaClass != otherObject.javaClass) return false

    val other = otherObject as ImageOptions
    if (isPerfMediaRemountInstrumentationFix) {
      if (placeholderColor != other.placeholderColor ||
          placeholderRes != other.placeholderRes ||
          !Objects.equal(placeholderDrawable, other.placeholderDrawable) ||
          !Objects.equal(placeholderScaleType, other.placeholderScaleType) ||
          !Objects.equal(placeholderFocusPoint, other.placeholderFocusPoint) ||
          placeholderApplyRoundingOptions != other.placeholderApplyRoundingOptions ||
          errorColor != other.errorColor ||
          errorRes != other.errorRes ||
          !Objects.equal(errorScaleType, other.errorScaleType) ||
          !Objects.equal(errorFocusPoint, other.errorFocusPoint) ||
          errorApplyRoundingOptions != other.errorApplyRoundingOptions ||
          overlayRes != other.overlayRes ||
          !Objects.equal(overlayDrawable, other.overlayDrawable) ||
          progressRes != other.progressRes ||
          !Objects.equal(progressDrawable, other.progressDrawable) ||
          !Objects.equal(progressScaleType, other.progressScaleType) ||
          !Objects.equal(actualImageColorFilter, other.actualImageColorFilter) ||
          _resizeToViewport != other._resizeToViewport ||
          fadeDurationMs != other.fadeDurationMs ||
          _autoPlay != other._autoPlay ||
          _autoStop != other._autoStop ||
          !Objects.equal(customDrawableFactory, other.customDrawableFactory) ||
          !Objects.equal(errorDrawable, other.errorDrawable) ||
          isPerfMediaRemountInstrumentationFix != other.isPerfMediaRemountInstrumentationFix) {
        return false
      }
    } else {
      if (placeholderColor != other.placeholderColor ||
          placeholderRes != other.placeholderRes ||
          !Objects.equal(placeholderDrawable, other.placeholderDrawable) ||
          !Objects.equal(placeholderScaleType, other.placeholderScaleType) ||
          !Objects.equal(placeholderFocusPoint, other.placeholderFocusPoint) ||
          placeholderApplyRoundingOptions != other.placeholderApplyRoundingOptions ||
          errorColor != other.errorColor ||
          errorRes != other.errorRes ||
          !Objects.equal(errorScaleType, other.errorScaleType) ||
          !Objects.equal(errorFocusPoint, other.errorFocusPoint) ||
          errorApplyRoundingOptions != other.errorApplyRoundingOptions ||
          overlayRes != other.overlayRes ||
          !Objects.equal(overlayDrawable, other.overlayDrawable) ||
          progressRes != other.progressRes ||
          progressDrawable !== other.progressDrawable ||
          progressScaleType !== other.progressScaleType ||
          !Objects.equal(actualImageColorFilter, other.actualImageColorFilter) ||
          _resizeToViewport != other._resizeToViewport ||
          fadeDurationMs != other.fadeDurationMs ||
          _autoPlay != other._autoPlay ||
          _autoStop != other._autoStop ||
          !Objects.equal(customDrawableFactory, other.customDrawableFactory) ||
          errorDrawable !== other.errorDrawable) {
        return false
      }
    }
    return equalDecodedOptions(other)
  }

  override fun hashCode(): Int {
    var result = super.hashCode()
    result = 31 * result + (placeholderColor ?: 0)
    result = 31 * result + placeholderRes
    result = 31 * result + (placeholderDrawable?.hashCode() ?: 0)
    result = 31 * result + (placeholderScaleType?.hashCode() ?: 0)
    result = 31 * result + (placeholderFocusPoint?.hashCode() ?: 0)
    result = 31 * result + if (placeholderApplyRoundingOptions) 1 else 0
    result = 31 * result + (errorColor ?: 0)
    result = 31 * result + errorRes
    result = 31 * result + (errorScaleType?.hashCode() ?: 0)
    result = 31 * result + (errorFocusPoint?.hashCode() ?: 0)
    result = 31 * result + (errorDrawable?.hashCode() ?: 0)
    result = 31 * result + if (errorApplyRoundingOptions) 1 else 0
    result = 31 * result + overlayRes
    result = 31 * result + (overlayDrawable?.hashCode() ?: 0)
    result = 31 * result + (progressDrawable?.hashCode() ?: 0)
    result = 31 * result + (progressScaleType?.hashCode() ?: 0)
    result = 31 * result + (actualImageColorFilter?.hashCode() ?: 0)
    result = 31 * result + if (_resizeToViewport) 1 else 0
    result = 31 * result + fadeDurationMs
    result = 31 * result + if (_autoPlay) 1 else 0
    result = 31 * result + if (_autoStop) 1 else 0
    result = 31 * result + if (isPerfMediaRemountInstrumentationFix) 1 else 0
    result = 31 * result + progressRes
    result = 31 * result + (customDrawableFactory?.hashCode() ?: 0)
    return result
  }

  override fun toString(): String = "ImageOptions{${toStringHelper()}}"

  override fun toStringHelper(): Objects.ToStringHelper =
      super.toStringHelper()
          .add("placeholderColor", placeholderColor)
          .add("placeholderRes", placeholderRes)
          .add("placeholderDrawable", placeholderDrawable)
          .add("placeholderScaleType", placeholderScaleType)
          .add("placeholderFocusPoint", placeholderFocusPoint)
          .add("placeholderApplyRoundingOptions", placeholderApplyRoundingOptions)
          .add("progressRes", progressRes)
          .add("progressDrawable", progressDrawable)
          .add("progressScaleType", progressScaleType)
          .add("errorColor", errorColor)
          .add("errorRes", errorRes)
          .add("errorScaleType", errorScaleType)
          .add("errorFocusPoint", errorFocusPoint)
          .add("errorDrawable", errorDrawable)
          .add("errorApplyRoundingOptions", errorApplyRoundingOptions)
          .add("actualImageColorFilter", actualImageColorFilter)
          .add("overlayRes", overlayRes)
          .add("overlayDrawable", overlayDrawable)
          .add("resizeToViewport", _resizeToViewport)
          .add("autoPlay", _autoPlay)
          .add("autoStop", _autoStop)
          .add("mPerfMediaRemountInstrumentationFix", isPerfMediaRemountInstrumentationFix)
          .add("fadeDurationMs", fadeDurationMs)
          .add("customDrawableFactory", customDrawableFactory)

  class Builder : DecodedImageOptions.Builder<Builder> {
    @ColorInt internal var _placeholderColor: Int? = null
    @DrawableRes internal var _placeholderRes = 0
    internal var _placeholderDrawable: Drawable? = null
    internal var _placeholderScaleType: ScalingUtils.ScaleType? = null
    internal var _placeholderFocusPoint: PointF? = null
    internal var _placeholderApplyRoundingOptions = false

    @DrawableRes internal var _progressRes = 0
    internal var _progressDrawable: Drawable? = null
    internal var _progressScaleType: ScalingUtils.ScaleType? = null

    @ColorInt internal var _errorColor: Int? = null
    @DrawableRes internal var _errorRes = 0
    internal var _errorScaleType: ScalingUtils.ScaleType? = null
    internal var _errorFocusPoint: PointF? = null
    internal var _errorDrawable: Drawable? = null
    internal var _errorApplyRoundingOptions = false
    internal var _actualImageColorFilter: ColorFilter? = null

    @DrawableRes internal var _overlayRes = 0
    internal var _overlayDrawable: Drawable? = null
    internal var _resizeToViewport = false
    internal var _autoPlay = false
    internal var _autoStop = true
    internal var _perfMediaRemountInstrumentationFix = false
    internal var _fadeDurationMs = 0
    internal var _customDrawableFactory: ImageOptionsDrawableFactory? = null
    internal var _experimentalDynamicSize = false

    internal constructor() : super()

    internal constructor(defaultOptions: ImageOptions) : super(defaultOptions) {
      _placeholderColor = defaultOptions.placeholderColor
      _placeholderRes = defaultOptions.placeholderRes
      _placeholderDrawable = defaultOptions.placeholderDrawable
      _placeholderScaleType = defaultOptions.placeholderScaleType
      _placeholderFocusPoint = defaultOptions.placeholderFocusPoint
      _placeholderApplyRoundingOptions = defaultOptions.placeholderApplyRoundingOptions
      _progressRes = defaultOptions.progressRes
      _progressDrawable = defaultOptions.progressDrawable
      _progressScaleType = defaultOptions.progressScaleType
      _errorColor = defaultOptions.errorColor
      _errorRes = defaultOptions.errorRes
      _errorScaleType = defaultOptions.errorScaleType
      _errorFocusPoint = defaultOptions.errorFocusPoint
      _errorDrawable = defaultOptions.errorDrawable
      _errorApplyRoundingOptions = defaultOptions.errorApplyRoundingOptions
      _actualImageColorFilter = defaultOptions.actualImageColorFilter
      _overlayRes = defaultOptions.overlayRes
      _overlayDrawable = defaultOptions.overlayDrawable
      _resizeToViewport = defaultOptions.shouldResizeToViewport()
      _autoPlay = defaultOptions.shouldAutoPlay()
      _autoStop = defaultOptions.shouldAutoStop()
      _fadeDurationMs = defaultOptions.fadeDurationMs
      _customDrawableFactory = defaultOptions.customDrawableFactory
      _experimentalDynamicSize = defaultOptions.experimentalDynamicSize
    }

    fun placeholder(placeholder: Drawable?): Builder = modify {
      _placeholderDrawable = placeholder
      _placeholderColor = null
      _placeholderRes = 0
    }

    fun placeholder(
        placeholder: Drawable?,
        placeholderScaleType: ScalingUtils.ScaleType?
    ): Builder = modify {
      _placeholderDrawable = placeholder
      _placeholderScaleType = placeholderScaleType
      _placeholderColor = null
      _placeholderRes = 0
    }

    fun placeholderColor(@ColorInt placeholderColor: Int): Builder = modify {
      _placeholderColor = placeholderColor
      _placeholderRes = 0
      _placeholderDrawable = null
    }

    fun placeholderRes(@DrawableRes placeholderRes: Int): Builder = modify {
      _placeholderRes = placeholderRes
      _placeholderColor = null
      _placeholderDrawable = null
    }

    fun placeholderRes(
        @DrawableRes placeholderRes: Int,
        placeholderScaleType: ScalingUtils.ScaleType?
    ): Builder = modify {
      _placeholderRes = placeholderRes
      _placeholderScaleType = placeholderScaleType
      _placeholderColor = null
      _placeholderDrawable = null
    }

    fun placeholderScaleType(placeholderScaleType: ScalingUtils.ScaleType?): Builder = modify {
      _placeholderScaleType = placeholderScaleType
    }

    fun placeholderFocusPoint(placeholderFocusPoint: PointF?): Builder = modify {
      _placeholderFocusPoint = placeholderFocusPoint
    }

    fun placeholderApplyRoundingOptions(placeholderApplyRoundingOptions: Boolean): Builder =
        modify {
          _placeholderApplyRoundingOptions = placeholderApplyRoundingOptions
        }

    fun errorColor(@ColorInt errorColor: Int): Builder = modify {
      _errorColor = errorColor
      _errorRes = 0
      _errorDrawable = null
    }

    fun errorRes(@DrawableRes errorRes: Int): Builder = modify {
      _errorColor = null
      _errorRes = errorRes
      _errorDrawable = null
    }

    fun errorScaleType(errorScaleType: ScalingUtils.ScaleType?): Builder = modify {
      _errorScaleType = errorScaleType
    }

    fun errorFocusPoint(errorFocusPoint: PointF?): Builder = modify {
      _errorFocusPoint = errorFocusPoint
    }

    fun errorDrawable(errorDrawable: Drawable?): Builder = modify {
      _errorColor = null
      _errorRes = 0
      _errorDrawable = errorDrawable
    }

    fun errorApplyRoundingOptions(errorApplyRoundingOptions: Boolean): Builder = modify {
      _errorApplyRoundingOptions = errorApplyRoundingOptions
    }

    fun progress(progress: Drawable?): Builder = modify { _progressDrawable = progress }

    fun progress(progress: Drawable?, progressScaleType: ScalingUtils.ScaleType?): Builder =
        modify {
          _progressDrawable = progress
          _progressScaleType = progressScaleType
        }

    fun progressRes(@DrawableRes progressRes: Int): Builder = modify { _progressRes = progressRes }

    fun progressRes(
        @DrawableRes progressRes: Int,
        progressScaleType: ScalingUtils.ScaleType?
    ): Builder = modify {
      _progressRes = progressRes
      _progressScaleType = progressScaleType
    }

    fun progressScaleType(progressScaleType: ScalingUtils.ScaleType?): Builder = modify {
      _progressScaleType = progressScaleType
    }

    fun overlayRes(@DrawableRes overlayRes: Int): Builder = modify {
      _overlayRes = overlayRes
      _overlayDrawable = null
    }

    fun overlay(overlayDrawable: Drawable?): Builder = modify {
      _overlayDrawable = overlayDrawable
      _overlayRes = 0
    }

    fun colorFilter(colorFilter: ColorFilter?): Builder = modify {
      _actualImageColorFilter = colorFilter
    }

    /**
     * Turns on autoplay for animated images
     *
     * @param autoPlay whether to enable autoplay for animated images
     */
    fun autoPlay(autoPlay: Boolean): Builder = modify { _autoPlay = autoPlay }

    /**
     * Turns on autoStop for animated images
     *
     * @param autoStop whether to enable autoStop for animated images when it scrolls off screen
     */
    fun autoStop(autoStop: Boolean): Builder = modify { _autoStop = autoStop }

    fun perfMediaRemountInstrumentationFix(fix: Boolean): Builder = modify {
      _perfMediaRemountInstrumentationFix = fix
    }

    /**
     * Will resize bitmap to viewport dimensions. Works only if
     * [com.facebook.imagepipeline.common.ResizeOptions] are not set. Works only with Vito for now.
     * Please do not use unless you messaged me for details: @defhlt
     *
     * @param resizeToViewport whether to enable this optimization
     */
    fun resizeToViewport(resizeToViewport: Boolean): Builder = modify {
      _resizeToViewport = resizeToViewport
    }

    /**
     * Sets the fade duration.
     *
     * @param fadeInDurationMs
     */
    fun fadeDurationMs(fadeInDurationMs: Int): Builder = modify {
      _fadeDurationMs = fadeInDurationMs
    }

    /**
     * Set a custom drawable factory to be used to create the actual image drawable.
     *
     * @param drawableFactory the factory to use
     */
    fun customDrawableFactory(drawableFactory: ImageOptionsDrawableFactory?): Builder = modify {
      _customDrawableFactory = drawableFactory
    }

    fun experimentalDynamicSize(dynamicSize: Boolean): Builder = modify {
      _experimentalDynamicSize = dynamicSize
    }

    override fun build(): ImageOptions = ImageOptions(this)

    private inline fun modify(block: Builder.() -> Unit): Builder {
      block()
      return this
    }
  }

  companion object {
    private var defaultImageOptions =
        Builder()
            .placeholderScaleType(ScalingUtils.ScaleType.CENTER_INSIDE)
            .progressScaleType(ScalingUtils.ScaleType.CENTER_INSIDE)
            .errorScaleType(ScalingUtils.ScaleType.CENTER_INSIDE)
            .priority(Priority.HIGH)
            .build()

    @JvmStatic fun defaults(): ImageOptions = defaultImageOptions

    @JvmStatic
    fun setDefaults(imageOptions: ImageOptions) {
      defaultImageOptions = imageOptions
    }

    @JvmStatic fun extend(imageOptions: ImageOptions): Builder = Builder(imageOptions)

    @JvmStatic fun create(): Builder = extend(defaults())
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.fresco.vito.options

import android.content.res.Resources
import android.graphics.drawable.Drawable
import com.facebook.imagepipeline.image.CloseableImage

interface ImageOptionsDrawableFactory {
  fun createDrawable(
      resources: Resources,
      closeableImage: CloseableImage,
      imageOptions: ImageOptions
  ): Drawable?
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.fresco.vito.options

data class RoundingOptions(
    val isCircular: Boolean,
    val cornerRadius: Float,
    val cornerRadii: FloatArray?,
    val isAntiAliased: Boolean,
    val isForceRoundAtDecode: Boolean
) {
  fun hasRoundedCorners(): Boolean = cornerRadius != CORNER_RADIUS_UNSET || cornerRadii != null

  companion object {
    const val CORNER_RADIUS_UNSET = 0f
    val AS_CIRCLE = RoundingOptions(true, CORNER_RADIUS_UNSET, null, false, false)
    val AS_CIRCLE_ANTI_ALIASING = RoundingOptions(true, CORNER_RADIUS_UNSET, null, true, false)

    @JvmStatic fun asCircle(): RoundingOptions = AS_CIRCLE

    @JvmStatic
    fun asCircle(antiAliasing: Boolean): RoundingOptions =
        if (antiAliasing) AS_CIRCLE_ANTI_ALIASING else AS_CIRCLE

    @JvmStatic
    fun asCircle(antiAliasing: Boolean, forceRoundAtDecode: Boolean): RoundingOptions {
      return RoundingOptions(true, CORNER_RADIUS_UNSET, null, antiAliasing, forceRoundAtDecode)
    }

    @JvmStatic
    fun forCornerRadiusPx(cornerRadiusPx: Float): RoundingOptions {
      return RoundingOptions(false, cornerRadiusPx, null, false, false)
    }

    @JvmStatic
    fun forCornerRadii(
        topLeft: Float,
        topRight: Float,
        bottomRight: Float,
        bottomLeft: Float
    ): RoundingOptions {
      val radii = FloatArray(8)
      radii[0] = topLeft
      radii[1] = topLeft
      radii[2] = topRight
      radii[3] = topRight
      radii[4] = bottomRight
      radii[5] = bottomRight
      radii[6] = bottomLeft
      radii[7] = bottomLeft
      return RoundingOptions(false, CORNER_RADIUS_UNSET, radii, false, false)
    }

    @JvmStatic
    @JvmOverloads
    fun forCornerRadii(cornerRadii: FloatArray, antiAliasing: Boolean = false): RoundingOptions {
      return RoundingOptions(false, CORNER_RADIUS_UNSET, cornerRadii, antiAliasing, false)
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.fresco.vito.options

import org.assertj.core.api.Assertions.assertThat
import org.junit.Test

class RoundingOptionsTest {

  @Test
  fun testHashCode_whenEqualParameters_thenEqualHashCode() {
    val a = RoundingOptions.forCornerRadii(10f, 20f, 30f, 40f)
    val b = RoundingOptions.forCornerRadii(10f, 20f, 30f, 40f)
    assertThat(b.hashCode()).isEqualTo(a.hashCode())
  }

  @Test
  fun testHashCode_whenDifferentParameters_thenDifferentHashCode() {
    val a = RoundingOptions.forCornerRadii(10f, 20f, 30f, 40f)
    val b = RoundingOptions.forCornerRadii(99f, 20f, 30f, 40f)
    assertThat(b.hashCode()).isNotEqualTo(a.hashCode())
  }

  @Test
  fun testHashCode_whenDifferentAntiAliasing_thenDifferentHashCode() {
    val a = RoundingOptions.asCircle(true)
    val b = RoundingOptions.asCircle(false)
    assertThat(b.hashCode()).isNotEqualTo(a.hashCode())
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.fresco.vito.provider

import com.facebook.common.logging.FLog
import com.facebook.fresco.vito.core.FrescoController2
import com.facebook.fresco.vito.core.FrescoVitoConfig
import com.facebook.fresco.vito.core.FrescoVitoPrefetcher
import com.facebook.fresco.vito.core.VitoImagePipeline
import java.lang.RuntimeException

object FrescoVitoProvider {

  private var _implementation: Implementation? = null

  @JvmStatic
  @Synchronized
  fun getController(): FrescoController2 = getImplementation().getController()

  @JvmStatic
  @Synchronized
  fun getPrefetcher(): FrescoVitoPrefetcher = getImplementation().getPrefetcher()

  @JvmStatic
  @Synchronized
  fun getImagePipeline(): VitoImagePipeline = getImplementation().getImagePipeline()

  @JvmStatic @Synchronized fun getConfig(): FrescoVitoConfig = getImplementation().getConfig()

  /**
   * Reset the implementation. This will remove any implementation currently set up and has to be
   * used with caution.
   */
  @JvmStatic
  @Synchronized
  fun resetImplementation() {
    _implementation = null
  }

  @JvmStatic @Synchronized fun hasBeenInitialized(): Boolean = _implementation != null

  // We do not allow to re-initialize Vito directly.
  // You can use #resetImplementation() if you must manually tear down Vito.
  @JvmStatic
  @Synchronized
  fun getImplementation(): Implementation {
    return _implementation ?: throw RuntimeException("Fresco context provider must be set")
  }

  @JvmStatic
  @Synchronized
  fun setImplementation(implementation: Implementation) {
    // We do not allow to re-initialize Vito directly.
    // You can use #resetImplementation() if you must manually tear down Vito.
    if (_implementation != null) {
      FLog.e(
          "FrescoVitoProvider",
          "Fresco Vito already initialized! Vito must be initialized only once.")
    }
    _implementation = implementation
  }

  interface Implementation {
    fun getController(): FrescoController2

    fun getPrefetcher(): FrescoVitoPrefetcher

    fun getImagePipeline(): VitoImagePipeline

    fun getConfig(): FrescoVitoConfig
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.fresco.vito.provider.impl

import com.facebook.callercontext.CallerContextVerifier
import com.facebook.common.internal.Supplier
import com.facebook.fresco.vito.core.FrescoController2
import com.facebook.fresco.vito.core.FrescoVitoConfig
import com.facebook.fresco.vito.core.FrescoVitoPrefetcher
import com.facebook.fresco.vito.core.ImagePipelineUtils
import com.facebook.fresco.vito.core.VitoImagePerfListener
import com.facebook.fresco.vito.core.VitoImagePipeline
import com.facebook.fresco.vito.core.impl.FrescoController2Impl
import com.facebook.fresco.vito.core.impl.FrescoVitoPrefetcherImpl
import com.facebook.fresco.vito.core.impl.HierarcherImpl
import com.facebook.fresco.vito.core.impl.VitoImagePipelineImpl
import com.facebook.fresco.vito.core.impl.debug.DefaultDebugOverlayFactory2
import com.facebook.fresco.vito.core.impl.debug.NoOpDebugOverlayFactory2
import com.facebook.fresco.vito.drawable.ArrayVitoDrawableFactory
import com.facebook.fresco.vito.drawable.BitmapDrawableFactory
import com.facebook.fresco.vito.draweesupport.DrawableFactoryWrapper
import com.facebook.fresco.vito.options.ImageOptionsDrawableFactory
import com.facebook.fresco.vito.provider.FrescoVitoProvider
import com.facebook.imagepipeline.core.ImagePipeline
import com.facebook.imagepipeline.core.ImagePipelineFactory
import java.lang.RuntimeException
import java.util.concurrent.Executor

class DefaultFrescoVitoProvider(
    private val frescoVitoConfig: FrescoVitoConfig,
    imagePipeline: ImagePipeline,
    imagePipelineUtils: ImagePipelineUtils,
    lightweightBackgroundThreadExecutor: Executor,
    uiThreadExecutor: Executor,
    debugOverlayEnabledSupplier: Supplier<Boolean?>?,
    callerContextVerifier: CallerContextVerifier,
    vitoImagePerfListener: VitoImagePerfListener,
) : FrescoVitoProvider.Implementation {

  private val frescoController: FrescoController2

  private val vitoImagePipeline: VitoImagePipeline

  private val frescoVitoPrefetcher: FrescoVitoPrefetcher

  init {
    if (!ImagePipelineFactory.hasBeenInitialized()) {
      throw RuntimeException(
          "Fresco must be initialized before DefaultFrescoVitoProvider can be used!")
    }
    frescoVitoPrefetcher =
        FrescoVitoPrefetcherImpl(imagePipeline, imagePipelineUtils, callerContextVerifier)
    vitoImagePipeline = VitoImagePipelineImpl(imagePipeline, imagePipelineUtils)
    frescoController =
        FrescoController2Impl(
            frescoVitoConfig,
            HierarcherImpl(createDefaultDrawableFactory()),
            lightweightBackgroundThreadExecutor,
            uiThreadExecutor,
            vitoImagePipeline,
            null,
            debugOverlayEnabledSupplier?.let { DefaultDebugOverlayFactory2(it) }
                ?: NoOpDebugOverlayFactory2(),
            null,
            vitoImagePerfListener)
  }

  override fun getController(): FrescoController2 = frescoController

  override fun getPrefetcher(): FrescoVitoPrefetcher = frescoVitoPrefetcher

  override fun getImagePipeline(): VitoImagePipeline = vitoImagePipeline

  override fun getConfig(): FrescoVitoConfig = frescoVitoConfig

  companion object {
    private fun createDefaultDrawableFactory(): ImageOptionsDrawableFactory {
      val animatedDrawableFactory =
          ImagePipelineFactory.getInstance().getAnimatedDrawableFactory(null)
      val bitmapFactory = BitmapDrawableFactory()
      return if (animatedDrawableFactory == null) {
        bitmapFactory
      } else {
        if (animatedDrawableFactory is ImageOptionsDrawableFactory) {
          ArrayVitoDrawableFactory(bitmapFactory, animatedDrawableFactory)
        } else {
          ArrayVitoDrawableFactory(bitmapFactory, DrawableFactoryWrapper(animatedDrawableFactory))
        }
      }
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.fresco.vito.provider.impl

import com.facebook.callercontext.CallerContextVerifier

object NoOpCallerContextVerifier : CallerContextVerifier {
  override fun verifyCallerContext(callerContext: Any?, isPrefetch: Boolean) {
    // No-op
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.fresco.vito.provider.impl.kotlin

import com.facebook.callercontext.CallerContextVerifier
import com.facebook.fresco.vito.core.FrescoController2
import com.facebook.fresco.vito.core.FrescoVitoConfig
import com.facebook.fresco.vito.core.FrescoVitoPrefetcher
import com.facebook.fresco.vito.core.ImagePipelineUtils
import com.facebook.fresco.vito.core.VitoImagePipeline
import com.facebook.fresco.vito.core.impl.DebugOverlayHandler
import com.facebook.fresco.vito.core.impl.FrescoVitoPrefetcherImpl
import com.facebook.fresco.vito.core.impl.KFrescoController
import com.facebook.fresco.vito.core.impl.VitoImagePipelineImpl
import com.facebook.fresco.vito.draweesupport.DrawableFactoryWrapper
import com.facebook.fresco.vito.options.ImageOptionsDrawableFactory
import com.facebook.fresco.vito.provider.FrescoVitoProvider
import com.facebook.fresco.vito.provider.impl.NoOpCallerContextVerifier
import com.facebook.imagepipeline.core.ImagePipeline
import com.facebook.imagepipeline.core.ImagePipelineFactory
import java.util.concurrent.Executor

class KFrescoVitoProvider(
    private val vitoConfig: FrescoVitoConfig,
    private val frescoImagePipeline: ImagePipeline,
    private val imagePipelineUtils: ImagePipelineUtils,
    private val uiThreadExecutor: Executor,
    private val lightweightBackgroundExecutor: Executor,
    private val callerContextVerifier: CallerContextVerifier = NoOpCallerContextVerifier,
    private val debugOverlayHandler: DebugOverlayHandler? = null
) : FrescoVitoProvider.Implementation {

  private val _imagePipeline: VitoImagePipeline by lazy {
    VitoImagePipelineImpl(frescoImagePipeline, imagePipelineUtils)
  }

  private val _controller: FrescoController2 by lazy {
    KFrescoController(
            config = vitoConfig,
            vitoImagePipeline = _imagePipeline,
            uiThreadExecutor = uiThreadExecutor,
            lightweightBackgroundThreadExecutor = lightweightBackgroundExecutor,
            drawableFactory = getFactory())
        .also { it.debugOverlayHandler = debugOverlayHandler }
  }

  private val _prefetcher: FrescoVitoPrefetcher by lazy {
    FrescoVitoPrefetcherImpl(frescoImagePipeline, imagePipelineUtils, callerContextVerifier)
  }

  override fun getController(): FrescoController2 = _controller

  override fun getPrefetcher(): FrescoVitoPrefetcher = _prefetcher

  override fun getImagePipeline(): VitoImagePipeline = _imagePipeline

  override fun getConfig(): FrescoVitoConfig = vitoConfig

  private fun getFactory(): ImageOptionsDrawableFactory? {
    return ImagePipelineFactory.getInstance().getAnimatedDrawableFactory(null)?.let {
      DrawableFactoryWrapper(it)
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.fresco.vito.renderer

import android.graphics.Matrix
import android.graphics.Rect

/** Canvas transformation */
interface CanvasTransformation {

  /**
   * Calculate a transformation based on the given parent bounds and child dimensions. The given
   * outTransform should be used and returned. If no transformation should be applied, null can be
   * returned.
   *
   * @param outTransform the Matrix to re-use
   * @param parentBounds the bounds to fill
   * @param childWidth the width of the child to draw
   * @param childHeight the height of the child to draw
   * @return a Matrix (usually outTransform) or null if no transformation required
   */
  fun calculateTransformation(
      outTransform: Matrix,
      parentBounds: Rect,
      childWidth: Int,
      childHeight: Int
  ): Matrix?
}