// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.

/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.mountables.widgets

import android.content.Context
import android.graphics.Color
import android.graphics.PorterDuff
import android.graphics.drawable.Drawable
import com.facebook.litho.MeasureScope
import com.facebook.litho.MountableComponent
import com.facebook.litho.MountableComponentScope
import com.facebook.litho.MountableRenderResult
import com.facebook.litho.SimpleMountable
import com.facebook.litho.SizeSpec
import com.facebook.litho.Style
import com.facebook.litho.widget.ProgressView
import com.facebook.rendercore.MeasureResult

/**
 * Renders an infinitely spinning progress bar.
 *
 * @param indeterminateDrawable Drawable to be shown to show progress.
 * @param color Tint color for the drawable.
 */
class Progress(
    private val color: Int = Color.TRANSPARENT,
    private val indeterminateDrawable: Drawable? = null,
    private val style: Style? = null
) : MountableComponent() {

  override fun MountableComponentScope.render(): MountableRenderResult {
    return MountableRenderResult(
        ProgressMountable(color = color, indeterminateDrawable = indeterminateDrawable), style)
  }
}

private const val defaultSize: Int = 50

internal class ProgressMountable(
    private val color: Int,
    private val indeterminateDrawable: Drawable?
) : SimpleMountable<ProgressView>(RenderType.VIEW) {

  override fun createContent(context: Context): ProgressView = ProgressView(context)

  override fun MeasureScope.measure(widthSpec: Int, heightSpec: Int): MeasureResult {
    return if (SizeSpec.getMode(widthSpec) == SizeSpec.UNSPECIFIED &&
        SizeSpec.getMode(heightSpec) == SizeSpec.UNSPECIFIED) {
      MeasureResult(defaultSize, defaultSize)
    } else {
      withEqualSize(widthSpec, heightSpec)
    }
  }

  override fun mount(c: Context, content: ProgressView, layoutData: Any?) {
    indeterminateDrawable?.let { content.indeterminateDrawable = indeterminateDrawable }
    content.indeterminateDrawable?.let {
      if (color != Color.TRANSPARENT) {
        content.indeterminateDrawable.mutate().setColorFilter(color, PorterDuff.Mode.MULTIPLY)
      }
    }
  }

  override fun unmount(c: Context, content: ProgressView, layoutData: Any?) {
    // restore the color first, since it acts on the indeterminateDrawable
    if (color != Color.TRANSPARENT && content.indeterminateDrawable != null) {
      content.indeterminateDrawable.mutate().clearColorFilter()
    }
    content.indeterminateDrawable = null
  }

  override fun shouldUpdate(
      newMountable: SimpleMountable<ProgressView>,
      currentLayoutData: Any?,
      nextLayoutData: Any?
  ): Boolean {
    newMountable as ProgressMountable
    return newMountable.color != color ||
        newMountable.indeterminateDrawable != indeterminateDrawable
  }
}


// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.

/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.mountables.widgets

import android.content.Context
import android.graphics.Color
import androidx.annotation.ColorInt
import com.facebook.litho.DynamicValue
import com.facebook.litho.MeasureScope
import com.facebook.litho.MountableComponent
import com.facebook.litho.MountableComponentScope
import com.facebook.litho.MountableRenderResult
import com.facebook.litho.SimpleMountable
import com.facebook.litho.Style
import com.facebook.litho.widget.CardClipDrawable.BOTTOM_LEFT
import com.facebook.litho.widget.CardClipDrawable.BOTTOM_RIGHT
import com.facebook.litho.widget.CardClipDrawable.NONE
import com.facebook.litho.widget.CardClipDrawable.TOP_LEFT
import com.facebook.litho.widget.CardClipDrawable.TOP_RIGHT
import com.facebook.litho.widget.TransparencyEnabledCardClipDrawable
import com.facebook.rendercore.MeasureResult

private const val DEFAULT_BACKGROUND_COLOR = Color.WHITE
private const val DEFAULT_CLIPPING_COLOR = Color.TRANSPARENT

/**
 * A component that paints a card with rounded edges to perform a clipping operation on the
 * component being rendered below it. Used in [CardSpec] when transparencyEnabled(true).
 *
 * @param cardBackgroundColor Background color for the drawable.
 * @param clippingColor Color for corner clipping.
 * @param cornerRadius Radius for corner clipping.
 * @param disableClipTopLeft If set, opt out of clipping the top-left corner.
 * @param disableClipTopRight If set, opt out of clipping the top-right corner.
 * @param disableClipBottomLeft If set, opt out of clipping the bottom-left corner.
 * @param disableClipBottomRight If set, opt out of clipping the bottom-right corner.
 */
class TransparencyEnabledCardClip(
    private val cardBackgroundColor: Int = DEFAULT_BACKGROUND_COLOR,
    private val clippingColor: Int = DEFAULT_CLIPPING_COLOR,
    private val cornerRadius: Float? = null,
    private val disableClipTopLeft: Boolean = false,
    private val disableClipTopRight: Boolean = false,
    private val disableClipBottomLeft: Boolean = false,
    private val disableClipBottomRight: Boolean = false,
    @ColorInt private val dynamicCardBackgroundColor: DynamicValue<Int>? = null,
    private val style: Style? = null
) : MountableComponent() {

  override fun MountableComponentScope.render(): MountableRenderResult {
    dynamicCardBackgroundColor?.bindTo(
        DEFAULT_BACKGROUND_COLOR, TransparencyEnabledCardClipDrawable::setBackgroundColor)

    return MountableRenderResult(
        mountable =
            TransparencyEnabledCardClipMountable(
                cardBackgroundColor = cardBackgroundColor,
                clippingColor = clippingColor,
                cornerRadius = cornerRadius,
                disableClipTopLeft = disableClipTopLeft,
                disableClipTopRight = disableClipTopRight,
                disableClipBottomLeft = disableClipBottomLeft,
                disableClipBottomRight = disableClipBottomRight),
        style = style)
  }
}

internal class TransparencyEnabledCardClipMountable(
    private val cardBackgroundColor: Int,
    private val clippingColor: Int,
    private val cornerRadius: Float? = null,
    private val disableClipTopLeft: Boolean,
    private val disableClipTopRight: Boolean,
    private val disableClipBottomLeft: Boolean,
    private val disableClipBottomRight: Boolean
) : SimpleMountable<TransparencyEnabledCardClipDrawable>(RenderType.DRAWABLE) {

  override fun createContent(context: Context): TransparencyEnabledCardClipDrawable =
      TransparencyEnabledCardClipDrawable()

  override fun MeasureScope.measure(widthSpec: Int, heightSpec: Int): MeasureResult =
      fromSpecs(widthSpec, heightSpec)

  override fun mount(c: Context, content: TransparencyEnabledCardClipDrawable, layoutData: Any?) {
    content.setBackgroundColor(cardBackgroundColor)
    content.setClippingColor(clippingColor)
    cornerRadius?.let { content.setCornerRadius(cornerRadius) }
    val clipEdge =
        ((if (disableClipTopLeft) TOP_LEFT else NONE) or
            (if (disableClipTopRight) TOP_RIGHT else NONE) or
            (if (disableClipBottomLeft) BOTTOM_LEFT else NONE) or
            if (disableClipBottomRight) BOTTOM_RIGHT else NONE)
    content.setDisableClip(clipEdge)
  }

  override fun unmount(c: Context, content: TransparencyEnabledCardClipDrawable, layoutData: Any?) {
    content.setBackgroundColor(DEFAULT_BACKGROUND_COLOR)
    content.setClippingColor(DEFAULT_CLIPPING_COLOR)
    content.setCornerRadius(0f)
    content.resetCornerPaint()
    content.setDisableClip(NONE)
  }
}


// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.

/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.mountables.widgets

import android.content.Context
import android.os.Build
import android.view.MotionEvent
import android.view.View
import androidx.core.widget.NestedScrollView
import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.ComponentTree
import com.facebook.litho.Dimen
import com.facebook.litho.MeasureScope
import com.facebook.litho.MountableComponent
import com.facebook.litho.MountableComponentScope
import com.facebook.litho.MountableRenderResult
import com.facebook.litho.SimpleMountable
import com.facebook.litho.Size
import com.facebook.litho.SizeSpec
import com.facebook.litho.Style
import com.facebook.litho.Wrapper
import com.facebook.litho.px
import com.facebook.litho.useState
import com.facebook.litho.widget.LithoScrollView
import com.facebook.litho.widget.ScrollStateListener
import com.facebook.litho.widget.VerticalScrollEventsController
import com.facebook.rendercore.MeasureResult
import kotlin.math.max
import kotlin.math.min

fun VerticalScroll(
    scrollbarEnabled: Boolean = false,
    nestedScrollingEnabled: Boolean = false,
    verticalFadingEdgeEnabled: Boolean = false,
    fillViewport: Boolean = false,
    scrollbarFadingEnabled: Boolean = true,
    incrementalMountEnabled: Boolean = false,
    overScrollMode: Int = View.OVER_SCROLL_IF_CONTENT_SCROLLS,
    fadingEdgeLength: Int = 0,
    initialScrollPosition: Dimen = 0.px,
    eventsController: VerticalScrollEventsController? = null,
    onScrollChange: ((NestedScrollView, scrollY: Int, oldScrollY: Int) -> Unit)? = null,
    onInterceptTouch: ((NestedScrollView, event: MotionEvent) -> Boolean)? = null,
    onScrollStateChange: ((View, Int) -> Unit)? = null,
    style: Style? = null,
    child: () -> Component,
): Component {

  return VerticalScroll(
      scrollbarEnabled,
      nestedScrollingEnabled,
      verticalFadingEdgeEnabled,
      fillViewport,
      scrollbarFadingEnabled,
      incrementalMountEnabled,
      overScrollMode,
      fadingEdgeLength,
      initialScrollPosition,
      eventsController,
      onScrollChange,
      onInterceptTouch,
      onScrollStateChange,
      child(),
      style,
  )
}

class VerticalScroll(
    val scrollbarEnabled: Boolean = false,
    val nestedScrollingEnabled: Boolean = false,
    val verticalFadingEdgeEnabled: Boolean = false,
    val fillViewport: Boolean = false,
    val scrollbarFadingEnabled: Boolean = true,
    val incrementalMountEnabled: Boolean = false,
    val overScrollMode: Int = View.OVER_SCROLL_IF_CONTENT_SCROLLS,
    val fadingEdgeLength: Int = 0,
    val initialScrollPosition: Dimen = 0.px,
    val eventsController: VerticalScrollEventsController? = null,
    val onScrollChange: ((NestedScrollView, scrollY: Int, oldScrollY: Int) -> Unit)? = null,
    val onInterceptTouch: ((NestedScrollView, event: MotionEvent) -> Boolean)? = null,
    val onScrollStateChange: ((View, Int) -> Unit)? = null,
    val child: Component,
    val style: Style? = null,
) : MountableComponent() {

  override fun MountableComponentScope.render(): MountableRenderResult {

    val scrollPosition = useState {
      LithoScrollView.ScrollPosition(initialScrollPosition.toPixels())
    }

    val componentTree = useState {
      ComponentTree.createNestedComponentTree(context, child)
          .incrementalMount(incrementalMountEnabled)
          .build()
    }

    return MountableRenderResult(
        VerticalScrollMountable(
            child,
            scrollbarEnabled,
            nestedScrollingEnabled,
            verticalFadingEdgeEnabled,
            fillViewport,
            scrollbarFadingEnabled,
            incrementalMountEnabled,
            overScrollMode,
            fadingEdgeLength,
            eventsController,
            onScrollChange,
            onScrollStateChange,
            onInterceptTouch,
            scrollPosition.value,
            componentTree.value,
        ),
        style)
  }
}

internal class VerticalScrollMountable(
    val component: Component,
    val scrollbarEnabled: Boolean = false,
    val nestedScrollingEnabled: Boolean = false,
    val verticalFadingEdgeEnabled: Boolean = false,
    val fillViewport: Boolean = false,
    val scrollbarFadingEnabled: Boolean = true,
    val incrementalMountEnabled: Boolean = false,
    val overScrollMode: Int = View.OVER_SCROLL_IF_CONTENT_SCROLLS,
    val fadingEdgeLength: Int = 0,
    val eventsController: VerticalScrollEventsController? = null,
    val onScrollChangeListener: ((NestedScrollView, scrollY: Int, oldScrollY: Int) -> Unit)? = null,
    val scrollStateListener: ScrollStateListener? = null,
    val onInterceptTouchListener: ((NestedScrollView, event: MotionEvent) -> Boolean)? = null,
    val scrollPosition: LithoScrollView.ScrollPosition,
    val componentTree: ComponentTree,
) : SimpleMountable<LithoScrollView>(RenderType.VIEW) {

  override fun doesMountRenderTreeHosts(): Boolean = true

  override fun createContent(context: Context): LithoScrollView = LithoScrollView(context)

  override fun MeasureScope.measure(widthSpec: Int, heightSpec: Int): MeasureResult {
    val heightMode = SizeSpec.getMode(heightSpec)
    val height = max(0, SizeSpec.getSize(heightSpec))

    // If fillViewport is true, then set a minimum height to ensure that the viewport is filled.
    val actualComponent =
        if (fillViewport) {
          Wrapper.create(ComponentContext(androidContext))
              .delegate(component)
              .minHeightPx(height)
              .build()
        } else {
          component
        }

    val size = Size()

    componentTree.setRootAndSizeSpecSync(
        actualComponent,
        widthSpec,
        SizeSpec.makeSizeSpec(0, SizeSpec.UNSPECIFIED),
        size,
    )

    // Compute the appropriate size depending on the heightSpec
    when (heightMode) {
      // If this Vertical scroll is being measured with a fixed height we don't care
      // about the size of the content and just use that instead
      SizeSpec.EXACTLY -> size.height = height
      // For at most we want the VerticalScroll to be as big as its content up to
      // the maximum height specified in the heightSpec
      SizeSpec.AT_MOST -> size.height = max(0, min(height, size.height))
    }

    // Ensure that width is not less than 0
    size.width = max(0, size.width)

    return MeasureResult(size.width, size.height, VerticalScrollLayoutData(size.width, size.height))
  }

  override fun mount(c: Context, content: LithoScrollView, layoutData: Any?) {
    content.mount(
        componentTree,
        scrollPosition,
        scrollStateListener,
    )
    content.isScrollbarFadingEnabled = scrollbarFadingEnabled
    content.isNestedScrollingEnabled = nestedScrollingEnabled
    content.isVerticalFadingEdgeEnabled = verticalFadingEdgeEnabled
    content.setFadingEdgeLength(fadingEdgeLength)

    // On older versions we need to disable the vertical scroll bar as otherwise we run into an NPE
    // that was only fixed in Lollipop - see
    // https://github.com/aosp-mirror/platform_frameworks_base/commit/6c8fef7fb866d244486a962dd82f4a6f26505f16#diff-7c8b4c8147fbbbf69293775bca384f31.

    // On older versions we need to disable the vertical scroll bar as otherwise we run into an NPE
    // that was only fixed in Lollipop - see
    // https://github.com/aosp-mirror/platform_frameworks_base/commit/6c8fef7fb866d244486a962dd82f4a6f26505f16#diff-7c8b4c8147fbbbf69293775bca384f31.
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
      content.isVerticalScrollBarEnabled = false
    } else {
      content.isVerticalScrollBarEnabled = scrollbarEnabled
    }

    onScrollChangeListener?.let {
      content.setOnScrollChangeListener(
          NestedScrollView.OnScrollChangeListener {
              nestedScrollView: NestedScrollView,
              _: Int,
              scrollY: Int,
              _: Int,
              oldScrollY: Int ->
            onScrollChangeListener.invoke(nestedScrollView, scrollY, oldScrollY)
          })
    }

    content.setOnInterceptTouchListener(onInterceptTouchListener)
    content.overScrollMode = overScrollMode

    eventsController?.setScrollView(content)
  }

  override fun unmount(c: Context, content: LithoScrollView, layoutData: Any?) {
    eventsController?.setScrollView(null)
    content.setOnScrollChangeListener(null as NestedScrollView.OnScrollChangeListener?)
    content.setOnInterceptTouchListener(null)
    content.unmount()
  }

  override fun shouldUpdate(
      newMountable: SimpleMountable<LithoScrollView>,
      currentLayoutData: Any?,
      nextLayoutData: Any?
  ): Boolean {
    newMountable as VerticalScrollMountable
    return !component.isEquivalentTo(newMountable.component) ||
        scrollbarEnabled != newMountable.scrollbarEnabled ||
        scrollbarFadingEnabled != newMountable.scrollbarFadingEnabled ||
        fillViewport != newMountable.fillViewport ||
        nestedScrollingEnabled != newMountable.nestedScrollingEnabled ||
        incrementalMountEnabled != newMountable.incrementalMountEnabled
  }
}

internal data class VerticalScrollLayoutData(val measuredWidth: Int, val measuredHeight: Int)


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.mountables.widgets

import android.graphics.Color
import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.FlexboxContainerScope
import com.facebook.litho.KComponent
import com.facebook.litho.ResourcesScope
import com.facebook.litho.Row
import com.facebook.litho.Style
import com.facebook.litho.core.height
import com.facebook.litho.core.margin
import com.facebook.litho.core.width
import com.facebook.litho.dp
import com.facebook.litho.kotlin.widget.Text
import com.facebook.litho.view.background
import com.facebook.litho.view.backgroundColor
import com.facebook.samples.litho.R.drawable.ic_launcher
import com.facebook.samples.litho.kotlin.drawable.RoundedRect

class WidgetsExampleComponent : KComponent() {
  override fun ComponentScope.render(): Component {

    return Column {
      child(Text("CardClip"))
      child(
          CardClip(
              clippingColor = Color.WHITE,
              cornerRadius = 20f,
              style = Style.width(60.dp).height(60.dp).backgroundColor(Color.GRAY)))
      child(Text("CardShadow"))
      child(
          CardShadow(
              shadowEndColor = Color.GRAY,
              shadowSize = 100f,
              cornerRadius = 20f,
              style = Style.width(60.dp).height(60.dp)))
      child(Text("Image"))
      child(
          Image(
              drawable = context.getResources().getDrawable(ic_launcher),
              style = Style.width(100.dp).height(100.dp)))
      child(Text("Progress"))
      child(Progress(style = Style.width(100.dp).height(100.dp)))
      child(Text("Horizontal Scroll"))
      child(HorizontalScroll { Row { getComponents(this) } })
      child(Text("Vertical Scroll"))
      child(VerticalScroll(style = Style.height(100.dp)) { Column { getComponents(this) } })
    }
  }

  private fun ResourcesScope.getComponents(flexboxContainerScope: FlexboxContainerScope) {
    for (i in 1..10) {
      flexboxContainerScope.child(
          Column(
              style =
                  Style.width(50.dp)
                      .height(50.dp)
                      .margin(all = 5.dp)
                      .background(RoundedRect(if (i % 2 == 0) 0xff666699 else 0xffd9d9d9, 8.dp))))
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.observability

import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.litho.Style
import com.facebook.litho.core.margin
import com.facebook.litho.core.padding
import com.facebook.litho.dp
import com.facebook.litho.kotlin.widget.Text
import com.facebook.litho.sp
import com.facebook.litho.view.onClick

internal class HeaderComponent(private val name: String) : KComponent() {

  override fun ComponentScope.render(): Component? {
    return Text(
        style = Style.margin(top = 16.dp, horizontal = 16.dp),
        text = "Hello $name, click the counter to increment it!",
        textSize = 20.sp)
  }
}

internal class CounterComponent(private val counter: Int, private val onCounterClick: () -> Unit) :
    KComponent() {

  override fun ComponentScope.render(): Component? {
    return Text(
        "Counter: $counter",
        textSize = 20.sp,
        style = Style.onClick { onCounterClick() }.padding(all = 16.dp))
  }
}

internal data class ViewState(val counter: Int, val name: String)


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.observability

import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.litho.useFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow

// start_example
internal class UseFlowComponent(private val viewModel: FakeFlowViewModel = FakeFlowViewModel()) :
    KComponent() {

  override fun ComponentScope.render(): Component? {
    val viewState = useFlow(viewModel.flow)

    return Column {
      child(HeaderComponent(viewState.name))
      child(
          CounterComponent(
              counter = viewState.counter,
              onCounterClick = { viewModel.onIncrementCounterClick() }))
    }
  }
}
// end_example

internal class FakeFlowViewModel {

  private var myCounter = 10
  private val name = "John"

  private val _flow = MutableStateFlow(ViewState(myCounter, name))
  internal val flow: StateFlow<ViewState> = _flow

  fun onIncrementCounterClick() {
    myCounter++
    _flow.value = ViewState(myCounter, name)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.observability

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.litho.livedata.useLiveData

// start_example
internal class UseLiveDataComponent(
    private val viewModel: FakeLiveDataViewModel = FakeLiveDataViewModel()
) : KComponent() {

  override fun ComponentScope.render(): Component? {
    val viewState = useLiveData(viewModel.liveData) ?: error("should not be null")

    return Column {
      child(HeaderComponent(viewState.name))
      child(
          CounterComponent(
              counter = viewState.counter,
              onCounterClick = { viewModel.onIncrementCounterClick() }))
    }
  }
}
// end_example

internal class FakeLiveDataViewModel {

  private var myCounter = 10
  private val name = "John"

  private val _liveData = MutableLiveData(ViewState(myCounter, name))
  internal val liveData: LiveData<ViewState> = _liveData

  fun onIncrementCounterClick() {
    myCounter++
    _liveData.value = ViewState(myCounter, name)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.primitives

import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.widget.EditText
import androidx.appcompat.widget.AppCompatEditText
import com.facebook.litho.LithoPrimitive
import com.facebook.litho.PrimitiveComponent
import com.facebook.litho.PrimitiveComponentScope
import com.facebook.litho.Style
import com.facebook.rendercore.SizeConstraints
import com.facebook.rendercore.primitives.LayoutBehavior
import com.facebook.rendercore.primitives.LayoutScope
import com.facebook.rendercore.primitives.PrimitiveLayoutResult
import com.facebook.rendercore.primitives.ViewAllocator
import com.facebook.rendercore.toHeightSpec
import com.facebook.rendercore.toWidthSpec
import kotlin.math.max
import kotlin.math.min

/**
 * This class is an oversimplified version of a TextInput Component. It was created for the purpose
 * of showcasing layout() function implemented using creating a View and measuring it.
 */
class SampleTextInput(
    private val initialText: CharSequence = "",
    private val hint: CharSequence? = "",
    private val inputBackground: Drawable = ColorDrawable(Color.TRANSPARENT),
    private val style: Style? = null
) : PrimitiveComponent() {

  override fun PrimitiveComponentScope.render(): LithoPrimitive {
    return LithoPrimitive(
        layoutBehavior = SampleTextInputLayoutBehavior(initialText, hint, inputBackground),
        mountBehavior =
            MountBehavior(
                // start_preallocation_example
                ViewAllocator(canPreallocate = true, poolSize = 10) { context -> EditText(context) }
                // end_preallocation_example
                ) {
                  hint.bindTo(EditText::setHint, null)
                  initialText.bindTo(EditText::setText, null)
                  bind(inputBackground) { content ->
                    content.background = getBackgroundOrDefault(androidContext, inputBackground)
                    onUnbind { content.background = null }
                  }
                },
        style = style)
  }
}

internal class SampleTextInputLayoutBehavior(
    private val initialText: CharSequence,
    private val hintText: CharSequence?,
    private val inputBackground: Drawable
) : LayoutBehavior {
  // start_layout_with_view_measurement
  override fun LayoutScope.layout(sizeConstraints: SizeConstraints): PrimitiveLayoutResult {
    // The height should be the measured height of EditText with relevant params
    val editTextForMeasure: EditText =
        AppCompatEditText(androidContext).apply {
          setHint(hintText)
          setText(initialText)
          background =
              getBackgroundOrDefault(
                  androidContext,
                  if (inputBackground === ColorDrawable(Color.TRANSPARENT)) background
                  else inputBackground)
        }

    editTextForMeasure.measure(sizeConstraints.toWidthSpec(), sizeConstraints.toHeightSpec())
    val measuredWidth = max(sizeConstraints.minWidth, editTextForMeasure.measuredWidth)
    val measuredHeight = max(sizeConstraints.minHeight, editTextForMeasure.measuredHeight)

    // For width we always take all available space, or collapse to 0 if unspecified.
    val width =
        if (!sizeConstraints.hasBoundedWidth) 0 else min(sizeConstraints.maxWidth, measuredWidth)

    return PrimitiveLayoutResult(width, measuredHeight)
  }
  // end_layout_with_view_measurement
}

private fun getBackgroundOrDefault(context: Context, specifiedBackground: Drawable): Drawable? {
  if (specifiedBackground === ColorDrawable(Color.TRANSPARENT)) {
    val attrs = intArrayOf(android.R.attr.background)
    val a = context.obtainStyledAttributes(null, attrs, android.R.attr.editTextStyle, 0)
    val defaultBackground = a.getDrawable(0)
    a.recycle()
    return defaultBackground
  }
  return specifiedBackground
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.primitives

import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.litho.Style
import com.facebook.litho.core.height
import com.facebook.litho.core.margin
import com.facebook.litho.core.padding
import com.facebook.litho.core.width
import com.facebook.litho.dp
import com.facebook.litho.kotlin.widget.Text

class SimpleImageViewExamplePrimitiveComponent : KComponent() {

  override fun ComponentScope.render(): Component {
    return Column(style = Style.padding(all = 20.dp)) {
      child(
          SimpleImageViewPrimitiveComponent(
              style = Style.width(100.dp).height(100.dp).margin(all = 50.dp)))
      child(Text("Litho logo rendered using a Primitive Component", textSize = 16f.dp))
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.primitives

import android.widget.ImageView
import com.facebook.litho.LithoPrimitive
import com.facebook.litho.PrimitiveComponent
import com.facebook.litho.PrimitiveComponentScope
import com.facebook.litho.Style
import com.facebook.litho.drawableRes
import com.facebook.rendercore.Size
import com.facebook.rendercore.SizeConstraints
import com.facebook.rendercore.primitives.LayoutBehavior
import com.facebook.rendercore.primitives.LayoutScope
import com.facebook.rendercore.primitives.Primitive
import com.facebook.rendercore.primitives.PrimitiveLayoutResult
import com.facebook.rendercore.primitives.ViewAllocator
import com.facebook.rendercore.utils.withEqualDimensions
import com.facebook.samples.litho.R

// start_image_primitive_component_example
class SimpleImageViewPrimitiveComponent(private val style: Style? = null) : PrimitiveComponent() {

  override fun PrimitiveComponentScope.render(): LithoPrimitive {
    return LithoPrimitive(
        primitive =
            Primitive(
                layoutBehavior = ImageLayoutBehavior,
                mountBehavior =
                    MountBehavior(ViewAllocator { context -> ImageView(context) }) {
                      bind(R.drawable.ic_launcher) { imageView ->
                        imageView.setImageDrawable(drawableRes(R.drawable.ic_launcher))
                        onUnbind { imageView.setImageResource(0) }
                      }
                    }),
        style = style)
  }
}
// end_image_primitive_component_example

// start_image_primitive_layout_behavior_example
internal object ImageLayoutBehavior : LayoutBehavior {
  private const val defaultSize: Int = 150

  override fun LayoutScope.layout(sizeConstraints: SizeConstraints): PrimitiveLayoutResult {
    return PrimitiveLayoutResult(
        size =
            if (!sizeConstraints.hasBoundedWidth && !sizeConstraints.hasBoundedHeight) {
              Size(defaultSize, defaultSize)
            } else {
              Size.withEqualDimensions(sizeConstraints)
            })
  }
}
// end_image_primitive_layout_behavior_example


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.primitives.bindto

import android.graphics.Color
import android.widget.ImageView
import androidx.core.content.ContextCompat
import com.facebook.litho.DynamicValue
import com.facebook.litho.LithoPrimitive
import com.facebook.litho.PrimitiveComponent
import com.facebook.litho.PrimitiveComponentScope
import com.facebook.litho.Style
import com.facebook.rendercore.Size
import com.facebook.rendercore.SizeConstraints
import com.facebook.rendercore.primitives.LayoutBehavior
import com.facebook.rendercore.primitives.LayoutScope
import com.facebook.rendercore.primitives.PrimitiveLayoutResult
import com.facebook.rendercore.primitives.ViewAllocator
import com.facebook.rendercore.utils.withEqualDimensions
import com.facebook.samples.litho.R

class ImageViewPrimitiveComponent(
    private val rotation: DynamicValue<Float>,
    private val background: DynamicValue<Float>,
    private val scale: DynamicValue<Float>,
    private val style: Style? = null
) : PrimitiveComponent() {
  override fun PrimitiveComponentScope.render(): LithoPrimitive {
    return LithoPrimitive(
        layoutBehavior = ImageLayoutBehavior,
        mountBehavior =
            MountBehavior(ViewAllocator { context -> ImageView(context) }) {
              bind(R.drawable.ic_launcher) { imageView ->
                imageView.setImageDrawable(
                    ContextCompat.getDrawable(context.androidContext, R.drawable.ic_launcher))
                onUnbind { imageView.setImageResource(0) }
              }

              // simple binding
              bindDynamic(rotation, ImageView::setRotation, 0f)
              bindDynamic(scale, ImageView::setScaleX, 1f)
              bindDynamic(scale, ImageView::setScaleY, 1f)

              // complex binding
              bindDynamic(background) { imageView: ImageView, value ->
                imageView.setBackgroundColor(
                    Color.HSVToColor(floatArrayOf(evaluate(value, 0f, 360f), 1f, 1f)))

                onUnbindDynamic { imageView.setBackgroundColor(Color.BLACK) }
              }
            },
        style)
  }
}

internal object ImageLayoutBehavior : LayoutBehavior {
  private const val defaultSize: Int = 150

  // start_primitive_measure_example
  override fun LayoutScope.layout(sizeConstraints: SizeConstraints): PrimitiveLayoutResult {
    return PrimitiveLayoutResult(
        size =
            if (!sizeConstraints.hasBoundedWidth && !sizeConstraints.hasBoundedHeight) {
              Size(defaultSize, defaultSize)
            } else {
              Size.withEqualDimensions(sizeConstraints)
            })
  }
  // end_primitive_measure_example
}

fun evaluate(fraction: Float, start: Float, end: Float): Float = start + fraction * (end - start)


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.primitives.bindto

import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.litho.Style
import com.facebook.litho.animated.useBinding
import com.facebook.litho.core.height
import com.facebook.litho.core.margin
import com.facebook.litho.core.padding
import com.facebook.litho.core.width
import com.facebook.litho.dp
import com.facebook.samples.litho.kotlin.animations.dynamicprops.SeekBar

class PrimitiveBindToExampleComponent : KComponent() {

  override fun ComponentScope.render(): Component? {
    val background = useBinding(50f)
    val rotation = useBinding(0f)
    val scale = useBinding(1f)
    return Column(style = Style.padding(all = 20.dp)) {
      child(
          SeekBar(
              initialValue = 0f,
              label = "background",
              onProgressChanged = { backgroundValue -> background.set(backgroundValue) }))
      child(
          SeekBar(
              initialValue = 0f,
              label = "rotation",
              onProgressChanged = { rotationValue ->
                rotation.set(evaluate(rotationValue, 0f, 360f))
              }))
      child(
          SeekBar(
              initialValue = 1f,
              label = "scale",
              onProgressChanged = { scaleValue -> scale.set(evaluate(scaleValue, .75f, 1.25f)) }))
      child(
          Column(style = Style.width(100.dp).height(100.dp).margin(all = 50.dp)) {
            child(
                ImageViewPrimitiveComponent(
                    background = background, rotation = rotation, scale = scale))
          })
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.primitives.controllers

import android.widget.Toast
import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.litho.Style
import com.facebook.litho.core.padding
import com.facebook.litho.dp
import com.facebook.litho.useCached
import com.facebook.samples.litho.kotlin.collection.Button

class ControllersExamplePrimitiveComponent : KComponent() {
  // start_primitive_controllers_example
  override fun ComponentScope.render(): Component {
    return Column(style = Style.padding(16.dp)) {
      val initialHour = 14
      val initialMinute = 30
      val controller =
          useCached(initialHour, initialMinute) { TimePickerController(initialHour, initialMinute) }
      child(TimePickerPrimitiveComponent(controller = controller))
      child(
          Button(
              text = "Set random time ",
              onClick = {
                controller.hour = (0..24).random()
                controller.minute = (0..60).random()
              }))
      // end_primitive_controllers_example
      child(
          Button(
              text = "Get current time",
              onClick = {
                Toast.makeText(
                        androidContext,
                        String.format("%02d:%02d", controller.hour, controller.minute),
                        Toast.LENGTH_SHORT)
                    .show()
              }))
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.primitives.controllers

import android.os.Build
import android.widget.TimePicker
import com.facebook.litho.LithoPrimitive
import com.facebook.litho.PrimitiveComponent
import com.facebook.litho.PrimitiveComponentScope
import com.facebook.litho.Style
import com.facebook.litho.ThreadUtils
import com.facebook.rendercore.Size
import com.facebook.rendercore.SizeConstraints
import com.facebook.rendercore.primitives.LayoutBehavior
import com.facebook.rendercore.primitives.LayoutScope
import com.facebook.rendercore.primitives.PrimitiveLayoutResult
import com.facebook.rendercore.primitives.ViewAllocator
import com.facebook.rendercore.utils.withEqualDimensions

class TimePickerPrimitiveComponent(
    private val controller: TimePickerController? = null,
    private val style: Style? = null
) : PrimitiveComponent() {

  override fun PrimitiveComponentScope.render(): LithoPrimitive {
    return LithoPrimitive(
        layoutBehavior = TimePickerLayoutBehavior,
        mountBehavior =
            // start_primitive_controller_mount_behavior_example
            MountBehavior(ViewAllocator { context -> TimePicker(context) }) {
              bind(controller) { content ->
                controller?.bind(content)
                onUnbind { controller?.unbind() }
              }
            }
        // end_primitive_controller_mount_behavior_example
        ,
        style = style)
  }
}

internal object TimePickerLayoutBehavior : LayoutBehavior {
  override fun LayoutScope.layout(sizeConstraints: SizeConstraints): PrimitiveLayoutResult {
    return PrimitiveLayoutResult(size = Size.withEqualDimensions(sizeConstraints))
  }
}

// start_primitive_controller_code_example
class TimePickerController(private var currentHour: Int, private var currentMinute: Int) {
  var minute: Int
    get() {
      ThreadUtils.assertMainThread()
      return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        content?.minute ?: currentMinute
      } else {
        content?.currentMinute ?: currentMinute
      }
    }
    set(value) {
      ThreadUtils.assertMainThread()
      currentMinute = value
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        content?.minute = value
      } else {
        content?.currentMinute = value
      }
    }

  var hour: Int
    get() {
      ThreadUtils.assertMainThread()
      return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        content?.hour ?: currentHour
      } else {
        content?.currentHour ?: currentHour
      }
    }
    set(value) {
      ThreadUtils.assertMainThread()
      currentHour = value
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        content?.hour = value
      } else {
        content?.currentHour = value
      }
    }

  private val onTimeChangedListener =
      TimePicker.OnTimeChangedListener { _, hour, minute ->
        currentHour = hour
        currentMinute = minute
      }

  private var content: TimePicker? = null
  // end_primitive_controller_code_example

  // start_primitive_controller_bind_code_example
  fun bind(content: TimePicker) {
    this.content = content
    hour = currentHour
    minute = currentMinute
    this.content?.setOnTimeChangedListener(onTimeChangedListener)
  }

  fun unbind() {
    this.content?.setOnTimeChangedListener(null)
    this.content = null
  }
  // end_primitive_controller_bind_code_example
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.primitives.widgets

import android.graphics.Color
import com.facebook.litho.LithoPrimitive
import com.facebook.litho.PrimitiveComponent
import com.facebook.litho.PrimitiveComponentScope
import com.facebook.litho.Style
import com.facebook.litho.widget.CardClipDrawable
import com.facebook.rendercore.primitives.DrawableAllocator
import com.facebook.rendercore.primitives.ExactSizeConstraintsLayoutBehavior

/**
 * A component that paints rounded edges to mimic a clipping operation on the component being
 * rendered below it. Used in {@link CardSpec}.
 *
 * @param clippingColor Color for corner clipping.
 * @param cornerRadius Radius for corner clipping.
 * @param disableClipTopLeft If set, opt out of clipping the top-left corner
 * @param disableClipTopRight If set, opt out of clipping the top-right corner
 * @param disableClipBottomLeft If set, opt out of clipping the bottom-left corner
 * @param disableClipBottomRight If set, opt out of clipping the bottom-right corner
 */
class CardClip(
    private val clippingColor: Int = Color.WHITE,
    private val cornerRadius: Float? = null,
    private val disableClipTopLeft: Boolean = false,
    private val disableClipTopRight: Boolean = false,
    private val disableClipBottomLeft: Boolean = false,
    private val disableClipBottomRight: Boolean = false,
    private val style: Style? = null
) : PrimitiveComponent() {

  override fun PrimitiveComponentScope.render(): LithoPrimitive {
    return LithoPrimitive(
        layoutBehavior = ExactSizeConstraintsLayoutBehavior,
        mountBehavior =
            MountBehavior(DrawableAllocator { CardClipDrawable() }) {
              clippingColor.bindTo(CardClipDrawable::setClippingColor, Color.WHITE)
              bind(cornerRadius) { content ->
                cornerRadius?.let { content.setCornerRadius(it) }
                onUnbind { content.setCornerRadius(0f) }
              }
              bind(
                  disableClipTopLeft,
                  disableClipTopRight,
                  disableClipBottomLeft,
                  disableClipBottomRight) { content ->
                    val clipEdge =
                        ((if (disableClipTopLeft) CardClipDrawable.TOP_LEFT
                        else CardClipDrawable.NONE) or
                            (if (disableClipTopRight) CardClipDrawable.TOP_RIGHT
                            else CardClipDrawable.NONE) or
                            (if (disableClipBottomLeft) CardClipDrawable.BOTTOM_LEFT
                            else CardClipDrawable.NONE) or
                            if (disableClipBottomRight) CardClipDrawable.BOTTOM_RIGHT
                            else CardClipDrawable.NONE)
                    content.setDisableClip(clipEdge)
                    onUnbind { content.setDisableClip(CardClipDrawable.NONE) }
                  }
            },
        style)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.primitives.widgets

import android.graphics.Color
import com.facebook.litho.LithoPrimitive
import com.facebook.litho.PrimitiveComponent
import com.facebook.litho.PrimitiveComponentScope
import com.facebook.litho.Style
import com.facebook.litho.widget.CardShadowDrawable
import com.facebook.rendercore.primitives.DrawableAllocator
import com.facebook.rendercore.primitives.ExactSizeConstraintsLayoutBehavior

/**
 * A component that is able to render the card's shadow.
 *
 * @param shadowStartColor Start color for the shadow.
 * @param shadowEndColor End color for the shadow.
 * @param cornerRadius Corner radius for the card that shows the shadow.
 * @param shadowSize Size of the shadow.
 * @param shadowDx The x offset of the shadow.
 * @param shadowDy The y offset of the shadow.
 */
class CardShadow(
    private val shadowStartColor: Int? = null,
    private val shadowEndColor: Int? = null,
    private val cornerRadius: Float? = null,
    private val shadowSize: Float? = null,
    private val shadowDx: Float = CardShadowDrawable.UNDEFINED,
    private val shadowDy: Float = CardShadowDrawable.UNDEFINED,
    private val hideTopShadow: Boolean = false,
    private val hideBottomShadow: Boolean = false,
    private val shadowLeftSizeOverride: Float = CardShadowDrawable.UNDEFINED,
    private val shadowRightSizeOverride: Float = CardShadowDrawable.UNDEFINED,
    private val style: Style? = null
) : PrimitiveComponent() {
  override fun PrimitiveComponentScope.render(): LithoPrimitive {
    return LithoPrimitive(
        layoutBehavior = ExactSizeConstraintsLayoutBehavior,
        mountBehavior =
            MountBehavior(DrawableAllocator { CardShadowDrawable() }) {
              hideTopShadow.bindTo(CardShadowDrawable::setHideTopShadow, false)
              hideBottomShadow.bindTo(CardShadowDrawable::setHideBottomShadow, false)
              shadowLeftSizeOverride.bindTo(
                  CardShadowDrawable::setShadowLeftSizeOverride, CardShadowDrawable.UNDEFINED)
              shadowRightSizeOverride.bindTo(
                  CardShadowDrawable::setShadowRightSizeOverride, CardShadowDrawable.UNDEFINED)
              shadowDx.bindTo(CardShadowDrawable::setShadowDx, CardShadowDrawable.UNDEFINED)
              shadowDy.bindTo(CardShadowDrawable::setShadowDy, CardShadowDrawable.UNDEFINED)
              bind(shadowStartColor) { content ->
                shadowStartColor?.let { content.setShadowStartColor(it) }
                onUnbind { content.setShadowStartColor(Color.BLACK) }
              }
              bind(shadowEndColor) { content ->
                shadowEndColor?.let { content.setShadowEndColor(it) }
                onUnbind { content.setShadowEndColor(Color.BLACK) }
              }
              bind(cornerRadius) { content ->
                cornerRadius?.let { content.setCornerRadius(it) }
                onUnbind { content.setCornerRadius(0f) }
              }
              bind(shadowSize) { content ->
                shadowSize?.let { content.setShadowSize(it) }
                onUnbind { content.setShadowSize(0f) }
              }
            },
        style = style)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.primitives.widgets

import android.view.View
import android.view.ViewTreeObserver
import android.widget.HorizontalScrollView
import com.facebook.litho.Component
import com.facebook.litho.ComponentTree
import com.facebook.litho.LithoLayoutContextExtraData.LithoLayoutExtraData
import com.facebook.litho.LithoPrimitive
import com.facebook.litho.PrimitiveComponent
import com.facebook.litho.PrimitiveComponentScope
import com.facebook.litho.R
import com.facebook.litho.Size
import com.facebook.litho.Style
import com.facebook.litho.useState
import com.facebook.litho.widget.HorizontalScrollEventsController
import com.facebook.litho.widget.HorizontalScrollLithoView
import com.facebook.litho.widget.ScrollStateListener
import com.facebook.rendercore.SizeConstraints
import com.facebook.rendercore.extensions.doesMountRenderTreeHosts
import com.facebook.rendercore.primitives.LayoutBehavior
import com.facebook.rendercore.primitives.LayoutScope
import com.facebook.rendercore.primitives.PrimitiveLayoutResult
import com.facebook.rendercore.primitives.ViewAllocator
import com.facebook.rendercore.toHeightSpec
import com.facebook.rendercore.utils.MeasureSpecUtils
import com.facebook.yoga.YogaDirection
import kotlin.math.max

private const val LAST_SCROLL_POSITION_UNSET = -1
private const val DEFAULT_OVER_SCROLL_MODE = View.OVER_SCROLL_IF_CONTENT_SCROLLS

fun HorizontalScroll(
    scrollbarEnabled: Boolean = true,
    eventsController: HorizontalScrollEventsController? = null,
    onScrollChangeListener: HorizontalScrollLithoView.OnScrollChangeListener? = null,
    scrollStateListener: ScrollStateListener? = null,
    incrementalMountEnabled: Boolean = false,
    overScrollMode: Int = DEFAULT_OVER_SCROLL_MODE,
    initialScrollPosition: Int = LAST_SCROLL_POSITION_UNSET,
    fillViewport: Boolean = false,
    style: Style? = null,
    child: () -> Component,
): Component {

  return HorizontalScroll(
      scrollbarEnabled = scrollbarEnabled,
      eventsController = eventsController,
      onScrollChangeListener = onScrollChangeListener,
      scrollStateListener = scrollStateListener,
      incrementalMountEnabled = incrementalMountEnabled,
      overScrollMode = overScrollMode,
      initialScrollPosition = initialScrollPosition,
      fillViewport = fillViewport,
      child = child(),
      style = style,
  )
}

class HorizontalScroll(
    private val scrollbarEnabled: Boolean = true,
    private val eventsController: HorizontalScrollEventsController? = null,
    private val onScrollChangeListener: HorizontalScrollLithoView.OnScrollChangeListener? = null,
    private val scrollStateListener: ScrollStateListener? = null,
    private val incrementalMountEnabled: Boolean = false,
    private val overScrollMode: Int = DEFAULT_OVER_SCROLL_MODE,
    private val initialScrollPosition: Int = LAST_SCROLL_POSITION_UNSET,
    private val fillViewport: Boolean = false,
    private val child: Component,
    private val style: Style? = null
) : PrimitiveComponent() {
  override fun PrimitiveComponentScope.render(): LithoPrimitive {
    val lastScrollPosition = useState {
      HorizontalScrollLithoView.ScrollPosition(initialScrollPosition)
    }

    val childComponentTree = useState {
      ComponentTree.createNestedComponentTree(context, child)
          .incrementalMount(incrementalMountEnabled)
          .build()
    }

    val scrollbarsAttr =
        getIntAttrValue(
            context,
            R.styleable.HorizontalScroll_android_scrollbars,
            R.styleable.HorizontalScroll,
            -1)

    val resolvedScrollbarEnabled =
        if (scrollbarsAttr == -1) scrollbarEnabled else scrollbarsAttr != 0

    return LithoPrimitive(
        layoutBehavior =
            HorizontalScrollLayoutBehavior(fillViewport, child, childComponentTree.value),
        mountBehavior =
            MountBehavior(ViewAllocator { context -> HorizontalScrollLithoView(context) }) {
              doesMountRenderTreeHosts = true

              resolvedScrollbarEnabled.bindTo(
                  HorizontalScrollLithoView::setHorizontalScrollBarEnabled, true)

              overScrollMode.bindTo(
                  HorizontalScrollLithoView::setOverScrollMode, DEFAULT_OVER_SCROLL_MODE)

              bind(eventsController) { content ->
                eventsController?.setScrollableView(content)
                onUnbind { eventsController?.setScrollableView(null) }
              }

              bindWithLayoutData<HorizontalScrollLayoutData>(
                  childComponentTree.value,
                  lastScrollPosition.value,
                  onScrollChangeListener,
                  scrollStateListener) { content, horizontalScrollLayoutData ->
                    content.mount(
                        childComponentTree.value,
                        lastScrollPosition.value,
                        horizontalScrollLayoutData.measuredWidth,
                        horizontalScrollLayoutData.measuredHeight,
                        onScrollChangeListener,
                        scrollStateListener)
                    onUnbind { content.unmount() }
                  }

              bindWithLayoutData<HorizontalScrollLayoutData>(lastScrollPosition.value) {
                  content,
                  horizontalScrollLayoutData ->
                val onPreDrawListener =
                    object : ViewTreeObserver.OnPreDrawListener {
                      override fun onPreDraw(): Boolean {
                        if (!content.viewTreeObserver.isAlive) {
                          return true
                        }
                        content.viewTreeObserver.removeOnPreDrawListener(this)
                        if (lastScrollPosition.value.x == LAST_SCROLL_POSITION_UNSET) {
                          if (horizontalScrollLayoutData.layoutDirection == YogaDirection.RTL) {
                            content.fullScroll(HorizontalScrollView.FOCUS_RIGHT)
                          }
                          lastScrollPosition.value.x = content.scrollX
                        } else {
                          content.scrollX = lastScrollPosition.value.x
                        }
                        return true
                      }
                    }

                content.viewTreeObserver.addOnPreDrawListener(onPreDrawListener)
                onUnbind {
                  if (content.viewTreeObserver.isAlive) {
                    content.viewTreeObserver.removeOnPreDrawListener(onPreDrawListener)
                  }
                }
              }
            },
        style = style)
  }
}

internal class HorizontalScrollLayoutBehavior(
    private val fillViewport: Boolean,
    private val childComponent: Component,
    private val childComponentTree: ComponentTree
) : LayoutBehavior {
  override fun LayoutScope.layout(sizeConstraints: SizeConstraints): PrimitiveLayoutResult {
    val size = Size()

    // Measure the component with undefined width spec, as the contents of the
    // hscroll have unlimited horizontal space.
    childComponentTree.setRootAndSizeSpecSync(
        childComponent, MeasureSpecUtils.unspecified(), sizeConstraints.toHeightSpec(), size)

    size.width = max(0, max(size.width, if (fillViewport) sizeConstraints.maxWidth else 0))
    size.height = max(0, size.height)

    val extraLayoutData: LithoLayoutExtraData? =
        layoutContext.layoutContextExtraData?.extraLayoutData as? LithoLayoutExtraData?
    val direction = extraLayoutData?.layoutDirection ?: DEFAULT_LAYOUT_DIRECTION
    return PrimitiveLayoutResult(
        width = size.width,
        height = size.height,
        layoutData = HorizontalScrollLayoutData(size.width, size.height, direction))
  }

  companion object {
    private val DEFAULT_LAYOUT_DIRECTION = YogaDirection.LTR
  }
}

internal data class HorizontalScrollLayoutData(
    val measuredWidth: Int,
    val measuredHeight: Int,
    val layoutDirection: YogaDirection
)


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.primitives.widgets

import android.graphics.drawable.Drawable
import android.widget.ImageView.ScaleType
import com.facebook.litho.DrawableMatrix
import com.facebook.litho.LithoPrimitive
import com.facebook.litho.MatrixDrawable
import com.facebook.litho.PrimitiveComponent
import com.facebook.litho.PrimitiveComponentScope
import com.facebook.litho.Style
import com.facebook.rendercore.Size
import com.facebook.rendercore.SizeConstraints
import com.facebook.rendercore.primitives.DrawableAllocator
import com.facebook.rendercore.primitives.LayoutBehavior
import com.facebook.rendercore.primitives.LayoutScope
import com.facebook.rendercore.primitives.PrimitiveLayoutResult
import com.facebook.rendercore.utils.withAspectRatio

/**
 * A component to render a [Drawable].
 *
 * Tip: Use the same instance of the [drawable] to avoid remounting it in subsequent layouts to
 * improve performance.
 *
 * @param drawable The [Drawable] to render.
 * @param scaleType The [ScaleType] to scale or transform the [drawable].
 */
class Image(
    private val drawable: Drawable,
    private val scaleType: ScaleType = ScaleType.FIT_XY,
    private val style: Style? = null
) : PrimitiveComponent() {
  override fun PrimitiveComponentScope.render(): LithoPrimitive {
    return LithoPrimitive(
        layoutBehavior = ImageLayoutBehavior(drawable, scaleType),
        mountBehavior =
            // start_image_primitive_mount_behavior_example
            MountBehavior(DrawableAllocator(poolSize = 30) { MatrixDrawable<Drawable>() }) {
              bindWithLayoutData<PrimitiveImageLayoutData>(drawable, scaleType) {
                  content,
                  layoutData ->
                content.mount(drawable, layoutData.matrix)
                content.bind(layoutData.width, layoutData.height)
                onUnbind { content.unmount() }
              }
            }
        // end_image_primitive_mount_behavior_example
        ,
        style = style)
  }
}

internal class ImageLayoutBehavior(
    private val drawable: Drawable,
    private val scaleType: ScaleType
) : LayoutBehavior {
  override fun LayoutScope.layout(sizeConstraints: SizeConstraints): PrimitiveLayoutResult {
    val intrinsicWidth = drawable.intrinsicWidth
    val intrinsicHeight = drawable.intrinsicHeight

    val size =
        if (!sizeConstraints.hasBoundedWidth && !sizeConstraints.hasBoundedHeight) {
          Size(intrinsicWidth, intrinsicHeight)
        } else {
          Size.withAspectRatio(
              sizeConstraints,
              intrinsicWidth.toFloat() / intrinsicHeight.toFloat(),
              intrinsicWidth,
              intrinsicHeight)
        }

    val matrix =
        if (scaleType == ScaleType.FIT_XY || intrinsicWidth <= 0 || intrinsicHeight <= 0) {
          null
        } else {
          DrawableMatrix.create(drawable, scaleType, size.width, size.height)
        }

    val useLayoutSize = ScaleType.FIT_XY == scaleType || intrinsicWidth <= 0 || intrinsicHeight <= 0

    return PrimitiveLayoutResult(
        size = size,
        layoutData =
            PrimitiveImageLayoutData(
                if (useLayoutSize) size.width else intrinsicWidth,
                if (useLayoutSize) size.height else intrinsicHeight,
                matrix))
  }
}

/**
 * The layout data required by the [Image] Primitive to mount, and bind the drawable in the host.
 */
class PrimitiveImageLayoutData(val width: Int, val height: Int, val matrix: DrawableMatrix? = null)


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.primitives.widgets

import android.graphics.Color
import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.FlexboxContainerScope
import com.facebook.litho.KComponent
import com.facebook.litho.ResourcesScope
import com.facebook.litho.Row
import com.facebook.litho.Style
import com.facebook.litho.core.height
import com.facebook.litho.core.margin
import com.facebook.litho.core.width
import com.facebook.litho.dp
import com.facebook.litho.drawableRes
import com.facebook.litho.kotlin.widget.Text
import com.facebook.litho.view.background
import com.facebook.litho.view.backgroundColor
import com.facebook.samples.litho.R.drawable.ic_launcher
import com.facebook.samples.litho.kotlin.drawable.RoundedRect

class PrimitiveWidgetsExampleComponent : KComponent() {
  override fun ComponentScope.render(): Component {

    return Column {
      child(Text("CardClip"))
      child(
          CardClip(
              clippingColor = Color.WHITE,
              cornerRadius = 20f,
              style = Style.width(60.dp).height(60.dp).backgroundColor(Color.GRAY)))
      child(Text("CardShadow"))
      child(
          CardShadow(
              shadowEndColor = Color.GRAY,
              shadowSize = 100f,
              cornerRadius = 20f,
              style = Style.width(60.dp).height(60.dp)))
      child(Text("Image"))
      child(Image(drawable = drawableRes(ic_launcher), style = Style.width(100.dp).height(100.dp)))
      child(Text("Progress"))
      child(Progress(style = Style.width(100.dp).height(100.dp)))
      child(Text("Horizontal Scroll"))
      child(HorizontalScroll { Row { getComponents(this) } })
      child(Text("Vertical Scroll"))
      child(VerticalScroll(style = Style.height(100.dp)) { Column { getComponents(this) } })
    }
  }

  private fun ResourcesScope.getComponents(flexboxContainerScope: FlexboxContainerScope) {
    for (i in 1..10) {
      flexboxContainerScope.child(
          Column(
              style =
                  Style.width(50.dp)
                      .height(50.dp)
                      .margin(all = 5.dp)
                      .background(RoundedRect(if (i % 2 == 0) 0xff666699 else 0xffd9d9d9, 8.dp))))
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.primitives.widgets

import android.graphics.Color
import android.graphics.PorterDuff
import android.graphics.drawable.Drawable
import com.facebook.litho.LithoPrimitive
import com.facebook.litho.PrimitiveComponent
import com.facebook.litho.PrimitiveComponentScope
import com.facebook.litho.Style
import com.facebook.litho.widget.ProgressView
import com.facebook.rendercore.Size
import com.facebook.rendercore.SizeConstraints
import com.facebook.rendercore.primitives.LayoutBehavior
import com.facebook.rendercore.primitives.LayoutScope
import com.facebook.rendercore.primitives.PrimitiveLayoutResult
import com.facebook.rendercore.primitives.ViewAllocator
import com.facebook.rendercore.utils.withEqualDimensions

/**
 * Renders an infinitely spinning progress bar.
 *
 * @param indeterminateDrawable Drawable to be shown to show progress.
 * @param color Tint color for the drawable.
 */
class Progress(
    private val color: Int = Color.TRANSPARENT,
    private val indeterminateDrawable: Drawable? = null,
    private val style: Style? = null
) : PrimitiveComponent() {

  @Suppress("DEPRECATION")
  override fun PrimitiveComponentScope.render(): LithoPrimitive {
    return LithoPrimitive(
        layoutBehavior = ProgressLayoutBehavior,
        mountBehavior =
            MountBehavior(ViewAllocator { context -> ProgressView(context) }) {
              bind(indeterminateDrawable, color) { content ->
                val defaultIndeterminateDrawable = content.indeterminateDrawable
                indeterminateDrawable?.let { content.indeterminateDrawable = indeterminateDrawable }
                content.indeterminateDrawable?.let {
                  if (color != Color.TRANSPARENT) {
                    content.indeterminateDrawable
                        .mutate()
                        .setColorFilter(color, PorterDuff.Mode.MULTIPLY)
                  }
                }
                onUnbind {
                  // restore the color first, since it acts on the indeterminateDrawable
                  if (color != Color.TRANSPARENT && content.indeterminateDrawable != null) {
                    content.indeterminateDrawable.mutate().clearColorFilter()
                  }
                  content.indeterminateDrawable = defaultIndeterminateDrawable
                }
              }
            },
        style = style)
  }
}

internal object ProgressLayoutBehavior : LayoutBehavior {
  private const val DEFAULT_SIZE: Int = 50

  override fun LayoutScope.layout(sizeConstraints: SizeConstraints): PrimitiveLayoutResult {
    return PrimitiveLayoutResult(
        size =
            if (!sizeConstraints.hasBoundedWidth && !sizeConstraints.hasBoundedHeight) {
              Size(DEFAULT_SIZE, DEFAULT_SIZE)
            } else {
              Size.withEqualDimensions(sizeConstraints)
            })
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.primitives.widgets

import android.os.Build
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import androidx.core.widget.NestedScrollView
import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.ComponentTree
import com.facebook.litho.Dimen
import com.facebook.litho.LithoPrimitive
import com.facebook.litho.PrimitiveComponent
import com.facebook.litho.PrimitiveComponentScope
import com.facebook.litho.Size
import com.facebook.litho.Style
import com.facebook.litho.Wrapper
import com.facebook.litho.px
import com.facebook.litho.useState
import com.facebook.litho.widget.LithoScrollView
import com.facebook.litho.widget.VerticalScrollEventsController
import com.facebook.rendercore.SizeConstraints
import com.facebook.rendercore.extensions.doesMountRenderTreeHosts
import com.facebook.rendercore.primitives.LayoutBehavior
import com.facebook.rendercore.primitives.LayoutScope
import com.facebook.rendercore.primitives.PrimitiveLayoutResult
import com.facebook.rendercore.primitives.ViewAllocator
import com.facebook.rendercore.toWidthSpec
import com.facebook.rendercore.utils.MeasureSpecUtils
import kotlin.math.max
import kotlin.math.min

fun VerticalScroll(
    scrollbarEnabled: Boolean = false,
    nestedScrollingEnabled: Boolean = false,
    verticalFadingEdgeEnabled: Boolean = false,
    fillViewport: Boolean = false,
    scrollbarFadingEnabled: Boolean = true,
    incrementalMountEnabled: Boolean = false,
    overScrollMode: Int = View.OVER_SCROLL_IF_CONTENT_SCROLLS,
    fadingEdgeLength: Int = 0,
    initialScrollPosition: Dimen = 0.px,
    eventsController: VerticalScrollEventsController? = null,
    onScrollChange: ((NestedScrollView, scrollY: Int, oldScrollY: Int) -> Unit)? = null,
    onInterceptTouch: ((NestedScrollView, event: MotionEvent) -> Boolean)? = null,
    onScrollStateChange: ((View, Int) -> Unit)? = null,
    style: Style? = null,
    child: () -> Component,
): Component {

  return VerticalScroll(
      scrollbarEnabled,
      nestedScrollingEnabled,
      verticalFadingEdgeEnabled,
      fillViewport,
      scrollbarFadingEnabled,
      incrementalMountEnabled,
      overScrollMode,
      fadingEdgeLength,
      initialScrollPosition,
      eventsController,
      onScrollChange,
      onInterceptTouch,
      onScrollStateChange,
      child(),
      style,
  )
}

class VerticalScroll(
    val scrollbarEnabled: Boolean = false,
    val nestedScrollingEnabled: Boolean = false,
    val verticalFadingEdgeEnabled: Boolean = false,
    val fillViewport: Boolean = false,
    val scrollbarFadingEnabled: Boolean = true,
    val incrementalMountEnabled: Boolean = false,
    val overScrollMode: Int = View.OVER_SCROLL_IF_CONTENT_SCROLLS,
    val fadingEdgeLength: Int = 0,
    val initialScrollPosition: Dimen = 0.px,
    val eventsController: VerticalScrollEventsController? = null,
    val onScrollChange: ((NestedScrollView, scrollY: Int, oldScrollY: Int) -> Unit)? = null,
    val onInterceptTouch: ((NestedScrollView, event: MotionEvent) -> Boolean)? = null,
    val onScrollStateChange: ((View, Int) -> Unit)? = null,
    val child: Component,
    val style: Style? = null,
) : PrimitiveComponent() {

  override fun PrimitiveComponentScope.render(): LithoPrimitive {
    val scrollPosition = useState {
      LithoScrollView.ScrollPosition(initialScrollPosition.toPixels())
    }

    val componentTree = useState {
      ComponentTree.createNestedComponentTree(context, child)
          .incrementalMount(incrementalMountEnabled)
          .build()
    }

    return LithoPrimitive(
        layoutBehavior = VerticalScrollLayoutBehavior(child, fillViewport, componentTree.value),
        mountBehavior =
            MountBehavior(ViewAllocator { context -> LithoScrollView(context) }) {
              doesMountRenderTreeHosts = true

              bind(componentTree.value, scrollPosition.value, onScrollStateChange) { content ->
                content.mount(
                    componentTree.value,
                    scrollPosition.value,
                    onScrollStateChange,
                )
                onUnbind { content.unmount() }
              }

              scrollbarFadingEnabled.bindTo(
                  LithoScrollView::setScrollbarFadingEnabled, false) // todo check default

              nestedScrollingEnabled.bindTo(
                  LithoScrollView::setNestedScrollingEnabled, false) // todo check default

              verticalFadingEdgeEnabled.bindTo(
                  LithoScrollView::setVerticalFadingEdgeEnabled, false) // todo check default

              fadingEdgeLength.bindTo(
                  LithoScrollView::setFadingEdgeLength,
                  ViewConfiguration.get(androidContext)
                      .scaledFadingEdgeLength) // todo check default

              bind(scrollbarEnabled) { content ->
                // On older versions we need to disable the vertical scroll bar as otherwise we run
                // into an NPE
                // that was only fixed in Lollipop - see
                // https://github.com/aosp-mirror/platform_frameworks_base/commit/6c8fef7fb866d244486a962dd82f4a6f26505f16#diff-7c8b4c8147fbbbf69293775bca384f31.
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                  content.isVerticalScrollBarEnabled = false
                } else {
                  content.isVerticalScrollBarEnabled = scrollbarEnabled
                }
                onUnbind {
                  content.isVerticalScrollBarEnabled = false // todo check default
                }
              }

              bind(onScrollChange) { content ->
                onScrollChange?.let { onScrollChangeListener ->
                  content.setOnScrollChangeListener {
                      nestedScrollView: NestedScrollView,
                      _: Int,
                      scrollY: Int,
                      _: Int,
                      oldScrollY: Int ->
                    onScrollChangeListener(nestedScrollView, scrollY, oldScrollY)
                  }
                }
                onUnbind {
                  content.setOnScrollChangeListener(
                      null as NestedScrollView.OnScrollChangeListener?)
                }
              }

              bind(onInterceptTouch) { content ->
                content.setOnInterceptTouchListener(onInterceptTouch)
                onUnbind { content.setOnInterceptTouchListener(null) }
              }

              overScrollMode.bindTo(
                  LithoScrollView::setOverScrollMode, View.OVER_SCROLL_IF_CONTENT_SCROLLS)

              bind(eventsController) { content ->
                eventsController?.setScrollView(content)
                onUnbind { eventsController?.setScrollView(null) }
              }
            },
        style = style)
  }
}

internal class VerticalScrollLayoutBehavior(
    private val component: Component,
    private val fillViewport: Boolean,
    private val componentTree: ComponentTree
) : LayoutBehavior {
  override fun LayoutScope.layout(sizeConstraints: SizeConstraints): PrimitiveLayoutResult {
    // If fillViewport is true, then set a minimum height to ensure that the viewport is filled.
    val actualComponent =
        if (fillViewport) {
          Wrapper.create(ComponentContext(androidContext))
              .delegate(component)
              .minHeightPx(sizeConstraints.maxHeight)
              .build()
        } else {
          component
        }

    val size = Size()

    componentTree.setRootAndSizeSpecSync(
        actualComponent,
        sizeConstraints.toWidthSpec(),
        MeasureSpecUtils.unspecified(),
        size,
    )

    // Compute the appropriate size depending on the heightSpec
    if (sizeConstraints.hasExactHeight) {
      // If this Vertical scroll is being measured with an exact height we don't care
      // about the size of the content and just use that instead
      size.height = sizeConstraints.maxHeight
    } else if (sizeConstraints.hasBoundedHeight) {
      // For bounded height we want the VerticalScroll to be as big as its content up to
      // the maximum height specified in the heightSpec
      size.height = max(0, min(sizeConstraints.maxHeight, size.height))
    }

    // Ensure that width is not less than 0
    size.width = max(0, size.width)

    return PrimitiveLayoutResult(
        width = size.width,
        height = size.height,
        layoutData = VerticalScrollLayoutData(size.width, size.height))
  }
}

internal data class VerticalScrollLayoutData(val measuredWidth: Int, val measuredHeight: Int)


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.sizeconstraintsawarecomponent

import android.view.View.MeasureSpec.UNSPECIFIED
import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.litho.Row
import com.facebook.litho.Size
import com.facebook.litho.SizeSpec
import com.facebook.litho.Style
import com.facebook.litho.core.margin
import com.facebook.litho.core.padding
import com.facebook.litho.core.width
import com.facebook.litho.dp
import com.facebook.litho.drawableRes
import com.facebook.litho.kotlin.widget.Image
import com.facebook.litho.kotlin.widget.SizeConstraintsAwareComponent
import com.facebook.litho.kotlin.widget.Text
import com.facebook.litho.sp
import com.facebook.litho.useState
import com.facebook.litho.view.onClick
import com.facebook.samples.litho.R
import com.facebook.yoga.YogaAlign

class SizeConstraintsAwareComponentKComponent : KComponent() {

  override fun ComponentScope.render(): Component {
    val width = useState { 50 }

    return Column(alignItems = YogaAlign.CENTER, style = Style.padding(all = 20.dp)) {
      child(
          Row {
            child(
                Text(
                    text = "â€”",
                    textSize = 30.dp,
                    style =
                        Style.padding(all = 30.dp).onClick {
                          width.update { prevCount -> 0.coerceAtLeast(prevCount - 25) }
                        }))
            child(
                Text(text = "${width.value}", textSize = 30.dp, style = Style.margin(all = 30.dp)))
            child(
                Text(
                    text = "+",
                    textSize = 30.dp,
                    style =
                        Style.padding(all = 30.dp).onClick {
                          width.update { prevCount -> prevCount + 25 }
                        }))
          })
      child(
          // start_sizeconstraintsawarecomponent_example
          SizeConstraintsAwareComponent(style = Style.width(width.value.dp)) { sizeConstraints ->
            val textComponent = Text(textSize = 16.sp, text = "Some text to measure")

            val textOutputSize = Size()

            textComponent.measure(
                context,
                SizeSpec.makeSizeSpec(0, UNSPECIFIED),
                SizeSpec.makeSizeSpec(0, UNSPECIFIED),
                textOutputSize)

            // Small component to use in case textComponent doesnâ€™t fit within
            // the current layout.
            val imageComponent = Image(drawable = drawableRes(R.drawable.ic_launcher))

            // Assuming sizeConstraints.hasBoundedWidth == true
            val doesTextFit = textOutputSize.width <= sizeConstraints.maxWidth

            if (doesTextFit) textComponent else imageComponent
          })
      // end_sizeconstraintsawarecomponent_example
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.state

import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.litho.Style
import com.facebook.litho.kotlin.widget.Text
import com.facebook.litho.view.onClick

// start_example_child
class ChildComponent(private val onIncrementCounter: () -> Unit) : KComponent() {
  override fun ComponentScope.render(): Component {
    return Text(
        style = Style.onClick { onIncrementCounter() },
        text = "Tap to increment the parent!",
    )
  }
}
// end_example_child


// (c) Facebook, Inc. and its affiliates. Confidential and proprietary.

/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.state

import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.litho.Row
import com.facebook.litho.Style
import com.facebook.litho.core.margin
import com.facebook.litho.dp
import com.facebook.litho.kotlin.widget.Text
import com.facebook.litho.useState
import com.facebook.litho.view.onClick

// start_counter
class CounterComponent : KComponent() {

  override fun ComponentScope.render(): Component {
    val counter = useState { 0 }
    return Row {
      child(
          Text(
              text = "+",
              textSize = 30.dp,
              style =
                  Style.margin(all = 30.dp).onClick {
                    counter.update { prevCount -> prevCount + 1 }
                  }))
      child(Text(text = "${counter.value}", textSize = 30.dp, style = Style.margin(all = 30.dp)))
      child(
          Text(
              text = "-",
              textSize = 30.dp,
              style =
                  Style.margin(all = 30.dp).onClick {
                    counter.update { prevCount -> prevCount - 1 }
                  }))
    }
  }
}
// end_counter


// (c) Facebook, Inc. and its affiliates. Confidential and proprietary.

/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.state

import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.litho.Row
import com.facebook.litho.Style
import com.facebook.litho.core.margin
import com.facebook.litho.dp
import com.facebook.litho.key
import com.facebook.litho.kotlin.widget.Text
import com.facebook.litho.useRef
import com.facebook.litho.useState
import com.facebook.litho.view.onClick
import com.facebook.litho.visibility.onVisible

class IdentityRootComponent : KComponent() {
  override fun ComponentScope.render(): Component {
    val isFirstCounterEnabled = useState { true }
    val isSecondCounterEnabled = useState { true }
    // start_use_ref
    val logOnce = useRef { false }
    // start_manual_key
    return Column(
        style =
            Style.onVisible {
              if (!logOnce.value) {
                // do some logging
                logOnce.value = true
              }
            }) { // end_use_ref
          if (isFirstCounterEnabled.value) {
            child(
                key("first_row") {
                  Row {
                    child(CounterComponent())
                    child(
                        Text(
                            text = "X",
                            textSize = 30.dp,
                            style =
                                Style.margin(all = 30.dp).onClick {
                                  isFirstCounterEnabled.update(false)
                                }))
                  }
                })
          }
          // end_manual_key
          if (isSecondCounterEnabled.value) {
            child(
                key("second_row") {
                  Row {
                    child(CounterComponent())
                    child(
                        Text(
                            text = "X",
                            textSize = 30.dp,
                            style =
                                Style.margin(all = 30.dp).onClick {
                                  isSecondCounterEnabled.update(false)
                                }))
                  }
                })
          }
        }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.state

import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.litho.kotlin.widget.Text
import com.facebook.litho.useState

// start_example_parent
class StateParentChildComponent : KComponent() {
  override fun ComponentScope.render(): Component {
    val clicks = useState { 0 }
    return Column {
      child(ChildComponent { clicks.update { c -> c + 1 } })
      child(Text(text = "Counter: ${clicks.value}"))
    }
  }
}
// end_example_parent


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.treeprops

import android.graphics.Typeface
import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.litho.getTreeProp
import com.facebook.litho.kotlin.widget.Text

class TreePropsChildComponent : KComponent() {

  override fun ComponentScope.render(): Component? {
    // using_tree_prop_start
    val color = getTreeProp<Int>()
    val typeface = getTreeProp<Typeface>()
    val title = getTreeProp<String>()
    // using_tree_prop_end
    return Text(text = title, typeface = typeface, backgroundColor = color)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.samples.litho.kotlin.treeprops

import android.graphics.Color
import android.graphics.Typeface
import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.litho.TreePropProvider

class TreePropsExampleComponent : KComponent() {
  override fun ComponentScope.render(): Component? {
    // declaring_tree_prop_start
    return TreePropProvider(
        Typeface::class.java to Typeface.DEFAULT_BOLD,
        String::class.java to getTextTitle(),
        Int::class.java to Color.RED) {
          TreePropsChildComponent()
        }
    // declaring_tree_prop_end
  }
}

private fun getTextTitle(): String {
  return "Text Title"
}