Posturas del foldable

# Un foldable puede estar en varias posturas al mismo tiempo:

# FLAT (extendido completamente)
# → La pantalla está completamente abierta y plana
# → Comportarse como tablet — usar layout expandido

# HALF_OPENED (doblado a ~90°)
# → El dispositivo está parcialmente doblado
# → Dos sub-modos según la orientación del pliegue:
#   → LANDSCAPE + HALF_OPENED = modo mesa (tabletop mode)
#   → PORTRAIT + HALF_OPENED = modo libro (book mode)

# Dispositivos con pantalla exterior + interior (ej: Galaxy Z Fold):
# → Pantalla exterior cerrada: layout de teléfono compact
# → Pantalla interior extendida: layout de tablet expanded
# → La app recibe un onChange de WindowSizeClass al abrir/cerrar

WindowInfoTracker — observar el estado del foldable

// implementation("androidx.window:window:1.3.0")

// Observar los cambios de layout del dispositivo:
@Composable
fun FoldableAwareLayout() {
    val activity = LocalContext.current as ComponentActivity

    // Observar el estado de las pantallas divididas y bisagra
    val layoutInfo by activity.windowInfoTracker
        .windowLayoutInfo(activity)
        .collectAsStateWithLifecycle(initialValue = null)

    // DisplayFeatures son los elementos físicos que interrumpen la pantalla
    // (bisagras, cámaras bajo pantalla, etc.)
    val foldingFeature = layoutInfo?.displayFeatures
        ?.filterIsInstance<FoldingFeature>()
        ?.firstOrNull()

    when {
        foldingFeature == null -> {
            // Tablet plana o teléfono normal — layout estándar
            LayoutEstandar()
        }
        foldingFeature.state == FoldingFeature.State.HALF_OPENED
            && foldingFeature.orientation == FoldingFeature.Orientation.HORIZONTAL -> {
            // Modo mesa — bisagra horizontal, dispositivo en landscape
            LayoutModoMesa(foldingFeature)
        }
        foldingFeature.state == FoldingFeature.State.HALF_OPENED
            && foldingFeature.orientation == FoldingFeature.Orientation.VERTICAL -> {
            // Modo libro — bisagra vertical, dispositivo en portrait
            LayoutModoLibro(foldingFeature)
        }
        foldingFeature.state == FoldingFeature.State.FLAT -> {
            // Extendido completamente — layout de tablet
            LayoutTablet()
        }
    }
}

La bisagra (hinge) — zona a evitar

La bisagra interrumpe físicamente la pantalla. Poner contenido importante sobre ella hace que quede oculto o distorsionado. La API da la posición exacta para evitarla:

@Composable
fun LayoutConBisagra(foldingFeature: FoldingFeature) {
    // foldingFeature.bounds: el rectángulo exacto que ocupa la bisagra en pixels
    val hingeOffset = with(LocalDensity.current) {
        foldingFeature.bounds.top.toDp()
    }

    // Dividir el layout exactamente en la bisagra:
    Column {
        // Panel superior — hasta la bisagra
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .height(hingeOffset)
        ) {
            ContenidoPanelSuperior()
        }

        // La bisagra — espacio vacío, no poner nada aquí
        Spacer(modifier = Modifier.height(
            with(LocalDensity.current) {
                (foldingFeature.bounds.bottom - foldingFeature.bounds.top).toDp()
            }
        ))

        // Panel inferior
        Box(modifier = Modifier.fillMaxSize()) {
            ContenidoPanelInferior()
        }
    }
}

// Para bisagra vertical (modo libro) — dividir horizontalmente:
// El mismo concepto pero con Row en lugar de Column

Modo mesa — el caso de uso más rico

En modo mesa el dispositivo está apoyado como una laptop. La parte superior de la pantalla es el "monitor" y la inferior es el "teclado". Es el modo con más potencial para apps de productividad:

@Composable
fun LayoutModoMesa(foldingFeature: FoldingFeature) {
    // La bisagra es horizontal — dividir la pantalla en top/bottom
    val hingeTopDp = with(LocalDensity.current) { foldingFeature.bounds.top.toDp() }

    Column(modifier = Modifier.fillMaxSize()) {
        // Panel superior: contenido principal (video, mapa, documento)
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .height(hingeTopDp)
        ) {
            // Ej: reproductor de video, preview de cámara, mapa
            ContenidoPrincipal()
        }

        // Panel inferior: controles, teclado virtual, lista de opciones
        Box(modifier = Modifier.fillMaxSize()) {
            // Ej: controles de reproducción, lista de pistas, opciones
            ControlesModoMesa()
        }
    }
}

// Casos de uso reales para modo mesa:
// → App de video: video arriba, controles abajo
// → App de recetas: ingredientes arriba, pasos arriba, timer abajo
// → Video llamada: cámara arriba, chat / botones abajo
// → Juego: campo de juego arriba, controles abajo

Modo libro — dos páginas en paralelo

@Composable
fun LayoutModoLibro(foldingFeature: FoldingFeature) {
    // La bisagra es vertical — dividir la pantalla en left/right
    val hingeLeftDp = with(LocalDensity.current) { foldingFeature.bounds.left.toDp() }

    Row(modifier = Modifier.fillMaxSize()) {
        // Panel izquierdo
        Box(modifier = Modifier.width(hingeLeftDp).fillMaxHeight()) {
            PanelIzquierdo()
        }
        // Panel derecho (ocupa el resto)
        Box(modifier = Modifier.fillMaxSize()) {
            PanelDerecho()
        }
    }
}
// Casos de uso para modo libro:
// → Lector: tabla de contenidos izquierda, contenido derecha
// → Email: lista de emails izquierda, detalle derecha (list-detail)
// → Mapa + lista de lugares

Simplificar con Compose Adaptive

// La librería adaptive de Material3 provee FoldableLayout
// que maneja automáticamente el posicionamiento respecto a la bisagra:
// implementation("androidx.compose.material3.adaptive:adaptive:1.0.0")

@Composable
fun LayoutAdaptativoConBisagra() {
    val windowInfo = currentWindowAdaptiveInfo()

    // ThreePaneScaffold maneja automáticamente los tres paneles
    // y la bisagra en foldables
    ListDetailPaneScaffold(
        directive = calculatePaneScaffoldDirective(windowInfo),
        value = rememberListDetailPaneScaffoldState().scaffoldValue,
        listPane = { ListaItems() },
        detailPane = { DetalleItem() }
    )
    // En teléfono: muestra solo lista o solo detalle (con back navigation)
    // En tablet/foldable extendido: muestra ambos en paralelo
    // En modo libro: separa exactamente en la bisagra
}