package com.fireworksdk.bridge.components.videofeed import kotlin.math.ceil /** UI-thread state for bounded forced measurement and native -> Yoga height feedback. */ internal class FWAutosizingState { enum class Pass { NONE, NORMAL, FORCE } private var dirty = true private var measuredWidth = -1 private var roundActive = false private var passesLeft = 0 private var previousHeightPx: Int? = null private var contentEmpty = false private var lastReportedHeightDp: Float? = null fun invalidate() { dirty = true } fun setContentEmpty(empty: Boolean) { contentEmpty = empty invalidate() } fun nextPass(width: Int, layoutRequested: Boolean): Pass { if (width <= 0) return Pass.NONE if (dirty || measuredWidth != width || (!roundActive && layoutRequested)) { dirty = false measuredWidth = width roundActive = true passesLeft = MAX_SETTLE_PASSES previousHeightPx = null } if (passesLeft > 0) { passesLeft-- return Pass.FORCE } // Never restart the force-measure budget merely because the same round requested layout // again. After the budget is spent, honor actual layout requests without forcing clean views. if (layoutRequested) return Pass.NORMAL roundActive = false // RN can remeasure the wrapper with the current Yoga height between our frames, // constraining the native child and clearing its layout-request flag. A stable content // height therefore cannot suppress the normal pass that restores unbounded measurement. // Only forced subtree measurement stops after convergence; normal passes can use caches. return Pass.NORMAL } fun didMeasure(heightPx: Int) { if (previousHeightPx == heightPx) passesLeft = 0 previousHeightPx = heightPx } fun heightToReport(heightPx: Int, density: Float): Float? { val heightDp = when { contentEmpty -> 0f heightPx <= 0 -> return null else -> ceil(heightPx / density) } if (heightDp == lastReportedHeightDp) return null lastReportedHeightDp = heightDp return heightDp } private companion object { const val MAX_SETTLE_PASSES = 8 } }