Add meal plan editor + smaller changes

This commit is contained in:
2026-06-04 17:24:33 +02:00
parent 121f79109a
commit d1916d3fe6
49 changed files with 2778 additions and 606 deletions

View File

@@ -0,0 +1,92 @@
@file:OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
package dev.ulfrx.recipe.ui.keyboard
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.ime
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.unit.dp
import kotlinx.cinterop.DoubleVar
import kotlinx.cinterop.allocArray
import kotlinx.cinterop.get
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.sizeOf
import kotlinx.cinterop.useContents
import platform.CoreGraphics.CGRect
import platform.Foundation.NSNotificationCenter
import platform.Foundation.NSNumber
import platform.Foundation.NSOperationQueue
import platform.Foundation.NSValue
import platform.UIKit.UIKeyboardAnimationDurationUserInfoKey
import platform.UIKit.UIKeyboardFrameEndUserInfoKey
import platform.UIKit.UIKeyboardWillChangeFrameNotification
import platform.UIKit.UIScreen
import kotlin.math.roundToInt
@Composable
internal actual fun rememberKeyboardTransitionState(): KeyboardTransitionState {
val currentInset = WindowInsets.ime.asPaddingValues().calculateBottomPadding()
var targetInset by remember { mutableStateOf(0.dp) }
var animationDurationMillis by remember { mutableStateOf(IosDefaultKeyboardAnimationDurationMillis) }
DisposableEffect(Unit) {
val observer =
NSNotificationCenter.defaultCenter.addObserverForName(
name = UIKeyboardWillChangeFrameNotification,
`object` = null,
queue = NSOperationQueue.mainQueue,
usingBlock = { notification ->
val userInfo = notification?.userInfo ?: return@addObserverForName
val frameValue = userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue
?: return@addObserverForName
val durationValue = userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber
val screenHeight =
UIScreen.mainScreen.bounds.useContents {
size.height
}
val keyboardTop =
memScoped {
// iOS app targets are arm64; CGRect is x, y, width, height
// as CGFloat/Double fields.
val keyboardFrame = allocArray<DoubleVar>(CGRectDoubleFieldCount)
frameValue.getValue(
value = keyboardFrame,
size = sizeOf<CGRect>().toULong(),
)
keyboardFrame[CGRectOriginYFieldIndex]
}
val targetHeight = (screenHeight - keyboardTop).coerceAtLeast(0.0)
targetInset = targetHeight.toFloat().dp
animationDurationMillis =
durationValue?.doubleValue
?.times(MillisPerSecond)
?.roundToInt()
?.takeIf { it > 0 }
?: IosDefaultKeyboardAnimationDurationMillis
},
)
onDispose {
NSNotificationCenter.defaultCenter.removeObserver(observer)
}
}
return KeyboardTransitionState(
currentInset = currentInset,
targetInset = targetInset,
animationDurationMillis = animationDurationMillis,
)
}
private const val IosDefaultKeyboardAnimationDurationMillis = 250
private const val MillisPerSecond = 1_000.0
private const val CGRectDoubleFieldCount = 4
private const val CGRectOriginYFieldIndex = 1