From 6d311c62233f27a732a2a6f0154758a1b21fc4cb Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Mon, 23 Mar 2026 18:09:05 +0900 Subject: [PATCH 001/109] =?UTF-8?q?=F0=9F=90=9B=20fix(hig):=20CupertinoLiq?= =?UTF-8?q?uidButton=20=EC=95=84=EC=9D=B4=EC=BD=98=20=ED=81=AC=EA=B8=B0=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CupertinoLiquidButton에서 잘못 참조하던 CupertinoButtonTokens을 CupertinoLiquidButtonTokens로 수정 - CupertinoLiquidButtonTokens의 IconButtonSize를 42.dp에서 48.dp로 변경 - 라이브러리 버전을 1.0.1에서 1.0.2로 업데이트 --- gradle/libs.versions.toml | 2 +- .../commonMain/kotlin/zone/ien/hig/CupertinoLiquidButton.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index aee02f29..1d4c6cb8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "9.0.0" kotlin = "2.3.20" -lib-version-name = "1.0.1" +lib-version-name = "1.0.2" # plugin compose-plugin = "1.10.3" diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoLiquidButton.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoLiquidButton.kt index 1eabeb31..4bda7deb 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoLiquidButton.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoLiquidButton.kt @@ -243,7 +243,7 @@ fun CupertinoLiquidIconButton( ) { CupertinoLiquidButton( onClick = onClick, - modifier = modifier.size(CupertinoButtonTokens.IconButtonSize), + modifier = modifier.size(CupertinoLiquidButtonTokens.IconButtonSize), enabled = enabled, colors = colors, size = CupertinoButtonSize.Regular, @@ -445,7 +445,7 @@ object CupertinoLiquidButtonDefaults { internal object CupertinoLiquidButtonTokens { const val PressedPlainButonAlpha = .33f - val IconButtonSize = 42.dp + val IconButtonSize = 48.dp const val BorderedButtonAlpha = .2f } From be9256c27504b725f46202d83b32c8f3b0432fca Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Mon, 23 Mar 2026 18:10:19 +0900 Subject: [PATCH 002/109] =?UTF-8?q?=F0=9F=94=96=20chore(version):=20?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=EC=9D=84=201.0.2-alpha01=EB=A1=9C=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 라이브러리 배포 및 테스트를 위해 버전을 1.0.2에서 1.0.2-alpha01로 업데이트합니다. --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1d4c6cb8..77263c1e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "9.0.0" kotlin = "2.3.20" -lib-version-name = "1.0.2" +lib-version-name = "1.0.2-alpha01" # plugin compose-plugin = "1.10.3" From 28b381ef0f829242c358c5c963d13a9f655b94c7 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 25 Mar 2026 10:48:22 +0900 Subject: [PATCH 003/109] =?UTF-8?q?=E2=9C=A8=20feat(hig):=20=EC=95=A0?= =?UTF-8?q?=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=20=ED=9A=A8=EA=B3=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=B0=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `TestScreen`의 `navigationIcon` 및 하단 버튼 레이아웃에 `AnimatedVisibility`를 적용하여 슬라이드 애니메이션 추가 - `CupertinoDropdownMenu` 내 불필요한 주석 코드 제거 및 코드 정리 - `App.kt`에서 테스트를 위해 `TestScreen` 호출 활성화 및 내비게이션 주석 처리 --- .../composeApp/src/commonMain/kotlin/App.kt | 10 +- .../src/commonMain/kotlin/test/TestScreen.kt | 95 ++++++++++++++----- .../zone/ien/hig/CupertinoDropdownMenu.kt | 7 -- 3 files changed, 78 insertions(+), 34 deletions(-) diff --git a/example/composeApp/src/commonMain/kotlin/App.kt b/example/composeApp/src/commonMain/kotlin/App.kt index fd071327..858a6a5b 100644 --- a/example/composeApp/src/commonMain/kotlin/App.kt +++ b/example/composeApp/src/commonMain/kotlin/App.kt @@ -59,11 +59,11 @@ fun App() { lightAccent else darkAccent, useDarkTheme = isDark ) { -// TestScreen() - RootNavigationGraph( - backStack = backStack, - viewModel = viewModel - ) + TestScreen() +// RootNavigationGraph( +// backStack = backStack, +// viewModel = viewModel +// ) } } } diff --git a/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt b/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt index 5c50d6fb..43224d83 100644 --- a/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt +++ b/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt @@ -1,5 +1,13 @@ package test +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideOut +import androidx.compose.animation.slideOutHorizontally import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -7,10 +15,12 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.SnackbarHost @@ -81,30 +91,40 @@ fun TestScreen( CupertinoText("sub title") }, navigationIcon = { - Box { - CupertinoLiquidIconButton( - onClick = { expanded = true }, - backdrop = backdrop - ) { - CupertinoIcon( - imageVector = CupertinoIcons.Default.ChevronBackward, - contentDescription = null, - modifier = Modifier.size(24.dp) - ) - } - CupertinoDropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false }, - backdrop = rememberLayerBackdrop() + Row { + + AnimatedVisibility( + visible = enabled, + enter = slideInHorizontally(tween(3000)) { -it }, + exit = slideOutHorizontally(tween(3000)) { -it } ) { - MenuSection( - title = { Text(text = "Title") } - ) { - repeat(4) { - MenuAction( - onClick = {} + Box { + CupertinoLiquidIconButton( + onClick = { expanded = true }, + backdrop = backdrop, + modifier = Modifier.padding(start = 16.dp) + ) { + CupertinoIcon( + imageVector = CupertinoIcons.Default.ChevronBackward, + contentDescription = null, + modifier = Modifier.size(24.dp) + ) + } + CupertinoDropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + backdrop = rememberLayerBackdrop() + ) { + MenuSection( + title = { Text(text = "Title") } ) { - Text(text = "Action") + repeat(4) { + MenuAction( + onClick = {} + ) { + Text(text = "Action") + } + } } } } @@ -294,6 +314,37 @@ fun TestScreen( .fillMaxWidth() .height(200.dp) ) + Row( + horizontalArrangement = Arrangement.spacedBy(24.dp), + modifier = Modifier.padding(horizontal = 24.dp) + ) { + AnimatedVisibility( + visible = enabled, + enter = slideInHorizontally(tween(3000)), + exit = slideOutHorizontally(tween(3000)) + ) { + CupertinoLiquidIconButton( + onClick = { }, + backdrop = rememberDefaultBackdrop() + ) { + CupertinoIcon( + imageVector = CupertinoIcons.Default.ChevronBackward, + contentDescription = null, + modifier = Modifier.size(24.dp) + ) + } + } + CupertinoLiquidIconButton( + onClick = { }, + backdrop = rememberDefaultBackdrop() + ) { + CupertinoIcon( + imageVector = CupertinoIcons.Default.ChevronBackward, + contentDescription = null, + modifier = Modifier.size(24.dp) + ) + } + } Box( modifier = Modifier .background(Color.White) diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt index c53ea7ba..80dd62ca 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt @@ -144,12 +144,9 @@ fun CupertinoDropdownMenu( val expandedStates = remember { MutableTransitionState(false) } expandedStates.targetState = expanded val safePadding = 32.dp -// val density = LocalDensity.current -// val safePaddingPx = with(density) { safePadding.roundToPx() } if (expandedStates.currentState || expandedStates.targetState) { var transformOrigin by remember { mutableStateOf(TransformOrigin.Center) } -// var menuOffset by remember { mutableStateOf(Offset.Zero) } val density = LocalDensity.current val popupPositionProvider = DropdownMenuPositionProvider( contentOffset = offset, @@ -158,10 +155,6 @@ fun CupertinoDropdownMenu( density = density ) { parentBounds, menuBounds -> transformOrigin = calculateTransformOrigin(parentBounds, menuBounds) -// menuOffset = Offset( -// menuBounds.left.toFloat() + safePaddingPx, -// menuBounds.top.toFloat() + safePaddingPx -// ) } Popup( From 28681f9a34d239de5cc0b3b6a90232850c77505d Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 25 Mar 2026 13:22:42 +0900 Subject: [PATCH 004/109] =?UTF-8?q?=E2=9C=A8=20feat(hig):=20CupertinoDropd?= =?UTF-8?q?ownMenu=20=ED=94=8C=EB=9E=AB=ED=8F=BC=EB=B3=84=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20expect/actual=20?= =?UTF-8?q?=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `CupertinoDropdownMenu`를 `expect` 함수로 전환하고 `commonMain`에서 상세 구현 제거 - iOS가 아닌 플랫폼(Android, Desktop, JS, Wasm)을 위한 `nonIosMain` 소스셋 및 `nonDarwinMain` 빌드 컨벤션 추가 - `CupertinoDropdownMenu`의 기존 로직을 `nonIosMain`으로 이동하여 멀티플랫폼 대응 준비 - `iosMain`에 `actual` 함수 스켈레톤 추가 및 관련 인터페이스 정의 업데이트 - `TestScreen` 및 `LazySectionScope` 내 미사용 임포트 정리 및 최적화 --- ...multiplatform-module-convention.gradle.kts | 7 + .../src/commonMain/kotlin/test/TestScreen.kt | 7 - .../zone/ien/hig/CupertinoDropdownMenu.kt | 671 +--------------- .../zone/ien/hig/section/LazySectionScope.kt | 1 - .../zone/ien/hig/CupertinoDropdownMenu.ios.kt | 35 + .../ien/hig/CupertinoDropdownMenu.nonIos.kt | 729 ++++++++++++++++++ 6 files changed, 781 insertions(+), 669 deletions(-) create mode 100644 hig/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.ios.kt create mode 100644 hig/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.nonIos.kt diff --git a/buildSrc/src/main/kotlin/multiplatform-module-convention.gradle.kts b/buildSrc/src/main/kotlin/multiplatform-module-convention.gradle.kts index 554d9863..eb8f3f14 100644 --- a/buildSrc/src/main/kotlin/multiplatform-module-convention.gradle.kts +++ b/buildSrc/src/main/kotlin/multiplatform-module-convention.gradle.kts @@ -110,6 +110,13 @@ kotlin { jsMain.get().dependsOn(this) wasmJsMain.dependsOn(this) } + val nonDarwinMain by creating { + dependsOn(commonMain.get()) + androidMain.get().dependsOn(this) + desktopMain.dependsOn(this) + jsMain.get().dependsOn(this) + wasmJsMain.dependsOn(this) + } val darwinMain by creating { dependsOn(commonMain.get()) diff --git a/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt b/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt index 43224d83..0c8b0e4c 100644 --- a/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt +++ b/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt @@ -1,12 +1,8 @@ package test import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut import androidx.compose.animation.slideInHorizontally -import androidx.compose.animation.slideOut import androidx.compose.animation.slideOutHorizontally import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -15,12 +11,10 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.SnackbarHost @@ -37,7 +31,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import com.kyant.backdrop.backdrops.layerBackdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt index 80dd62ca..3d11b142 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt @@ -20,102 +20,28 @@ package zone.ien.hig -import androidx.compose.animation.core.LinearOutSlowInEasing -import androidx.compose.animation.core.MutableTransitionState -import androidx.compose.animation.core.Spring -import androidx.compose.animation.core.animateFloat -import androidx.compose.animation.core.rememberTransition -import androidx.compose.animation.core.spring -import androidx.compose.animation.core.tween import androidx.compose.foundation.ScrollState -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.calculateStartPadding -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.CornerBasedShape -import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.Immutable import androidx.compose.runtime.ReadOnlyComposable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.graphics.TransformOrigin -import androidx.compose.ui.graphics.drawscope.translate -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.graphics.takeOrElse -import androidx.compose.ui.layout.SubcomposeLayout -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.role -import androidx.compose.ui.semantics.selected -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpOffset -import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.IntRect -import androidx.compose.ui.unit.IntSize -import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp -import androidx.compose.ui.util.fastCoerceAtMost -import androidx.compose.ui.util.fastForEach -import androidx.compose.ui.util.fastForEachIndexed -import androidx.compose.ui.util.fastMap -import androidx.compose.ui.util.fastSumBy -import androidx.compose.ui.util.lerp -import androidx.compose.ui.window.Popup -import androidx.compose.ui.window.PopupPositionProvider import androidx.compose.ui.window.PopupProperties import com.kyant.backdrop.Backdrop -import com.kyant.backdrop.drawBackdrop -import com.kyant.backdrop.effects.blur -import com.kyant.backdrop.effects.lens -import com.kyant.backdrop.effects.vibrancy -import com.kyant.shapes.RoundedRectangle -import com.kyant.shapes.RoundedRectangularShape import zone.ien.hig.icons.CupertinoIcons import zone.ien.hig.icons.outlined.Checkmark import zone.ien.hig.section.CupertinoSectionDefaults -import zone.ien.hig.section.CupertinoSectionTokens import zone.ien.hig.section.SectionStyle -import zone.ien.hig.theme.BrightSeparatorColor import zone.ien.hig.theme.CupertinoColors import zone.ien.hig.theme.CupertinoTheme import zone.ien.hig.theme.systemGray5 -import zone.ien.hig.theme.systemRed -import zone.ien.hig.utils.InteractiveHighlight -import kotlin.math.abs -import kotlin.math.atan2 -import kotlin.math.cos -import kotlin.math.max -import kotlin.math.min -import kotlin.math.sin -import kotlin.math.tanh - -sealed interface CupertinoMenuScope /** * Cupertino elevated dropdown menu. Usually used for top bar actions. @@ -128,7 +54,7 @@ sealed interface CupertinoMenuScope * */ @Composable @ExperimentalCupertinoApi -fun CupertinoDropdownMenu( +expect fun CupertinoDropdownMenu( expanded: Boolean, onDismissRequest: () -> Unit, modifier: Modifier = Modifier, @@ -140,314 +66,20 @@ fun CupertinoDropdownMenu( properties: PopupProperties = PopupProperties(focusable = true, clippingEnabled = false), backdrop: Backdrop, content: @Composable CupertinoMenuScope.() -> Unit, -) { - val expandedStates = remember { MutableTransitionState(false) } - expandedStates.targetState = expanded - val safePadding = 32.dp - - if (expandedStates.currentState || expandedStates.targetState) { - var transformOrigin by remember { mutableStateOf(TransformOrigin.Center) } - val density = LocalDensity.current - val popupPositionProvider = DropdownMenuPositionProvider( - contentOffset = offset, - safePadding = safePadding, - verticalMargin = 0.dp, - density = density - ) { parentBounds, menuBounds -> - transformOrigin = calculateTransformOrigin(parentBounds, menuBounds) - } - - Popup( - onDismissRequest = onDismissRequest, - popupPositionProvider = popupPositionProvider, - properties = properties, - ) { - val scope = remember { CupertinoMenuScopeImpl() } - - DropdownMenuContent( - containerColor = containerColor, - expandedStates = expandedStates, - transformOriginState = transformOrigin, - scrollState = scrollState, - modifier = modifier.padding(safePadding), - content = { scope.run { content() } }, - width = width, - paddingValue = paddingValues, - backdrop = backdrop, - ) - } - } -} - -/** - * Plain menu item with manual padding control. - * Usually shouldn't be used directly. - * - * @param minHeight minimum item height - * @param content item content - * - * @see MenuSection - * @see MenuTitle - * @see MenuAction - * @see MenuPickerAction - * @see MenuDivider - * */ -@Composable -fun CupertinoMenuScope.MenuItem( - modifier: Modifier = Modifier, - minHeight: Dp = MinItemHeight, - content: @Composable (padding: PaddingValues) -> Unit, -) { - this as CupertinoMenuScopeImpl - - Box( - modifier = modifier.heightIn(minHeight), - contentAlignment = Alignment.CenterStart, - ) { - content( - MenuPaddingValues.let { - if (!hasPicker) { - it - } else { - it.copy( - start = it.calculateStartPadding(LocalLayoutDirection.current) + SelectorSize, - ) - } - }, - ) - } -} - -/** - * Group of buttons with top [MenuTitle] and bottom [MenuDivider] - * - * @see MenuTitle - * @see MenuDivider - * */ -@Composable -inline fun CupertinoMenuScope.MenuSection( - noinline title: (@Composable () -> Unit)? = null, - content: @Composable CupertinoMenuScope.() -> Unit, -) { - if (title != null) { - MenuTitle(title = title) - } - content() -} - -/** - * Title of the [MenuSection] - * */ -@Composable -fun CupertinoMenuScope.MenuTitle( - modifier: Modifier = Modifier, - title: @Composable () -> Unit, -) { - MenuItem( - modifier = modifier, - minHeight = MinTitleHeight, - ) { - CompositionLocalProvider( - LocalContentColor provides CupertinoTheme.colorScheme.secondaryLabel, - ) { - ProvideTextStyle( - CupertinoTheme.typography.footnote, - ) { - Box( - Modifier - .padding(it), - ) { - title() - } - } - } - } -} - -/** - * Default menu button - - * @param onClick block performed on action click - * @param modifier item modifier - * @param onClickLabel semantics label for the [onClick] action. Should be the same text as in [title] - * @param contentColor color of the item contend. - * Usually [CupertinoColors.systemRed] is used for destructive actions. - * @param icon action trailing icon - * @param caption content before [icon] - * @param title action title - * */ -@Composable -fun CupertinoMenuScope.MenuAction( - onClick: () -> Unit, - modifier: Modifier = Modifier, - onClickLabel: String? = null, - enabled: Boolean = true, - contentColor: Color = CupertinoDropdownMenuDefaults.ContentColor, - icon: (@Composable () -> Unit) = {}, - caption: @Composable () -> Unit = {}, - title: @Composable () -> Unit, -) = ActionWithoutPadding( - onClickLabel = onClickLabel, - modifier = modifier, - onClick = onClick, - enabled = enabled, - contentColor = contentColor, - icon = icon, - caption = caption, ) -{ - Box( - modifier = Modifier.padding(it), - ) { - title() - } -} - -/** - * Picker action with leading icon ([Checkmark] by default) if selected. - * - * If menu has at least one picker action (no matter selected or not) - * then all menu items will have additional start padding - * - * @param isSelected selection flag. If item is selected, it will have a [selectionIcon] - * @param onClick block performed on action click - * @param modifier item modifier - * @param onClickLabel semantics label for the [onClick] action. Should be the same text as in [title] - * @param contentColor color of the item contend. - * Usually [CupertinoColors.systemRed] is used for destructive actions. - * @param icon action trailing icon - * @param caption content before [icon] - * @param title action title - * */ -@Composable -fun CupertinoMenuScope.MenuPickerAction( - isSelected: Boolean, - onClick: () -> Unit, - modifier: Modifier = Modifier, - onClickLabel: String? = null, - enabled: Boolean = true, - contentColor: Color = CupertinoDropdownMenuDefaults.ContentColor, - selectionIcon: (@Composable () -> Unit) = { CupertinoDropdownMenuDefaults.PickerLeadingIcon() }, - icon: (@Composable () -> Unit) = {}, - caption: @Composable () -> Unit = {}, - title: @Composable () -> Unit, -) { - this as CupertinoMenuScopeImpl - DisposableEffect(this) { - val prev = hasPicker - hasPicker = true - onDispose { - hasPicker = prev - } - } +sealed interface CupertinoMenu { - ActionWithoutPadding( - modifier = modifier.semantics(mergeDescendants = true) { - selected = isSelected - role = Role.DropdownList - }, - onClickLabel = onClickLabel, - onClick = onClick, - enabled = enabled, - contentColor = contentColor, - icon = icon, - caption = caption, - title = { pv -> - Box( - contentAlignment = Alignment.CenterStart, - ) { - Box( - modifier = Modifier.size(MinItemHeight), - contentAlignment = Alignment.Center, - ) { - if (isSelected) { - selectionIcon() - } - } - Box( - modifier = Modifier.padding(pv), - ) { - title() - } - } - }, - ) + data class MenuItem(): CupertinoMenu + data class MenuSection(): CupertinoMenu + data class MenuTitle(): CupertinoMenu + data class MenuAction(): CupertinoMenu + data class MenuPickerAction(): CupertinoMenu + data class MenuDevider(): CupertinoMenu } /** - * Separator for the menu actions groups - * */ -@Composable -fun CupertinoMenuScope.MenuDivider( - modifier: Modifier = Modifier, - color: Color? = null, - height: Dp = DividerHeight, -) = MenuItem( - minHeight = DividerHeight, -) { - Spacer( - modifier = modifier - .height(height) - .fillMaxWidth() - .padding(horizontal = MenuHorizontalMargin) - .background(color ?: CupertinoDropdownMenuDefaults.DividerColor), - ) -} - -@OptIn(ExperimentalCupertinoApi::class) -@Composable -private fun CupertinoMenuScope.ActionWithoutPadding( - onClick: () -> Unit, - modifier: Modifier = Modifier, - onClickLabel: String? = null, - enabled: Boolean = true, - contentColor: Color = Color.Unspecified, - icon: @Composable () -> Unit = {}, - caption: @Composable () -> Unit = {}, - title: @Composable (PaddingValues) -> Unit, -) = MenuItem { - val color = contentColor.takeOrElse { LocalContentColor.current }.let { if (enabled) it else it.copy(alpha = it.alpha / 4f) } - - ProvideTextStyle(CupertinoTheme.typography.callout) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(SplitPadding), - modifier = modifier - .heightIn(min = CupertinoSectionTokens.MinHeight) - .fillMaxWidth() - .padding(8.dp) - .clip(RoundedRectangle(24.dp)) - .clickable( - enabled = enabled, - onClick = onClick, - onClickLabel = onClickLabel, - role = Role.DropdownList, - ) - ) { - CompositionLocalProvider(LocalContentColor provides color) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(CupertinoSectionTokens.SplitPadding), - modifier = Modifier.padding(it.copy(end = 0.dp)) - ) { - caption.invoke() - - Box( - contentAlignment = Alignment.Center, - modifier = Modifier.size(MinItemHeight / 3), - ) { - icon.invoke() - } - } - title(it.copy(start = 0.dp)) - } - } - } -} - -/** - * Contains default values used for [CupertinoDropdownMenu]. + * Contains default values used for [hig.CupertinoDropdownMenu]. */ @Immutable object CupertinoDropdownMenuDefaults { @@ -486,287 +118,4 @@ object CupertinoDropdownMenuDefaults { contentDescription = null, ) } -} - -@Composable -private fun DropdownMenuContent( - width: Dp, - containerColor: Color, - expandedStates: MutableTransitionState, - transformOriginState: TransformOrigin, - scrollState: ScrollState, - paddingValue: PaddingValues, - modifier: Modifier = Modifier, - backdrop: Backdrop, - content: @Composable () -> Unit, -) { - // Menu open/close animation. - val transition = rememberTransition(expandedStates, "DropDownMenu") - val animationScope = rememberCoroutineScope() - - val scale by transition.animateFloat( - transitionSpec = { - if (false isTransitioningTo true) { - // Dismissed to expanded - MenuEnterTransition - } else { - // Expanded to dismissed. - MenuExitTransition - } - }, - ) { - if (it) { - // Menu is expanded. - 1f - } else { - // Menu is dismissed. - .1f - } - } - val alpha by transition.animateFloat( - transitionSpec = { - if (false isTransitioningTo true) { - // Dismissed to expanded - MenuEnterTransition - } else { - MenuExitTransition - } - }, - ) { - if (it) { - // Menu is expanded. - 1f - } else { - // Menu is dismissed. - 0f - } - } - val shape = CupertinoDropdownMenuDefaults.Shape - val interactiveHighlight = remember(animationScope) { InteractiveHighlight(animationScope = animationScope) } - - CupertinoSurface( - color = Color.Transparent, - modifier = Modifier - .padding(paddingValue) - .graphicsLayer { - scaleX = scale - scaleY = scale - this.alpha = alpha - transformOrigin = transformOriginState - clip = false - } - .width(width) - ) { - CompositionLocalProvider( - LocalSeparatorColor provides BrightSeparatorColor, - ) { - ProvideTextStyle( - CupertinoTheme.typography.body - ) { - SubcomposeLayout( - modifier = modifier - .drawBackdrop( - backdrop = backdrop, - shape = { shape }, - effects = { - vibrancy() - blur(2.dp.toPx()) - if (shape is RoundedRectangularShape || shape is CornerBasedShape) { - lens(12.dp.toPx(), 24.dp.toPx()) - } - }, - layerBlock = { - val width = this.size.width - val height = this.size.height - - val progress = interactiveHighlight.pressProgress - val scale = lerp(1f, 1f + 4.dp.toPx() / height, progress) - - val maxOffset = this.size.minDimension - val initialDerivative = 0.05f - val offset = interactiveHighlight.offset - - translationX = maxOffset * tanh(initialDerivative * offset.x / maxOffset) - translationY = maxOffset * tanh(initialDerivative * offset.y / maxOffset) - - val maxDragScale = 4.dp.toPx() / height - val offsetAngle = atan2(offset.y, offset.x) - - scaleX = scale + maxDragScale * abs(cos(offsetAngle) * offset.x / this.size.maxDimension) * (width / height).fastCoerceAtMost(1f) - scaleY = scale + maxDragScale * abs(sin(offsetAngle) * offset.y / this.size.maxDimension) * (height / width).fastCoerceAtMost(1f) - }, - onDrawSurface = { - drawRect(containerColor.copy(alpha = 0.95f)) - }, - ) - .fillMaxWidth() - .heightIn(max = MenuMaxHeight) - .verticalScroll(scrollState), - ) { constraints -> - val layoutWidth = constraints.maxWidth - val itemPlaceables = subcompose(CupertinoDropdownMenuSlots.Item, content).fastMap { it.measure(constraints) } - val allPlacements = buildList(itemPlaceables.size * 2) { itemPlaceables.fastForEach { placeable -> add(placeable) } } - val height = allPlacements.fastSumBy { it.height } - - layout(layoutWidth, height) { - var y = 0 - allPlacements.fastForEach { - it.placeRelative(0, y) - y += it.height - } - } - } - } - } - } -} - -private enum class CupertinoDropdownMenuSlots { - Section, - Item, - Separator, -} - -internal fun calculateTransformOrigin( - parentBounds: IntRect, - menuBounds: IntRect, -): TransformOrigin { - val pivotX = - when { - menuBounds.left >= parentBounds.right -> 0f - menuBounds.right <= parentBounds.left -> 1f - menuBounds.width == 0 -> 0f - else -> { - val intersectionCenter = - ( - max(parentBounds.left, menuBounds.left) + - min( - parentBounds.right, - menuBounds.right, - ) - ) / 2 - (intersectionCenter - menuBounds.left).toFloat() / menuBounds.width - } - } - val pivotY = - when { - menuBounds.top >= parentBounds.bottom -> 0f - menuBounds.bottom <= parentBounds.top -> 1f - menuBounds.height == 0 -> 0f - else -> { - val intersectionCenter = - ( - max(parentBounds.top, menuBounds.top) + - min(parentBounds.bottom, menuBounds.bottom) - ) / 2 - (intersectionCenter - menuBounds.top).toFloat() / menuBounds.height - } - } - return TransformOrigin(pivotX, pivotY) -} - -@Immutable -internal data class DropdownMenuPositionProvider( - val contentOffset: DpOffset, - val density: Density, - val safePadding: Dp = 0.dp, - val verticalMargin: Dp = MenuVerticalMargin, - val onPositionCalculated: (IntRect, IntRect) -> Unit = { _, _ -> }, -) : PopupPositionProvider { - override fun calculatePosition( - anchorBounds: IntRect, - windowSize: IntSize, - layoutDirection: LayoutDirection, - popupContentSize: IntSize, - ): IntOffset { - val verticalMargin = with(density) { verticalMargin.roundToPx() } - val safePaddingPx = with(density) { safePadding.roundToPx() } - val contentOffsetX = with(density) { - contentOffset.x.roundToPx() * (if (layoutDirection == LayoutDirection.Ltr) 1 else -1) - } - val contentOffsetY = with(density) { contentOffset.y.roundToPx() } - - // popupContentSize는 이미 safePadding * 2를 포함한 크기 - // 실제 메뉴 크기 = popupContentSize - safePadding * 2 - // Popup 자체는 safePadding만큼 앞당겨서 배치해야 내부 컨텐츠가 anchor에 붙음 - val leftToAnchorLeft = anchorBounds.left + contentOffsetX - safePaddingPx - val rightToAnchorRight = anchorBounds.right - popupContentSize.width + contentOffsetX + safePaddingPx - val rightToWindowRight = windowSize.width - popupContentSize.width - val leftToWindowLeft = 0 - - val x = if (layoutDirection == LayoutDirection.Ltr) { - sequenceOf( - leftToAnchorLeft, - rightToAnchorRight, - if (anchorBounds.left >= 0) rightToWindowRight else leftToWindowLeft, - ) - } else { - sequenceOf( - rightToAnchorRight, - leftToAnchorLeft, - if (anchorBounds.right <= windowSize.width) leftToWindowLeft else rightToWindowRight, - ) - }.firstOrNull { - it >= -safePaddingPx && it + popupContentSize.width <= windowSize.width + safePaddingPx - } ?: rightToAnchorRight - -// val topToAnchorTop = maxOf( -// anchorBounds.top + contentOffsetY - safePaddingPx, -// verticalMargin - safePaddingPx -// ) - val topToAnchorTop = anchorBounds.top + contentOffsetY - safePaddingPx - val bottomToAnchorBottom = anchorBounds.bottom - popupContentSize.height + contentOffsetY + safePaddingPx - val bottomToWindowBottom = windowSize.height - popupContentSize.height - verticalMargin + safePaddingPx - -// val y = sequenceOf( -// topToAnchorTop, -// bottomToAnchorBottom, -// bottomToWindowBottom, -// ).firstOrNull { -// it + safePaddingPx >= verticalMargin && -// it + popupContentSize.height - safePaddingPx <= windowSize.height - verticalMargin -// } ?: bottomToAnchorBottom - - val y = sequenceOf( - topToAnchorTop, - bottomToAnchorBottom, - bottomToWindowBottom, - ).firstOrNull { - it + popupContentSize.height - safePaddingPx <= windowSize.height - verticalMargin - // 👆 상단 조건(verticalMargin 체크) 제거 - } ?: bottomToAnchorBottom - - onPositionCalculated( - anchorBounds, - IntRect(x, y, x + popupContentSize.width, y + popupContentSize.height), - ) - return IntOffset(x, y) - } -} - -internal class CupertinoMenuScopeImpl : CupertinoMenuScope { - var hasPicker: Boolean by mutableStateOf(false) -} - -private val MenuMaxHeight: Dp = 600.dp -private val SelectorSize = 20.dp - -private val MenuHorizontalMargin = 24.dp -private val MenuVerticalMargin = 24.dp -private val MinItemHeight = 48.dp -private val DividerHeight = 1.dp -private val MinTitleHeight = 32.dp -private val SplitPadding = 16.dp -private val MenuPaddingValues = PaddingValues(16.dp, 8.dp) - -private val MenuEnterTransition = - spring( - dampingRatio = .825f, - stiffness = Spring.StiffnessMediumLow, - ) - -private val MenuExitTransition = - tween( - durationMillis = 350, - easing = LinearOutSlowInEasing, - ) +} \ No newline at end of file diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/section/LazySectionScope.kt b/hig/src/commonMain/kotlin/zone/ien/hig/section/LazySectionScope.kt index 29822411..8804f955 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/section/LazySectionScope.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/section/LazySectionScope.kt @@ -60,7 +60,6 @@ import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.kyant.backdrop.backdrops.rememberLayerBackdrop -import zone.ien.hig.utils.rememberDefaultBackdrop import zone.ien.hig.LocalContentColor import zone.ien.hig.CupertinoButtonTokens import zone.ien.hig.CupertinoDatePicker diff --git a/hig/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.ios.kt b/hig/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.ios.kt new file mode 100644 index 00000000..ad3c8d8a --- /dev/null +++ b/hig/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.ios.kt @@ -0,0 +1,35 @@ +package zone.ien.hig + +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.uikit.LocalUIViewController +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.window.PopupProperties +import com.kyant.backdrop.Backdrop + +@Composable +@ExperimentalCupertinoApi +actual fun CupertinoDropdownMenu( + expanded: Boolean, + onDismissRequest: () -> Unit, + modifier: Modifier, + offset: DpOffset, + paddingValues: PaddingValues, + containerColor: Color, + width: Dp, + scrollState: ScrollState, + properties: PopupProperties, + backdrop: Backdrop, + content: @Composable (CupertinoMenuScope.() -> Unit) +) { + val density = LocalDensity.current + val viewController = LocalUIViewController.current + + +} \ No newline at end of file diff --git a/hig/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.nonIos.kt b/hig/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.nonIos.kt new file mode 100644 index 00000000..8f475442 --- /dev/null +++ b/hig/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.nonIos.kt @@ -0,0 +1,729 @@ +/* + * Copyright (c) 2023-2024. Compose Cupertino project and open source contributors. + * Copyright (c) 2025. Scott Lanoue. + * Copyright (c) 2026. IENGROUND of IENLAB. + * + * 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 zone.ien.hig + +import androidx.compose.animation.core.LinearOutSlowInEasing +import androidx.compose.animation.core.MutableTransitionState +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.rememberTransition +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.calculateStartPadding +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CornerBasedShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.TransformOrigin +import androidx.compose.ui.graphics.drawscope.translate +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.takeOrElse +import androidx.compose.ui.layout.SubcomposeLayout +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.selected +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntRect +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastCoerceAtMost +import androidx.compose.ui.util.fastForEach +import androidx.compose.ui.util.fastForEachIndexed +import androidx.compose.ui.util.fastMap +import androidx.compose.ui.util.fastSumBy +import androidx.compose.ui.util.lerp +import androidx.compose.ui.window.Popup +import androidx.compose.ui.window.PopupPositionProvider +import androidx.compose.ui.window.PopupProperties +import com.kyant.backdrop.Backdrop +import com.kyant.backdrop.drawBackdrop +import com.kyant.backdrop.effects.blur +import com.kyant.backdrop.effects.lens +import com.kyant.backdrop.effects.vibrancy +import com.kyant.shapes.RoundedRectangle +import com.kyant.shapes.RoundedRectangularShape +import zone.ien.hig.icons.CupertinoIcons +import zone.ien.hig.icons.outlined.Checkmark +import zone.ien.hig.section.CupertinoSectionDefaults +import zone.ien.hig.section.CupertinoSectionTokens +import zone.ien.hig.section.SectionStyle +import zone.ien.hig.theme.BrightSeparatorColor +import zone.ien.hig.theme.CupertinoColors +import zone.ien.hig.theme.CupertinoTheme +import zone.ien.hig.theme.systemGray5 +import zone.ien.hig.theme.systemRed +import zone.ien.hig.utils.InteractiveHighlight +import kotlin.math.abs +import kotlin.math.atan2 +import kotlin.math.cos +import kotlin.math.max +import kotlin.math.min +import kotlin.math.sin +import kotlin.math.tanh + +sealed interface CupertinoMenuScope + +/** + * Cupertino elevated dropdown menu. Usually used for top bar actions. + * + * @see MenuSection + * @see MenuTitle + * @see MenuAction + * @see MenuPickerAction + * @see MenuDivider + * */ +@Composable +@ExperimentalCupertinoApi +actual fun CupertinoDropdownMenu( + expanded: Boolean, + onDismissRequest: () -> Unit, + modifier: Modifier, + offset: DpOffset, + paddingValues: PaddingValues, + containerColor: Color, + width: Dp, + scrollState: ScrollState, + properties: PopupProperties, + backdrop: Backdrop, + content: @Composable CupertinoMenuScope.() -> Unit, +) { + val expandedStates = remember { MutableTransitionState(false) } + expandedStates.targetState = expanded + val safePadding = 32.dp + + if (expandedStates.currentState || expandedStates.targetState) { + var transformOrigin by remember { mutableStateOf(TransformOrigin.Center) } + val density = LocalDensity.current + val popupPositionProvider = DropdownMenuPositionProvider( + contentOffset = offset, + safePadding = safePadding, + verticalMargin = 0.dp, + density = density + ) { parentBounds, menuBounds -> + transformOrigin = calculateTransformOrigin(parentBounds, menuBounds) + } + + Popup( + onDismissRequest = onDismissRequest, + popupPositionProvider = popupPositionProvider, + properties = properties, + ) { + val scope = remember { CupertinoMenuScopeImpl() } + + DropdownMenuContent( + containerColor = containerColor, + expandedStates = expandedStates, + transformOriginState = transformOrigin, + scrollState = scrollState, + modifier = modifier.padding(safePadding), + content = { scope.run { content() } }, + width = width, + paddingValue = paddingValues, + backdrop = backdrop, + ) + } + } +} + +/** + * Plain menu item with manual padding control. + * Usually shouldn't be used directly. + * + * @param minHeight minimum item height + * @param content item content + * + * @see MenuSection + * @see MenuTitle + * @see MenuAction + * @see MenuPickerAction + * @see MenuDivider + * */ +@Composable +fun CupertinoMenuScope.MenuItem( + modifier: Modifier = Modifier, + minHeight: Dp = MinItemHeight, + content: @Composable (padding: PaddingValues) -> Unit, +) { + this as CupertinoMenuScopeImpl + + Box( + modifier = modifier.heightIn(minHeight), + contentAlignment = Alignment.CenterStart, + ) { + content( + MenuPaddingValues.let { + if (!hasPicker) { + it + } else { + it.copy( + start = it.calculateStartPadding(LocalLayoutDirection.current) + SelectorSize, + ) + } + }, + ) + } +} + +/** + * Group of buttons with top [MenuTitle] and bottom [MenuDivider] + * + * @see MenuTitle + * @see MenuDivider + * */ +@Composable +inline fun CupertinoMenuScope.MenuSection( + noinline title: (@Composable () -> Unit)? = null, + content: @Composable CupertinoMenuScope.() -> Unit, +) { + if (title != null) { + MenuTitle(title = title) + } + content() +} + +/** + * Title of the [MenuSection] + * */ +@Composable +fun CupertinoMenuScope.MenuTitle( + modifier: Modifier = Modifier, + title: @Composable () -> Unit, +) { + MenuItem( + modifier = modifier, + minHeight = MinTitleHeight, + ) { + CompositionLocalProvider( + LocalContentColor provides CupertinoTheme.colorScheme.secondaryLabel, + ) { + ProvideTextStyle( + CupertinoTheme.typography.footnote, + ) { + Box( + Modifier + .padding(it), + ) { + title() + } + } + } + } +} + +/** + * Default menu button + + * @param onClick block performed on action click + * @param modifier item modifier + * @param onClickLabel semantics label for the [onClick] action. Should be the same text as in [title] + * @param contentColor color of the item contend. + * Usually [CupertinoColors.systemRed] is used for destructive actions. + * @param icon action trailing icon + * @param caption content before [icon] + * @param title action title + * */ +@Composable +fun CupertinoMenuScope.MenuAction( + onClick: () -> Unit, + modifier: Modifier = Modifier, + onClickLabel: String? = null, + enabled: Boolean = true, + contentColor: Color = CupertinoDropdownMenuDefaults.ContentColor, + icon: (@Composable () -> Unit) = {}, + caption: @Composable () -> Unit = {}, + title: @Composable () -> Unit, +) = ActionWithoutPadding( + onClickLabel = onClickLabel, + modifier = modifier, + onClick = onClick, + enabled = enabled, + contentColor = contentColor, + icon = icon, + caption = caption, +) { + Box( + modifier = Modifier.padding(it), + ) { + title() + } +} + +/** + * Picker action with leading icon ([Checkmark] by default) if selected. + * + * If menu has at least one picker action (no matter selected or not) + * then all menu items will have additional start padding + * + * @param isSelected selection flag. If item is selected, it will have a [selectionIcon] + * @param onClick block performed on action click + * @param modifier item modifier + * @param onClickLabel semantics label for the [onClick] action. Should be the same text as in [title] + * @param contentColor color of the item contend. + * Usually [CupertinoColors.systemRed] is used for destructive actions. + * @param icon action trailing icon + * @param caption content before [icon] + * @param title action title + * */ +@Composable +fun CupertinoMenuScope.MenuPickerAction( + isSelected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier, + onClickLabel: String? = null, + enabled: Boolean = true, + contentColor: Color = CupertinoDropdownMenuDefaults.ContentColor, + selectionIcon: (@Composable () -> Unit) = { CupertinoDropdownMenuDefaults.PickerLeadingIcon() }, + icon: (@Composable () -> Unit) = {}, + caption: @Composable () -> Unit = {}, + title: @Composable () -> Unit, +) { + this as CupertinoMenuScopeImpl + + DisposableEffect(this) { + val prev = hasPicker + hasPicker = true + onDispose { + hasPicker = prev + } + } + + ActionWithoutPadding( + modifier = modifier.semantics(mergeDescendants = true) { + selected = isSelected + role = Role.DropdownList + }, + onClickLabel = onClickLabel, + onClick = onClick, + enabled = enabled, + contentColor = contentColor, + icon = icon, + caption = caption, + title = { pv -> + Box( + contentAlignment = Alignment.CenterStart, + ) { + Box( + modifier = Modifier.size(MinItemHeight), + contentAlignment = Alignment.Center, + ) { + if (isSelected) { + selectionIcon() + } + } + Box( + modifier = Modifier.padding(pv), + ) { + title() + } + } + }, + ) +} + +/** + * Separator for the menu actions groups + * */ +@Composable +fun CupertinoMenuScope.MenuDivider( + modifier: Modifier = Modifier, + color: Color? = null, + height: Dp = DividerHeight, +) = MenuItem( + minHeight = DividerHeight, +) { + Spacer( + modifier = modifier + .height(height) + .fillMaxWidth() + .padding(horizontal = MenuHorizontalMargin) + .background(color ?: CupertinoDropdownMenuDefaults.DividerColor), + ) +} + +@OptIn(ExperimentalCupertinoApi::class) +@Composable +private fun CupertinoMenuScope.ActionWithoutPadding( + onClick: () -> Unit, + modifier: Modifier = Modifier, + onClickLabel: String? = null, + enabled: Boolean = true, + contentColor: Color = Color.Unspecified, + icon: @Composable () -> Unit = {}, + caption: @Composable () -> Unit = {}, + title: @Composable (PaddingValues) -> Unit, +) = MenuItem { + val color = contentColor.takeOrElse { LocalContentColor.current }.let { if (enabled) it else it.copy(alpha = it.alpha / 4f) } + + ProvideTextStyle(CupertinoTheme.typography.callout) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(SplitPadding), + modifier = modifier + .heightIn(min = CupertinoSectionTokens.MinHeight) + .fillMaxWidth() + .padding(8.dp) + .clip(RoundedRectangle(24.dp)) + .clickable( + enabled = enabled, + onClick = onClick, + onClickLabel = onClickLabel, + role = Role.DropdownList, + ) + ) { + CompositionLocalProvider(LocalContentColor provides color) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(CupertinoSectionTokens.SplitPadding), + modifier = Modifier.padding(it.copy(end = 0.dp)) + ) { + caption.invoke() + + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.size(MinItemHeight / 3), + ) { + icon.invoke() + } + } + title(it.copy(start = 0.dp)) + } + } + } +} + +@Composable +private fun DropdownMenuContent( + width: Dp, + containerColor: Color, + expandedStates: MutableTransitionState, + transformOriginState: TransformOrigin, + scrollState: ScrollState, + paddingValue: PaddingValues, + modifier: Modifier = Modifier, + backdrop: Backdrop, + content: @Composable () -> Unit, +) { + // Menu open/close animation. + val transition = rememberTransition(expandedStates, "DropDownMenu") + val animationScope = rememberCoroutineScope() + + val scale by transition.animateFloat( + transitionSpec = { + if (false isTransitioningTo true) { + // Dismissed to expanded + MenuEnterTransition + } else { + // Expanded to dismissed. + MenuExitTransition + } + }, + ) { + if (it) { + // Menu is expanded. + 1f + } else { + // Menu is dismissed. + .1f + } + } + val alpha by transition.animateFloat( + transitionSpec = { + if (false isTransitioningTo true) { + // Dismissed to expanded + MenuEnterTransition + } else { + MenuExitTransition + } + }, + ) { + if (it) { + // Menu is expanded. + 1f + } else { + // Menu is dismissed. + 0f + } + } + val shape = CupertinoDropdownMenuDefaults.Shape + val interactiveHighlight = remember(animationScope) { InteractiveHighlight(animationScope = animationScope) } + + CupertinoSurface( + color = Color.Transparent, + modifier = Modifier + .padding(paddingValue) + .graphicsLayer { + scaleX = scale + scaleY = scale + this.alpha = alpha + transformOrigin = transformOriginState + clip = false + } + .width(width) + ) { + CompositionLocalProvider( + LocalSeparatorColor provides BrightSeparatorColor, + ) { + ProvideTextStyle( + CupertinoTheme.typography.body + ) { + SubcomposeLayout( + modifier = modifier + .drawBackdrop( + backdrop = backdrop, + shape = { shape }, + effects = { + vibrancy() + blur(2.dp.toPx()) + if (shape is RoundedRectangularShape || shape is CornerBasedShape) { + lens(12.dp.toPx(), 24.dp.toPx()) + } + }, + layerBlock = { + val width = this.size.width + val height = this.size.height + + val progress = interactiveHighlight.pressProgress + val scale = lerp(1f, 1f + 4.dp.toPx() / height, progress) + + val maxOffset = this.size.minDimension + val initialDerivative = 0.05f + val offset = interactiveHighlight.offset + + translationX = maxOffset * tanh(initialDerivative * offset.x / maxOffset) + translationY = maxOffset * tanh(initialDerivative * offset.y / maxOffset) + + val maxDragScale = 4.dp.toPx() / height + val offsetAngle = atan2(offset.y, offset.x) + + scaleX = scale + maxDragScale * abs(cos(offsetAngle) * offset.x / this.size.maxDimension) * (width / height).fastCoerceAtMost(1f) + scaleY = scale + maxDragScale * abs(sin(offsetAngle) * offset.y / this.size.maxDimension) * (height / width).fastCoerceAtMost(1f) + }, + onDrawSurface = { + drawRect(containerColor.copy(alpha = 0.95f)) + }, + ) + .fillMaxWidth() + .heightIn(max = MenuMaxHeight) + .verticalScroll(scrollState), + ) { constraints -> + val layoutWidth = constraints.maxWidth + val itemPlaceables = subcompose(CupertinoDropdownMenuSlots.Item, content).fastMap { it.measure(constraints) } + val allPlacements = buildList(itemPlaceables.size * 2) { itemPlaceables.fastForEach { placeable -> add(placeable) } } + val height = allPlacements.fastSumBy { it.height } + + layout(layoutWidth, height) { + var y = 0 + allPlacements.fastForEach { + it.placeRelative(0, y) + y += it.height + } + } + } + } + } + } +} + +private enum class CupertinoDropdownMenuSlots { + Section, + Item, + Separator, +} + +internal fun calculateTransformOrigin( + parentBounds: IntRect, + menuBounds: IntRect, +): TransformOrigin { + val pivotX = + when { + menuBounds.left >= parentBounds.right -> 0f + menuBounds.right <= parentBounds.left -> 1f + menuBounds.width == 0 -> 0f + else -> { + val intersectionCenter = + ( + max(parentBounds.left, menuBounds.left) + + min( + parentBounds.right, + menuBounds.right, + ) + ) / 2 + (intersectionCenter - menuBounds.left).toFloat() / menuBounds.width + } + } + val pivotY = + when { + menuBounds.top >= parentBounds.bottom -> 0f + menuBounds.bottom <= parentBounds.top -> 1f + menuBounds.height == 0 -> 0f + else -> { + val intersectionCenter = + ( + max(parentBounds.top, menuBounds.top) + + min(parentBounds.bottom, menuBounds.bottom) + ) / 2 + (intersectionCenter - menuBounds.top).toFloat() / menuBounds.height + } + } + return TransformOrigin(pivotX, pivotY) +} + +@Immutable +internal data class DropdownMenuPositionProvider( + val contentOffset: DpOffset, + val density: Density, + val safePadding: Dp = 0.dp, + val verticalMargin: Dp = MenuVerticalMargin, + val onPositionCalculated: (IntRect, IntRect) -> Unit = { _, _ -> }, +) : PopupPositionProvider { + override fun calculatePosition( + anchorBounds: IntRect, + windowSize: IntSize, + layoutDirection: LayoutDirection, + popupContentSize: IntSize, + ): IntOffset { + val verticalMargin = with(density) { verticalMargin.roundToPx() } + val safePaddingPx = with(density) { safePadding.roundToPx() } + val contentOffsetX = with(density) { + contentOffset.x.roundToPx() * (if (layoutDirection == LayoutDirection.Ltr) 1 else -1) + } + val contentOffsetY = with(density) { contentOffset.y.roundToPx() } + + // popupContentSize는 이미 safePadding * 2를 포함한 크기 + // 실제 메뉴 크기 = popupContentSize - safePadding * 2 + // Popup 자체는 safePadding만큼 앞당겨서 배치해야 내부 컨텐츠가 anchor에 붙음 + val leftToAnchorLeft = anchorBounds.left + contentOffsetX - safePaddingPx + val rightToAnchorRight = anchorBounds.right - popupContentSize.width + contentOffsetX + safePaddingPx + val rightToWindowRight = windowSize.width - popupContentSize.width + val leftToWindowLeft = 0 + + val x = if (layoutDirection == LayoutDirection.Ltr) { + sequenceOf( + leftToAnchorLeft, + rightToAnchorRight, + if (anchorBounds.left >= 0) rightToWindowRight else leftToWindowLeft, + ) + } else { + sequenceOf( + rightToAnchorRight, + leftToAnchorLeft, + if (anchorBounds.right <= windowSize.width) leftToWindowLeft else rightToWindowRight, + ) + }.firstOrNull { + it >= -safePaddingPx && it + popupContentSize.width <= windowSize.width + safePaddingPx + } ?: rightToAnchorRight + +// val topToAnchorTop = maxOf( +// anchorBounds.top + contentOffsetY - safePaddingPx, +// verticalMargin - safePaddingPx +// ) + val topToAnchorTop = anchorBounds.top + contentOffsetY - safePaddingPx + val bottomToAnchorBottom = anchorBounds.bottom - popupContentSize.height + contentOffsetY + safePaddingPx + val bottomToWindowBottom = windowSize.height - popupContentSize.height - verticalMargin + safePaddingPx + +// val y = sequenceOf( +// topToAnchorTop, +// bottomToAnchorBottom, +// bottomToWindowBottom, +// ).firstOrNull { +// it + safePaddingPx >= verticalMargin && +// it + popupContentSize.height - safePaddingPx <= windowSize.height - verticalMargin +// } ?: bottomToAnchorBottom + + val y = sequenceOf( + topToAnchorTop, + bottomToAnchorBottom, + bottomToWindowBottom, + ).firstOrNull { + it + popupContentSize.height - safePaddingPx <= windowSize.height - verticalMargin + // 👆 상단 조건(verticalMargin 체크) 제거 + } ?: bottomToAnchorBottom + + onPositionCalculated( + anchorBounds, + IntRect(x, y, x + popupContentSize.width, y + popupContentSize.height), + ) + return IntOffset(x, y) + } +} + +internal class CupertinoMenuScopeImpl : CupertinoMenuScope { + var hasPicker: Boolean by mutableStateOf(false) +} + +private val MenuMaxHeight: Dp = 600.dp +private val SelectorSize = 20.dp + +private val MenuHorizontalMargin = 24.dp +private val MenuVerticalMargin = 24.dp +private val MinItemHeight = 48.dp +private val DividerHeight = 1.dp +private val MinTitleHeight = 32.dp +private val SplitPadding = 16.dp +private val MenuPaddingValues = PaddingValues(16.dp, 8.dp) + +private val MenuEnterTransition = + spring( + dampingRatio = .825f, + stiffness = Spring.StiffnessMediumLow, + ) + +private val MenuExitTransition = + tween( + durationMillis = 350, + easing = LinearOutSlowInEasing, + ) From 9a0af6bfb8176b8604629ff9a2000db3e68e039f Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 25 Mar 2026 15:45:33 +0900 Subject: [PATCH 005/109] =?UTF-8?q?=E2=9C=A8=20feat(hig):=20CupertinoDropd?= =?UTF-8?q?ownMenu=20=EC=95=84=EC=9D=B4=EC=BD=98=20=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=95=84=EC=9B=83=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CupertinoDropdownMenu의 `MenuAction` 파라미터에서 기존 `icon`, `caption`을 `leadingIcon`, `trailingIcon`으로 변경하고 전반적인 레이아웃을 개선했습니다. - `MenuAction` 파라미터 이름을 `leadingIcon`으로 변경하고 `trailingIcon` 추가 - 메뉴 아이템 내 아이콘 크기를 조정하고 텍스트 영역에 `weight(1f)` 적용 - `CupertinoDropdownMenu` 내부에 수직 패딩 및 아이템 가로 패딩 수정 - 불필요한 미사용 코드 및 주석 제거 - 라이브러리 버전을 `1.0.2-alpha02`로 업데이트 - 샘플 앱(`CupertinoWidgetsScreen`, `TestScreen`)에 변경된 파라미터 반영 및 테스트 코드 추가 --- .../cupertino/CupertinoWidgetsScreen.kt | 18 +++--- .../src/commonMain/kotlin/test/TestScreen.kt | 7 +-- gradle/libs.versions.toml | 2 +- .../zone/ien/hig/CupertinoDropdownMenu.kt | 57 +++++++++---------- 4 files changed, 42 insertions(+), 42 deletions(-) diff --git a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt index 8d209d85..0dfaaea8 100644 --- a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt +++ b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt @@ -68,6 +68,7 @@ import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Badge import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -1796,10 +1797,11 @@ private fun DropdownExample( } ) { MenuAction( + enabled = false, onClick = { dropdownVisible = false }, - icon = { + leadingIcon = { CupertinoIcon( imageVector = CupertinoIcons.Default.SquareAndArrowUp, contentDescription = null @@ -1809,15 +1811,17 @@ private fun DropdownExample( CupertinoText("Share") } MenuAction( - enabled = false, onClick = { dropdownVisible = false }, - icon = { + leadingIcon = { CupertinoIcon( imageVector = CupertinoIcons.Default.Bookmark, contentDescription = null ) + }, + trailingIcon = { + Badge { Text("1") } } ) { CupertinoText("Add to Favorites") @@ -1830,7 +1834,7 @@ private fun DropdownExample( }, contentColor = red, - icon = { + leadingIcon = { CupertinoIcon( imageVector = CupertinoIcons.Default.Trash, contentDescription = null @@ -1880,7 +1884,7 @@ private fun DropdownExample2( onClick = { dropdownVisible = false }, - icon = { + leadingIcon = { CupertinoIcon( imageVector = CupertinoIcons.Default.SquareAndArrowUp, contentDescription = null @@ -1894,7 +1898,7 @@ private fun DropdownExample2( onClick = { dropdownVisible = false }, - icon = { + leadingIcon = { CupertinoIcon( imageVector = CupertinoIcons.Default.Bookmark, contentDescription = null @@ -1911,7 +1915,7 @@ private fun DropdownExample2( }, contentColor = red, - icon = { + leadingIcon = { CupertinoIcon( imageVector = CupertinoIcons.Default.Trash, contentDescription = null diff --git a/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt b/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt index 5c50d6fb..8f558a11 100644 --- a/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt +++ b/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt @@ -27,7 +27,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import com.kyant.backdrop.backdrops.layerBackdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop @@ -218,7 +217,7 @@ fun TestScreen( ) { MenuAction( onClick = { expanded = false }, - icon = { + leadingIcon = { CupertinoIcon( imageVector = CupertinoIcons.Default.CheckmarkCircle, contentDescription = null @@ -229,7 +228,7 @@ fun TestScreen( } MenuAction( onClick = { expanded = false }, - icon = { + leadingIcon = { CupertinoIcon( imageVector = CupertinoIcons.Default.Pin, contentDescription = null @@ -240,7 +239,7 @@ fun TestScreen( } MenuAction( onClick = { expanded = false }, - icon = { + leadingIcon = { CupertinoIcon( imageVector = CupertinoIcons.Default.PersonCropCircle, contentDescription = null diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 77263c1e..469d22c8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "9.0.0" kotlin = "2.3.20" -lib-version-name = "1.0.2-alpha01" +lib-version-name = "1.0.2-alpha02" # plugin compose-plugin = "1.10.3" diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt index c53ea7ba..30d4a3ce 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt @@ -58,11 +58,9 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.TransformOrigin -import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.takeOrElse import androidx.compose.ui.layout.SubcomposeLayout @@ -82,7 +80,6 @@ import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastCoerceAtMost import androidx.compose.ui.util.fastForEach -import androidx.compose.ui.util.fastForEachIndexed import androidx.compose.ui.util.fastMap import androidx.compose.ui.util.fastSumBy import androidx.compose.ui.util.lerp @@ -144,12 +141,9 @@ fun CupertinoDropdownMenu( val expandedStates = remember { MutableTransitionState(false) } expandedStates.targetState = expanded val safePadding = 32.dp -// val density = LocalDensity.current -// val safePaddingPx = with(density) { safePadding.roundToPx() } if (expandedStates.currentState || expandedStates.targetState) { var transformOrigin by remember { mutableStateOf(TransformOrigin.Center) } -// var menuOffset by remember { mutableStateOf(Offset.Zero) } val density = LocalDensity.current val popupPositionProvider = DropdownMenuPositionProvider( contentOffset = offset, @@ -158,10 +152,6 @@ fun CupertinoDropdownMenu( density = density ) { parentBounds, menuBounds -> transformOrigin = calculateTransformOrigin(parentBounds, menuBounds) -// menuOffset = Offset( -// menuBounds.left.toFloat() + safePaddingPx, -// menuBounds.top.toFloat() + safePaddingPx -// ) } Popup( @@ -279,8 +269,7 @@ fun CupertinoMenuScope.MenuTitle( * @param onClickLabel semantics label for the [onClick] action. Should be the same text as in [title] * @param contentColor color of the item contend. * Usually [CupertinoColors.systemRed] is used for destructive actions. - * @param icon action trailing icon - * @param caption content before [icon] + * @param leadingIcon action leading icon * @param title action title * */ @Composable @@ -290,8 +279,8 @@ fun CupertinoMenuScope.MenuAction( onClickLabel: String? = null, enabled: Boolean = true, contentColor: Color = CupertinoDropdownMenuDefaults.ContentColor, - icon: (@Composable () -> Unit) = {}, - caption: @Composable () -> Unit = {}, + leadingIcon: @Composable () -> Unit = {}, + trailingIcon: @Composable () -> Unit = {}, title: @Composable () -> Unit, ) = ActionWithoutPadding( onClickLabel = onClickLabel, @@ -299,8 +288,8 @@ fun CupertinoMenuScope.MenuAction( onClick = onClick, enabled = enabled, contentColor = contentColor, - icon = icon, - caption = caption, + leadingIcon = leadingIcon, + trailingIcon = trailingIcon, ) { Box( @@ -322,8 +311,7 @@ fun CupertinoMenuScope.MenuAction( * @param onClickLabel semantics label for the [onClick] action. Should be the same text as in [title] * @param contentColor color of the item contend. * Usually [CupertinoColors.systemRed] is used for destructive actions. - * @param icon action trailing icon - * @param caption content before [icon] + * @param leadingIcon action leading icon * @param title action title * */ @Composable @@ -335,8 +323,8 @@ fun CupertinoMenuScope.MenuPickerAction( enabled: Boolean = true, contentColor: Color = CupertinoDropdownMenuDefaults.ContentColor, selectionIcon: (@Composable () -> Unit) = { CupertinoDropdownMenuDefaults.PickerLeadingIcon() }, - icon: (@Composable () -> Unit) = {}, - caption: @Composable () -> Unit = {}, + leadingIcon: (@Composable () -> Unit) = {}, + trailingIcon: @Composable () -> Unit = {}, title: @Composable () -> Unit, ) { this as CupertinoMenuScopeImpl @@ -358,8 +346,8 @@ fun CupertinoMenuScope.MenuPickerAction( onClick = onClick, enabled = enabled, contentColor = contentColor, - icon = icon, - caption = caption, + leadingIcon = leadingIcon, + trailingIcon = trailingIcon, title = { pv -> Box( contentAlignment = Alignment.CenterStart, @@ -410,8 +398,8 @@ private fun CupertinoMenuScope.ActionWithoutPadding( onClickLabel: String? = null, enabled: Boolean = true, contentColor: Color = Color.Unspecified, - icon: @Composable () -> Unit = {}, - caption: @Composable () -> Unit = {}, + leadingIcon: @Composable () -> Unit = {}, + trailingIcon: @Composable () -> Unit = {}, title: @Composable (PaddingValues) -> Unit, ) = MenuItem { val color = contentColor.takeOrElse { LocalContentColor.current }.let { if (enabled) it else it.copy(alpha = it.alpha / 4f) } @@ -423,7 +411,7 @@ private fun CupertinoMenuScope.ActionWithoutPadding( modifier = modifier .heightIn(min = CupertinoSectionTokens.MinHeight) .fillMaxWidth() - .padding(8.dp) + .padding(horizontal = 8.dp) .clip(RoundedRectangle(24.dp)) .clickable( enabled = enabled, @@ -438,16 +426,24 @@ private fun CupertinoMenuScope.ActionWithoutPadding( horizontalArrangement = Arrangement.spacedBy(CupertinoSectionTokens.SplitPadding), modifier = Modifier.padding(it.copy(end = 0.dp)) ) { - caption.invoke() - Box( contentAlignment = Alignment.Center, - modifier = Modifier.size(MinItemHeight / 3), + modifier = Modifier.size(MinItemHeight / 2), + ) { + leadingIcon.invoke() + } + Box( + modifier = Modifier.weight(1f) + ) { + title(it.copy(start = 0.dp)) + } + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.size(MinItemHeight / 2), ) { - icon.invoke() + trailingIcon.invoke() } } - title(it.copy(start = 0.dp)) } } } @@ -607,6 +603,7 @@ private fun DropdownMenuContent( }, ) .fillMaxWidth() + .padding(vertical = 8.dp) .heightIn(max = MenuMaxHeight) .verticalScroll(scrollState), ) { constraints -> From 8c283c15e88601d8c3343d623934167763a27108 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 25 Mar 2026 16:09:37 +0900 Subject: [PATCH 006/109] =?UTF-8?q?=E2=99=BB=EF=B8=8Frefactor(hig):=20Cupe?= =?UTF-8?q?rtinoDropdownMenu=20=EC=95=84=EC=9D=B4=EC=BD=98=20null=20?= =?UTF-8?q?=ED=97=88=EC=9A=A9=20=EB=B0=8F=20=EB=B2=84=EC=A0=84=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `CupertinoDropdownMenu`의 `leadingIcon` 및 `trailingIcon`의 기본값을 빈 람다에서 `null`로 변경하여 불필요한 레이아웃 생성을 방지 - 아이콘이 존재할 때만 `Box` 컨테이너를 렌더링하도록 조건부 로직 적용 - 라이브러리 버전을 `1.0.2-alpha03`으로 업데이트 --- gradle/libs.versions.toml | 2 +- .../zone/ien/hig/CupertinoDropdownMenu.kt | 29 ++++++++++--------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 469d22c8..66b84ad4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "9.0.0" kotlin = "2.3.20" -lib-version-name = "1.0.2-alpha02" +lib-version-name = "1.0.2-alpha03" # plugin compose-plugin = "1.10.3" diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt index 30d4a3ce..6ae436ef 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt @@ -279,8 +279,8 @@ fun CupertinoMenuScope.MenuAction( onClickLabel: String? = null, enabled: Boolean = true, contentColor: Color = CupertinoDropdownMenuDefaults.ContentColor, - leadingIcon: @Composable () -> Unit = {}, - trailingIcon: @Composable () -> Unit = {}, + leadingIcon: @Composable (() -> Unit)? = null, + trailingIcon: @Composable (() -> Unit)? = null, title: @Composable () -> Unit, ) = ActionWithoutPadding( onClickLabel = onClickLabel, @@ -398,8 +398,8 @@ private fun CupertinoMenuScope.ActionWithoutPadding( onClickLabel: String? = null, enabled: Boolean = true, contentColor: Color = Color.Unspecified, - leadingIcon: @Composable () -> Unit = {}, - trailingIcon: @Composable () -> Unit = {}, + leadingIcon: @Composable (() -> Unit)? = null, + trailingIcon: @Composable (() -> Unit)? = null, title: @Composable (PaddingValues) -> Unit, ) = MenuItem { val color = contentColor.takeOrElse { LocalContentColor.current }.let { if (enabled) it else it.copy(alpha = it.alpha / 4f) } @@ -426,22 +426,23 @@ private fun CupertinoMenuScope.ActionWithoutPadding( horizontalArrangement = Arrangement.spacedBy(CupertinoSectionTokens.SplitPadding), modifier = Modifier.padding(it.copy(end = 0.dp)) ) { - Box( - contentAlignment = Alignment.Center, - modifier = Modifier.size(MinItemHeight / 2), - ) { - leadingIcon.invoke() + leadingIcon?.let { icon -> + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.size(MinItemHeight / 2), + + ) { icon() } } Box( modifier = Modifier.weight(1f) ) { title(it.copy(start = 0.dp)) } - Box( - contentAlignment = Alignment.Center, - modifier = Modifier.size(MinItemHeight / 2), - ) { - trailingIcon.invoke() + trailingIcon?.let { icon -> + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.size(MinItemHeight / 2), + ) { icon() } } } } From e3d30b8657e851f80bb5c1226a0ea1be339e9249 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Thu, 26 Mar 2026 11:00:26 +0900 Subject: [PATCH 007/109] =?UTF-8?q?=E2=9C=A8=20feat(hig):=20CupertinoDropd?= =?UTF-8?q?ownMenu=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=95=84=EC=9B=83=20=EC=B5=9C=EC=A0=81=ED=99=94=20=EB=B0=8F=20?= =?UTF-8?q?=EB=B2=84=EC=A0=84=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `MenuAction`에 `hasIcon` 상태를 추가하여 메뉴 내 아이콘이 있는 아이템이 하나라도 있을 경우 모든 아이템의 텍스트 정렬이 유지되도록 여백 확보 로직 추가 - `CupertinoDropdownMenu` 내 레이아웃 측정 시 `widthIn` 및 `itemConstraints`를 적용하여 최소 너비 보장 및 가로 꽉 차는 배치 수정 - 불필요한 KDoc 주석 삭제 및 코드 스타일 정리 (삼항 연산자, 중괄호 등) - `libs.versions.toml`에서 라이브러리 버전을 `1.0.2-alpha04`로 업데이트 - `CupertinoWidgetsScreen` 예제에서 테스트를 위해 Share 아이템의 아이콘을 임시 주석 처리 --- .../cupertino/CupertinoWidgetsScreen.kt | 12 +- gradle/libs.versions.toml | 2 +- .../zone/ien/hig/CupertinoDropdownMenu.kt | 405 ++++++------------ 3 files changed, 133 insertions(+), 286 deletions(-) diff --git a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt index 0dfaaea8..b6ddf370 100644 --- a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt +++ b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt @@ -1801,12 +1801,12 @@ private fun DropdownExample( onClick = { dropdownVisible = false }, - leadingIcon = { - CupertinoIcon( - imageVector = CupertinoIcons.Default.SquareAndArrowUp, - contentDescription = null - ) - } +// leadingIcon = { +// CupertinoIcon( +// imageVector = CupertinoIcons.Default.SquareAndArrowUp, +// contentDescription = null +// ) +// } ) { CupertinoText("Share") } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 66b84ad4..0d8400b4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "9.0.0" kotlin = "2.3.20" -lib-version-name = "1.0.2-alpha03" +lib-version-name = "1.0.2-alpha04" # plugin compose-plugin = "1.10.3" diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt index 6ae436ef..4da6279c 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt @@ -16,8 +16,6 @@ * limitations under the License. */ - - package zone.ien.hig import androidx.compose.animation.core.LinearOutSlowInEasing @@ -41,7 +39,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CornerBasedShape import androidx.compose.foundation.verticalScroll @@ -102,7 +100,6 @@ import zone.ien.hig.theme.BrightSeparatorColor import zone.ien.hig.theme.CupertinoColors import zone.ien.hig.theme.CupertinoTheme import zone.ien.hig.theme.systemGray5 -import zone.ien.hig.theme.systemRed import zone.ien.hig.utils.InteractiveHighlight import kotlin.math.abs import kotlin.math.atan2 @@ -114,15 +111,6 @@ import kotlin.math.tanh sealed interface CupertinoMenuScope -/** - * Cupertino elevated dropdown menu. Usually used for top bar actions. - * - * @see MenuSection - * @see MenuTitle - * @see MenuAction - * @see MenuPickerAction - * @see MenuDivider - * */ @Composable @ExperimentalCupertinoApi fun CupertinoDropdownMenu( @@ -166,29 +154,16 @@ fun CupertinoDropdownMenu( expandedStates = expandedStates, transformOriginState = transformOrigin, scrollState = scrollState, - modifier = modifier.padding(safePadding), content = { scope.run { content() } }, width = width, paddingValue = paddingValues, backdrop = backdrop, + modifier = modifier.padding(safePadding) ) } } } -/** - * Plain menu item with manual padding control. - * Usually shouldn't be used directly. - * - * @param minHeight minimum item height - * @param content item content - * - * @see MenuSection - * @see MenuTitle - * @see MenuAction - * @see MenuPickerAction - * @see MenuDivider - * */ @Composable fun CupertinoMenuScope.MenuItem( modifier: Modifier = Modifier, @@ -198,80 +173,45 @@ fun CupertinoMenuScope.MenuItem( this as CupertinoMenuScopeImpl Box( - modifier = modifier.heightIn(minHeight), contentAlignment = Alignment.CenterStart, + modifier = modifier.heightIn(minHeight) ) { content( MenuPaddingValues.let { - if (!hasPicker) { - it - } else { - it.copy( - start = it.calculateStartPadding(LocalLayoutDirection.current) + SelectorSize, - ) - } + if (!hasPicker) it + else it.copy( + start = it.calculateStartPadding(LocalLayoutDirection.current) + SelectorSize, + ) }, ) } } -/** - * Group of buttons with top [MenuTitle] and bottom [MenuDivider] - * - * @see MenuTitle - * @see MenuDivider - * */ @Composable inline fun CupertinoMenuScope.MenuSection( noinline title: (@Composable () -> Unit)? = null, content: @Composable CupertinoMenuScope.() -> Unit, ) { - if (title != null) { - MenuTitle(title = title) - } + if (title != null) MenuTitle(title = title) content() } -/** - * Title of the [MenuSection] - * */ @Composable fun CupertinoMenuScope.MenuTitle( modifier: Modifier = Modifier, title: @Composable () -> Unit, ) { - MenuItem( - modifier = modifier, - minHeight = MinTitleHeight, - ) { + MenuItem(modifier = modifier, minHeight = MinTitleHeight) { CompositionLocalProvider( LocalContentColor provides CupertinoTheme.colorScheme.secondaryLabel, ) { - ProvideTextStyle( - CupertinoTheme.typography.footnote, - ) { - Box( - Modifier - .padding(it), - ) { - title() - } + ProvideTextStyle(CupertinoTheme.typography.footnote) { + Box(Modifier.padding(it)) { title() } } } } } -/** - * Default menu button - - * @param onClick block performed on action click - * @param modifier item modifier - * @param onClickLabel semantics label for the [onClick] action. Should be the same text as in [title] - * @param contentColor color of the item contend. - * Usually [CupertinoColors.systemRed] is used for destructive actions. - * @param leadingIcon action leading icon - * @param title action title - * */ @Composable fun CupertinoMenuScope.MenuAction( onClick: () -> Unit, @@ -282,38 +222,31 @@ fun CupertinoMenuScope.MenuAction( leadingIcon: @Composable (() -> Unit)? = null, trailingIcon: @Composable (() -> Unit)? = null, title: @Composable () -> Unit, -) = ActionWithoutPadding( - onClickLabel = onClickLabel, - modifier = modifier, - onClick = onClick, - enabled = enabled, - contentColor = contentColor, - leadingIcon = leadingIcon, - trailingIcon = trailingIcon, -) -{ - Box( - modifier = Modifier.padding(it), +) { + this as CupertinoMenuScopeImpl + + // ✅ 아이콘이 있는 아이템이 하나라도 있으면 hasIcon = true 등록 + if (leadingIcon != null) { + DisposableEffect(this) { + val prev = hasIcon + hasIcon = true + onDispose { hasIcon = prev } + } + } + + ActionWithoutPadding( + onClickLabel = onClickLabel, + modifier = modifier, + onClick = onClick, + enabled = enabled, + contentColor = contentColor, + leadingIcon = leadingIcon, + trailingIcon = trailingIcon, ) { - title() + Box(modifier = Modifier.padding(it)) { title() } } } -/** - * Picker action with leading icon ([Checkmark] by default) if selected. - * - * If menu has at least one picker action (no matter selected or not) - * then all menu items will have additional start padding - * - * @param isSelected selection flag. If item is selected, it will have a [selectionIcon] - * @param onClick block performed on action click - * @param modifier item modifier - * @param onClickLabel semantics label for the [onClick] action. Should be the same text as in [title] - * @param contentColor color of the item contend. - * Usually [CupertinoColors.systemRed] is used for destructive actions. - * @param leadingIcon action leading icon - * @param title action title - * */ @Composable fun CupertinoMenuScope.MenuPickerAction( isSelected: Boolean, @@ -332,9 +265,7 @@ fun CupertinoMenuScope.MenuPickerAction( DisposableEffect(this) { val prev = hasPicker hasPicker = true - onDispose { - hasPicker = prev - } + onDispose { hasPicker = prev } } ActionWithoutPadding( @@ -349,38 +280,25 @@ fun CupertinoMenuScope.MenuPickerAction( leadingIcon = leadingIcon, trailingIcon = trailingIcon, title = { pv -> - Box( - contentAlignment = Alignment.CenterStart, - ) { + Box(contentAlignment = Alignment.CenterStart) { Box( modifier = Modifier.size(MinItemHeight), contentAlignment = Alignment.Center, ) { - if (isSelected) { - selectionIcon() - } - } - Box( - modifier = Modifier.padding(pv), - ) { - title() + if (isSelected) selectionIcon() } + Box(modifier = Modifier.padding(pv)) { title() } } }, ) } -/** - * Separator for the menu actions groups - * */ @Composable fun CupertinoMenuScope.MenuDivider( modifier: Modifier = Modifier, color: Color? = null, height: Dp = DividerHeight, -) = MenuItem( - minHeight = DividerHeight, -) { +) = MenuItem(minHeight = DividerHeight) { Spacer( modifier = modifier .height(height) @@ -402,7 +320,10 @@ private fun CupertinoMenuScope.ActionWithoutPadding( trailingIcon: @Composable (() -> Unit)? = null, title: @Composable (PaddingValues) -> Unit, ) = MenuItem { - val color = contentColor.takeOrElse { LocalContentColor.current }.let { if (enabled) it else it.copy(alpha = it.alpha / 4f) } + this as CupertinoMenuScopeImpl + + val color = contentColor.takeOrElse { LocalContentColor.current } + .let { if (enabled) it else it.copy(alpha = it.alpha / 4f) } ProvideTextStyle(CupertinoTheme.typography.callout) { Row( @@ -424,20 +345,26 @@ private fun CupertinoMenuScope.ActionWithoutPadding( Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(CupertinoSectionTokens.SplitPadding), - modifier = Modifier.padding(it.copy(end = 0.dp)) + modifier = Modifier.padding(it) ) { - leadingIcon?.let { icon -> - Box( - contentAlignment = Alignment.Center, - modifier = Modifier.size(MinItemHeight / 2), - - ) { icon() } + when { + // 아이콘 있음 → 아이콘 표시 + leadingIcon != null -> { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.size(MinItemHeight / 2), + ) { leadingIcon() } + } + // 아이콘 없지만 다른 아이템에 아이콘 있음 → 공백 확보 + hasIcon -> { + Spacer(modifier = Modifier.size(MinItemHeight / 2)) + } } - Box( - modifier = Modifier.weight(1f) - ) { + + Box(modifier = Modifier.weight(1f)) { title(it.copy(start = 0.dp)) } + trailingIcon?.let { icon -> Box( contentAlignment = Alignment.Center, @@ -450,36 +377,27 @@ private fun CupertinoMenuScope.ActionWithoutPadding( } } -/** - * Contains default values used for [CupertinoDropdownMenu]. - */ @Immutable object CupertinoDropdownMenuDefaults { val DefaultWidth = 260.dp val SmallWidth = 160.dp - val Elevation = 16.dp - val PaddingValues = PaddingValues(0.dp) val Shape: Shape - @Composable - @ReadOnlyComposable + @Composable @ReadOnlyComposable get() = CupertinoSectionDefaults.shape(SectionStyle.InsetGrouped) val ContainerColor: Color - @Composable - @ReadOnlyComposable + @Composable @ReadOnlyComposable get() = CupertinoTheme.colorScheme.tertiarySystemBackground val ContentColor: Color - @Composable - @ReadOnlyComposable + @Composable @ReadOnlyComposable get() = CupertinoTheme.colorScheme.label val DividerColor: Color - @Composable - @ReadOnlyComposable + @Composable @ReadOnlyComposable get() = CupertinoColors.systemGray5 @Composable @@ -504,47 +422,22 @@ private fun DropdownMenuContent( backdrop: Backdrop, content: @Composable () -> Unit, ) { - // Menu open/close animation. val transition = rememberTransition(expandedStates, "DropDownMenu") val animationScope = rememberCoroutineScope() + val density = LocalDensity.current val scale by transition.animateFloat( transitionSpec = { - if (false isTransitioningTo true) { - // Dismissed to expanded - MenuEnterTransition - } else { - // Expanded to dismissed. - MenuExitTransition - } + if (false isTransitioningTo true) MenuEnterTransition else MenuExitTransition }, - ) { - if (it) { - // Menu is expanded. - 1f - } else { - // Menu is dismissed. - .1f - } - } + ) { if (it) 1f else .1f } + val alpha by transition.animateFloat( transitionSpec = { - if (false isTransitioningTo true) { - // Dismissed to expanded - MenuEnterTransition - } else { - MenuExitTransition - } + if (false isTransitioningTo true) MenuEnterTransition else MenuExitTransition }, - ) { - if (it) { - // Menu is expanded. - 1f - } else { - // Menu is dismissed. - 0f - } - } + ) { if (it) 1f else 0f } + val shape = CupertinoDropdownMenuDefaults.Shape val interactiveHighlight = remember(animationScope) { InteractiveHighlight(animationScope = animationScope) } @@ -559,14 +452,10 @@ private fun DropdownMenuContent( transformOrigin = transformOriginState clip = false } - .width(width) + .widthIn(min = width) ) { - CompositionLocalProvider( - LocalSeparatorColor provides BrightSeparatorColor, - ) { - ProvideTextStyle( - CupertinoTheme.typography.body - ) { + CompositionLocalProvider(LocalSeparatorColor provides BrightSeparatorColor) { + ProvideTextStyle(CupertinoTheme.typography.body) { SubcomposeLayout( modifier = modifier .drawBackdrop( @@ -580,12 +469,10 @@ private fun DropdownMenuContent( } }, layerBlock = { - val width = this.size.width - val height = this.size.height - + val w = this.size.width + val h = this.size.height val progress = interactiveHighlight.pressProgress - val scale = lerp(1f, 1f + 4.dp.toPx() / height, progress) - + val s = lerp(1f, 1f + 4.dp.toPx() / h, progress) val maxOffset = this.size.minDimension val initialDerivative = 0.05f val offset = interactiveHighlight.offset @@ -593,27 +480,36 @@ private fun DropdownMenuContent( translationX = maxOffset * tanh(initialDerivative * offset.x / maxOffset) translationY = maxOffset * tanh(initialDerivative * offset.y / maxOffset) - val maxDragScale = 4.dp.toPx() / height + val maxDragScale = 4.dp.toPx() / h val offsetAngle = atan2(offset.y, offset.x) - scaleX = scale + maxDragScale * abs(cos(offsetAngle) * offset.x / this.size.maxDimension) * (width / height).fastCoerceAtMost(1f) - scaleY = scale + maxDragScale * abs(sin(offsetAngle) * offset.y / this.size.maxDimension) * (height / width).fastCoerceAtMost(1f) + scaleX = s + maxDragScale * abs(cos(offsetAngle) * offset.x / this.size.maxDimension) * (w / h).fastCoerceAtMost(1f) + scaleY = s + maxDragScale * abs(sin(offsetAngle) * offset.y / this.size.maxDimension) * (h / w).fastCoerceAtMost(1f) }, onDrawSurface = { drawRect(containerColor.copy(alpha = 0.95f)) }, ) - .fillMaxWidth() .padding(vertical = 8.dp) .heightIn(max = MenuMaxHeight) .verticalScroll(scrollState), ) { constraints -> - val layoutWidth = constraints.maxWidth - val itemPlaceables = subcompose(CupertinoDropdownMenuSlots.Item, content).fastMap { it.measure(constraints) } - val allPlacements = buildList(itemPlaceables.size * 2) { itemPlaceables.fastForEach { placeable -> add(placeable) } } - val height = allPlacements.fastSumBy { it.height } + val minWidth = with(density) { width.roundToPx() } + val itemConstraints = constraints.copy( + minWidth = minWidth, + maxWidth = minWidth + ) + val itemPlaceables = subcompose(CupertinoDropdownMenuSlots.Item, content) + .fastMap { it.measure(itemConstraints) } + val allPlacements = buildList(itemPlaceables.size * 2) { + itemPlaceables.fastForEach { add(it) } + } - layout(layoutWidth, height) { + val layoutWidth = allPlacements.maxOfOrNull { it.width } + ?.coerceAtLeast(minWidth) ?: minWidth + val layoutHeight = allPlacements.fastSumBy { it.height } + + layout(layoutWidth, layoutHeight) { var y = 0 allPlacements.fastForEach { it.placeRelative(0, y) @@ -626,47 +522,27 @@ private fun DropdownMenuContent( } } -private enum class CupertinoDropdownMenuSlots { - Section, - Item, - Separator, -} +private enum class CupertinoDropdownMenuSlots { Section, Item, Separator } -internal fun calculateTransformOrigin( - parentBounds: IntRect, - menuBounds: IntRect, -): TransformOrigin { - val pivotX = - when { - menuBounds.left >= parentBounds.right -> 0f - menuBounds.right <= parentBounds.left -> 1f - menuBounds.width == 0 -> 0f - else -> { - val intersectionCenter = - ( - max(parentBounds.left, menuBounds.left) + - min( - parentBounds.right, - menuBounds.right, - ) - ) / 2 - (intersectionCenter - menuBounds.left).toFloat() / menuBounds.width - } +internal fun calculateTransformOrigin(parentBounds: IntRect, menuBounds: IntRect): TransformOrigin { + val pivotX = when { + menuBounds.left >= parentBounds.right -> 0f + menuBounds.right <= parentBounds.left -> 1f + menuBounds.width == 0 -> 0f + else -> { + val intersectionCenter = (max(parentBounds.left, menuBounds.left) + min(parentBounds.right, menuBounds.right)) / 2 + (intersectionCenter - menuBounds.left).toFloat() / menuBounds.width } - val pivotY = - when { - menuBounds.top >= parentBounds.bottom -> 0f - menuBounds.bottom <= parentBounds.top -> 1f - menuBounds.height == 0 -> 0f - else -> { - val intersectionCenter = - ( - max(parentBounds.top, menuBounds.top) + - min(parentBounds.bottom, menuBounds.bottom) - ) / 2 - (intersectionCenter - menuBounds.top).toFloat() / menuBounds.height - } + } + val pivotY = when { + menuBounds.top >= parentBounds.bottom -> 0f + menuBounds.bottom <= parentBounds.top -> 1f + menuBounds.height == 0 -> 0f + else -> { + val intersectionCenter = (max(parentBounds.top, menuBounds.top) + min(parentBounds.bottom, menuBounds.bottom)) / 2 + (intersectionCenter - menuBounds.top).toFloat() / menuBounds.height } + } return TransformOrigin(pivotX, pivotY) } @@ -691,55 +567,27 @@ internal data class DropdownMenuPositionProvider( } val contentOffsetY = with(density) { contentOffset.y.roundToPx() } - // popupContentSize는 이미 safePadding * 2를 포함한 크기 - // 실제 메뉴 크기 = popupContentSize - safePadding * 2 - // Popup 자체는 safePadding만큼 앞당겨서 배치해야 내부 컨텐츠가 anchor에 붙음 val leftToAnchorLeft = anchorBounds.left + contentOffsetX - safePaddingPx val rightToAnchorRight = anchorBounds.right - popupContentSize.width + contentOffsetX + safePaddingPx val rightToWindowRight = windowSize.width - popupContentSize.width val leftToWindowLeft = 0 val x = if (layoutDirection == LayoutDirection.Ltr) { - sequenceOf( - leftToAnchorLeft, - rightToAnchorRight, - if (anchorBounds.left >= 0) rightToWindowRight else leftToWindowLeft, - ) + sequenceOf(leftToAnchorLeft, rightToAnchorRight, if (anchorBounds.left >= 0) rightToWindowRight else leftToWindowLeft) } else { - sequenceOf( - rightToAnchorRight, - leftToAnchorLeft, - if (anchorBounds.right <= windowSize.width) leftToWindowLeft else rightToWindowRight, - ) + sequenceOf(rightToAnchorRight, leftToAnchorLeft, if (anchorBounds.right <= windowSize.width) leftToWindowLeft else rightToWindowRight) }.firstOrNull { it >= -safePaddingPx && it + popupContentSize.width <= windowSize.width + safePaddingPx } ?: rightToAnchorRight -// val topToAnchorTop = maxOf( -// anchorBounds.top + contentOffsetY - safePaddingPx, -// verticalMargin - safePaddingPx -// ) val topToAnchorTop = anchorBounds.top + contentOffsetY - safePaddingPx val bottomToAnchorBottom = anchorBounds.bottom - popupContentSize.height + contentOffsetY + safePaddingPx val bottomToWindowBottom = windowSize.height - popupContentSize.height - verticalMargin + safePaddingPx -// val y = sequenceOf( -// topToAnchorTop, -// bottomToAnchorBottom, -// bottomToWindowBottom, -// ).firstOrNull { -// it + safePaddingPx >= verticalMargin && -// it + popupContentSize.height - safePaddingPx <= windowSize.height - verticalMargin -// } ?: bottomToAnchorBottom - - val y = sequenceOf( - topToAnchorTop, - bottomToAnchorBottom, - bottomToWindowBottom, - ).firstOrNull { - it + popupContentSize.height - safePaddingPx <= windowSize.height - verticalMargin - // 👆 상단 조건(verticalMargin 체크) 제거 - } ?: bottomToAnchorBottom + val y = sequenceOf(topToAnchorTop, bottomToAnchorBottom, bottomToWindowBottom) + .firstOrNull { + it + popupContentSize.height - safePaddingPx <= windowSize.height - verticalMargin + } ?: bottomToAnchorBottom onPositionCalculated( anchorBounds, @@ -749,13 +597,14 @@ internal data class DropdownMenuPositionProvider( } } +// ✅ hasIcon 추가 internal class CupertinoMenuScopeImpl : CupertinoMenuScope { var hasPicker: Boolean by mutableStateOf(false) + var hasIcon: Boolean by mutableStateOf(false) } private val MenuMaxHeight: Dp = 600.dp private val SelectorSize = 20.dp - private val MenuHorizontalMargin = 24.dp private val MenuVerticalMargin = 24.dp private val MinItemHeight = 48.dp @@ -764,14 +613,12 @@ private val MinTitleHeight = 32.dp private val SplitPadding = 16.dp private val MenuPaddingValues = PaddingValues(16.dp, 8.dp) -private val MenuEnterTransition = - spring( - dampingRatio = .825f, - stiffness = Spring.StiffnessMediumLow, - ) +private val MenuEnterTransition = spring( + dampingRatio = .825f, + stiffness = Spring.StiffnessMediumLow, +) -private val MenuExitTransition = - tween( - durationMillis = 350, - easing = LinearOutSlowInEasing, - ) +private val MenuExitTransition = tween( + durationMillis = 350, + easing = LinearOutSlowInEasing, +) \ No newline at end of file From 8f46c49c60a0f007f754e646f683fc5034ecf6c0 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Thu, 26 Mar 2026 14:37:04 +0900 Subject: [PATCH 008/109] =?UTF-8?q?=F0=9F=94=A7=20chore:=20backdrop=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EA=B7=B8=EB=A3=B9=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=B0=8F=20README=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - backdrop 라이브러리 그룹을 io.github.kyant0에서 zone.ien.backdrop으로 변경 - backdrop 버전을 2.0.0-alpha04로 업데이트 - README.md에 slanos/compose-cupertino 포크 출처 정보 추가 - README.md의 Disclaimer 섹션 삭제 및 문구 정돈 --- README.md | 10 +++------- gradle/libs.versions.toml | 5 +++-- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index c04bcb94..53206dad 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ # compose-hig -This is a fork of [alexzhirkevich/compose-cupertino](https://github.com/alexzhirkevich/compose-cupertino) except its been updated to work with CMP 1.7+ +This is a fork of [alexzhirkevich/compose-cupertino](https://github.com/alexzhirkevich/compose-cupertino) and [slanos/compose-cupertino](https://github.com/slanos/compose-cupertino). -Additionally this repo has automated builds to enable faster releases, to take advantage of new compose multiplatform features as they become available. +Additionally, this repo has automated builds to enable faster releases, to take advantage of new compose multiplatform features as they become available. ## New features (compared to the OG `compose-cupertino`) @@ -66,8 +66,4 @@ Ensure that the copyright information is included in your file(s): */ ``` -Ideally you set up Android Studio / IDE to handle copyright notices for you: https://stackoverflow.com/a/48718711/1730421 - -# Disclaimer - -Note this repo is not officially supported in any capacity; changes will be applied upstream when applicable ([example1](https://github.com/alexzhirkevich/compose-cupertino/pull/74), [example2](https://github.com/alexzhirkevich/compose-cupertino/pull/77)) +Ideally you set up Android Studio / IDE to handle copyright notices for you: https://stackoverflow.com/a/48718711/1730421 \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0d8400b4..a47cb7d3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ compose-navigation3 = "1.0.0-alpha06" # ui activity-compose = "1.13.0" lifecycle = "2.10.0" -backdrop = "2.0.0-alpha04+dev1" +backdrop = "2.0.0-alpha04" capsule = "1.2.0" haze = "1.7.2" @@ -43,7 +43,8 @@ compose-ui-util = { group = "org.jetbrains.compose.ui", name = "ui-util", versio compose-resources = { group = "org.jetbrains.compose.components", name = "components-resources", version.ref = "compose-plugin" } compose-navigation3 = { group = "org.jetbrains.androidx.navigation3", name = "navigation3-ui", version.ref = "compose-navigation3" } -backdrop = { group = "io.github.kyant0", name = "backdrop", version.ref = "backdrop"} +backdrop = { group = "zone.ien.backdrop", name = "backdrop", version.ref = "backdrop"} +#backdrop = { group = "io.github.kyant0", name = "backdrop", version.ref = "backdrop"} capsule = { group = "io.github.kyant0", name = "shapes", version.ref = "capsule"} haze = { group = "dev.chrisbanes.haze", name = "haze", version.ref = "haze" } From bf6d44474da765f41954de78702c0304544e64d8 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Thu, 26 Mar 2026 15:29:40 +0900 Subject: [PATCH 009/109] =?UTF-8?q?=F0=9F=94=96=20chore(version):=20?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=EC=9D=84=201.0.2-alpha05=EB=A1=9C=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a47cb7d3..22823342 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "9.0.0" kotlin = "2.3.20" -lib-version-name = "1.0.2-alpha04" +lib-version-name = "1.0.2-alpha05" # plugin compose-plugin = "1.10.3" From 539509d826f546897935601001c135366516c3a3 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Thu, 26 Mar 2026 15:58:10 +0900 Subject: [PATCH 010/109] =?UTF-8?q?=F0=9F=93=A6=20chore:=20=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EB=B2=84=EC=A0=84?= =?UTF-8?q?=EC=9D=84=201.0.2-alpha06=EC=9C=BC=EB=A1=9C=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - gradle/libs.versions.toml 파일의 lib-version-name을 1.0.2-alpha05에서 1.0.2-alpha06으로 변경합니다. --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 22823342..4b12e5c3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "9.0.0" kotlin = "2.3.20" -lib-version-name = "1.0.2-alpha05" +lib-version-name = "1.0.2-alpha06" # plugin compose-plugin = "1.10.3" From 721c1b216c007293dfc4738262322c78730b76a4 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Sun, 29 Mar 2026 20:36:52 +0900 Subject: [PATCH 011/109] =?UTF-8?q?=E2=9C=A8=20feat(hig):=20CupertinoDropd?= =?UTF-8?q?ownMenuNative=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20iOS=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=ED=8B=B0=EB=B8=8C=20=EB=A9=94=EB=89=B4=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - iOS에서 `UIButton`과 `UIMenu`를 활용한 `CupertinoDropdownMenuNative` 구현 - `CupertinoMenuItemData` 및 `CupertinoMenuSectionData` 모델 정의 - `Painter`를 `UIImage`로 변환하고 크기를 조정하는 유틸리티 함수 추가 - `CupertinoDropdownMenu`를 공통 인터페이스에서 직접 구현으로 변경 및 리팩토링 - 예제 앱에 네이티브 메뉴 테스트 코드 및 라이브러리 버전 업데이트(1.0.2-alpha02) Closes #52 --- .../cupertino/CupertinoWidgetsScreen.kt | 106 ++- .../src/commonMain/kotlin/test/TestScreen.kt | 122 ++- gradle/libs.versions.toml | 2 +- .../zone/ien/hig/SystemBarAppearance.ios.kt | 2 +- hig-native/build.gradle.kts | 1 + .../ien/hig/CupertinoDropdownMenuNative.kt | 46 ++ .../hig/CupertinoDropdownMenuNative.ios.kt | 197 +++++ .../iosMain/kotlin/zone/ien/hig/util.ios.kt | 102 +++ .../hig/CupertinoDropdownMenuNative.nonIos.kt | 87 +++ .../zone/ien/hig/CupertinoDropdownMenu.kt | 671 +++++++++++++++- .../zone/ien/hig/CupertinoDropdownMenu.ios.kt | 35 - .../ien/hig/CupertinoDropdownMenu.nonIos.kt | 729 ------------------ 12 files changed, 1246 insertions(+), 854 deletions(-) create mode 100644 hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt create mode 100644 hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt create mode 100644 hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.nonIos.kt delete mode 100644 hig/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.ios.kt delete mode 100644 hig/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.nonIos.kt diff --git a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt index 8d209d85..6affacdf 100644 --- a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt +++ b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt @@ -115,12 +115,15 @@ import zone.ien.hig.CupertinoDateTimePicker import zone.ien.hig.CupertinoDateTimePickerNative import zone.ien.hig.CupertinoDateTimePickerState import zone.ien.hig.CupertinoDropdownMenu +import zone.ien.hig.CupertinoDropdownMenuNative import zone.ien.hig.CupertinoIcon import zone.ien.hig.CupertinoIconButton import zone.ien.hig.CupertinoLiquidAlertDialog import zone.ien.hig.CupertinoLiquidButton import zone.ien.hig.CupertinoLiquidButtonDefaults import zone.ien.hig.CupertinoLiquidIconButton +import zone.ien.hig.CupertinoMenuItemData +import zone.ien.hig.CupertinoMenuSectionData import zone.ien.hig.CupertinoNavigationTitle import zone.ien.hig.CupertinoPickerState import zone.ien.hig.CupertinoRangeSlider @@ -413,7 +416,8 @@ private fun Body( } SectionItem { DropdownExample2( - backdrop = backdrop + backdrop = backdrop, + isNative = nativePickers.value ) } } @@ -1846,7 +1850,8 @@ private fun DropdownExample( @Composable private fun DropdownExample2( - backdrop: Backdrop + backdrop: Backdrop, + isNative: Boolean ) { var dropdownVisible by remember { mutableStateOf(false) } val layerBackdrop = rememberDefaultBackdrop() @@ -1866,59 +1871,80 @@ private fun DropdownExample2( val red = CupertinoColors.systemRed - CupertinoDropdownMenu( - expanded = dropdownVisible, - onDismissRequest = { dropdownVisible = false }, - backdrop = backdrop - ) { - MenuSection( - title = { - Text("Menu") - } - ) { - MenuAction( - onClick = { - dropdownVisible = false - }, - icon = { - CupertinoIcon( - imageVector = CupertinoIcons.Default.SquareAndArrowUp, - contentDescription = null + if (isNative) { + CupertinoDropdownMenuNative( + expanded = dropdownVisible, + onDismissRequest = { dropdownVisible = false }, + backdrop = backdrop, + sections = listOf( + CupertinoMenuSectionData( + title = "Menu", + items = listOf( + CupertinoMenuItemData( + title = "Share", + onClick = { + dropdownVisible = false + } + ) ) + ) + ) + ) + } else { + CupertinoDropdownMenu( + expanded = dropdownVisible, + onDismissRequest = { dropdownVisible = false }, + backdrop = backdrop + ) { + MenuSection( + title = { + Text("Menu") } ) { - CupertinoText("Share") + MenuAction( + onClick = { + dropdownVisible = false + }, + icon = { + CupertinoIcon( + imageVector = CupertinoIcons.Default.SquareAndArrowUp, + contentDescription = null + ) + } + ) { + CupertinoText("Share") + } + MenuAction( + enabled = false, + onClick = { + dropdownVisible = false + }, + icon = { + CupertinoIcon( + imageVector = CupertinoIcons.Default.Bookmark, + contentDescription = null + ) + } + ) { + CupertinoText("Add to Favorites") + } } + MenuAction( - enabled = false, onClick = { dropdownVisible = false + }, + contentColor = red, icon = { CupertinoIcon( - imageVector = CupertinoIcons.Default.Bookmark, + imageVector = CupertinoIcons.Default.Trash, contentDescription = null ) } ) { - CupertinoText("Add to Favorites") - } - } - - MenuAction( - onClick = { - dropdownVisible = false - - }, - contentColor = red, - icon = { - CupertinoIcon( - imageVector = CupertinoIcons.Default.Trash, - contentDescription = null - ) + CupertinoText("Delete") } - ) { - CupertinoText("Delete") } } } diff --git a/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt b/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt index 0c8b0e4c..d4fca4d1 100644 --- a/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt +++ b/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt @@ -29,6 +29,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -38,10 +39,13 @@ import zone.ien.hig.utils.rememberDefaultBackdrop import kotlinx.coroutines.launch import org.jetbrains.compose.resources.painterResource import zone.ien.hig.CupertinoDropdownMenu +import zone.ien.hig.CupertinoDropdownMenuNative import zone.ien.hig.CupertinoIcon import zone.ien.hig.CupertinoLiquidButton import zone.ien.hig.CupertinoLiquidButtonDefaults import zone.ien.hig.CupertinoLiquidIconButton +import zone.ien.hig.CupertinoMenuItemData +import zone.ien.hig.CupertinoMenuSectionData import zone.ien.hig.CupertinoNavigationTitle import zone.ien.hig.CupertinoScaffold import zone.ien.hig.CupertinoText @@ -215,54 +219,95 @@ fun TestScreen( isInteractive = false, colors = CupertinoLiquidButtonDefaults.glassProminentButtonColors(), ) { - Text(text = "Open Menu") + Text(text = "${if (expanded) "Close" else "Open"} Menu") } // drawBackdrop is used inside Popup internally - CupertinoDropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false }, - backdrop = rememberDefaultBackdrop() - ) { - MenuSection( + if (enabled) { + CupertinoDropdownMenuNative( + expanded = expanded, + onDismissRequest = { + expanded = false + }, + backdrop = rememberDefaultBackdrop(), + items = listOf( + CupertinoMenuItemData( + title = "메시지 선택", + onClick = { + expanded = false + coroutineScope.launch { + snackbarState.showSnackbar("메시지 선택") + } + }, + icon = rememberVectorPainter(CupertinoIcons.Default.CheckmarkCircle) + ), + CupertinoMenuItemData( + title = "고정 편집", + onClick = { + expanded = false + coroutineScope.launch { + snackbarState.showSnackbar("고정 편집") + } + }, + icon = rememberVectorPainter(CupertinoIcons.Default.Pin) + ), + CupertinoMenuItemData( + title = "이름 및 사진 설정", + onClick = { + expanded = false + coroutineScope.launch { + snackbarState.showSnackbar("이름 및 사진 설정") + } + }, + icon = rememberVectorPainter(CupertinoIcons.Default.PersonCropCircle) + ), + ) + ) + } else { + CupertinoDropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + backdrop = rememberDefaultBackdrop() + ) { + MenuSection( // title = { // Text("Menu") // } - ) { - MenuAction( - onClick = { expanded = false }, - icon = { - CupertinoIcon( - imageVector = CupertinoIcons.Default.CheckmarkCircle, - contentDescription = null - ) - } ) { - CupertinoText("메시지 선택") - } - MenuAction( - onClick = { expanded = false }, - icon = { - CupertinoIcon( - imageVector = CupertinoIcons.Default.Pin, - contentDescription = null - ) + MenuAction( + onClick = { expanded = false }, + icon = { + CupertinoIcon( + imageVector = CupertinoIcons.Default.CheckmarkCircle, + contentDescription = null + ) + } + ) { + CupertinoText("메시지 선택") } - ) { - CupertinoText("고정 편집") - } - MenuAction( - onClick = { expanded = false }, - icon = { - CupertinoIcon( - imageVector = CupertinoIcons.Default.PersonCropCircle, - contentDescription = null - ) + MenuAction( + onClick = { expanded = false }, + icon = { + CupertinoIcon( + imageVector = CupertinoIcons.Default.Pin, + contentDescription = null + ) + } + ) { + CupertinoText("고정 편집") + } + MenuAction( + onClick = { expanded = false }, + icon = { + CupertinoIcon( + imageVector = CupertinoIcons.Default.PersonCropCircle, + contentDescription = null + ) + } + ) { + CupertinoText("이름 및 사진 설정") } - ) { - CupertinoText("이름 및 사진 설정") } - } // MenuDivider() // MenuSection( //// title = { @@ -297,6 +342,7 @@ fun TestScreen( // CupertinoText("Add to Favorites") // } // } + } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 77263c1e..469d22c8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "9.0.0" kotlin = "2.3.20" -lib-version-name = "1.0.2-alpha01" +lib-version-name = "1.0.2-alpha02" # plugin compose-plugin = "1.10.3" diff --git a/hig-core/src/iosMain/kotlin/zone/ien/hig/SystemBarAppearance.ios.kt b/hig-core/src/iosMain/kotlin/zone/ien/hig/SystemBarAppearance.ios.kt index b34505a5..5c9565c7 100644 --- a/hig-core/src/iosMain/kotlin/zone/ien/hig/SystemBarAppearance.ios.kt +++ b/hig-core/src/iosMain/kotlin/zone/ien/hig/SystemBarAppearance.ios.kt @@ -20,7 +20,7 @@ package zone.ien.hig import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.ui.interop.LocalUIViewController +import androidx.compose.ui.uikit.LocalUIViewController import org.jetbrains.skiko.OS import org.jetbrains.skiko.OSVersion import org.jetbrains.skiko.available diff --git a/hig-native/build.gradle.kts b/hig-native/build.gradle.kts index e76ce420..33f8d695 100644 --- a/hig-native/build.gradle.kts +++ b/hig-native/build.gradle.kts @@ -33,6 +33,7 @@ kotlin { implementation(libs.compose.runtime) implementation(libs.compose.foundation) implementation(libs.capsule) + implementation(libs.backdrop) } } } diff --git a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt new file mode 100644 index 00000000..8e0bbb4a --- /dev/null +++ b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt @@ -0,0 +1,46 @@ +package zone.ien.hig + +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.rememberScrollState +import androidx.compose.runtime.Composable +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.PopupProperties +import com.kyant.backdrop.Backdrop + +data class CupertinoMenuItemData( + val title: String, + val icon: Painter? = null, + val isDestructive: Boolean = false, + val isDisabled: Boolean = false, + val onClick: () -> Unit, +) + +data class CupertinoMenuSectionData( + val title: String = "", + val items: List, +) + +@OptIn(ExperimentalComposeUiApi::class) +@ExperimentalCupertinoApi +@Composable +expect fun CupertinoDropdownMenuNative( + expanded: Boolean, + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, + offset: DpOffset = DpOffset(0.dp, 0.dp), + paddingValues: PaddingValues = CupertinoDropdownMenuDefaults.PaddingValues, + containerColor: Color = CupertinoDropdownMenuDefaults.ContainerColor, + width: Dp = CupertinoDropdownMenuDefaults.DefaultWidth, + scrollState: ScrollState = rememberScrollState(), + properties: PopupProperties = PopupProperties(focusable = true, clippingEnabled = false), + backdrop: Backdrop, + items: List = listOf(), + sections: List = listOf(), +) \ No newline at end of file diff --git a/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt new file mode 100644 index 00000000..1f89cb8c --- /dev/null +++ b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt @@ -0,0 +1,197 @@ +package zone.ien.hig + +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.positionInWindow +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.uikit.LocalUIViewController +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.window.PopupProperties +import com.kyant.backdrop.Backdrop +import kotlinx.cinterop.BetaInteropApi +import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.cinterop.useContents +import platform.CoreGraphics.CGPointMake +import platform.CoreGraphics.CGRectMake +import platform.UIKit.UIAction +import platform.UIKit.UIButton +import platform.UIKit.UIButtonTypePlain +import platform.UIKit.UIColor +import platform.UIKit.UIContextMenuConfigurationElementOrder +import platform.UIKit.UIContextMenuConfigurationElementOrderFixed +import platform.UIKit.UIControlStateNormal +import platform.UIKit.UIDevice +import platform.UIKit.UIEvent +import platform.UIKit.UIGestureRecognizer +import platform.UIKit.UIImage +import platform.UIKit.UIMenu +import platform.UIKit.UIMenuElement +import platform.UIKit.UIMenuElementAttributesDestructive +import platform.UIKit.UIMenuElementAttributesDisabled +import platform.UIKit.UIMenuOptionsDisplayInline +import platform.UIKit.touchesBegan +import platform.darwin.NSObject + +@OptIn(ExperimentalForeignApi::class, BetaInteropApi::class) +@ExperimentalCupertinoApi +@Composable +actual fun CupertinoDropdownMenuNative( + expanded: Boolean, + onDismissRequest: () -> Unit, + modifier: Modifier, + offset: DpOffset, + paddingValues: PaddingValues, + containerColor: Color, + width: Dp, + scrollState: ScrollState, + properties: PopupProperties, + backdrop: Backdrop, + items: List, + sections: List +) { + val density = LocalDensity.current + val viewController = LocalUIViewController.current + + val delegate = remember { CupertinoDropdownMenuDelegate() } + delegate.items = items + delegate.sections = sections + delegate.onDismissRequest = onDismissRequest + + var posX by remember { mutableStateOf(0.0) } + var posY by remember { mutableStateOf(0.0) } + var btnW by remember { mutableStateOf(0.0) } + var btnH by remember { mutableStateOf(0.0) } + + // UIButton을 뷰 계층 최하단에 삽입 (터치 가로채기 방지) + DisposableEffect(viewController) { + val button = UIButton.buttonWithType(UIButtonTypePlain).apply { + backgroundColor = UIColor.clearColor + setTitle("", forState = UIControlStateNormal) + showsMenuAsPrimaryAction = true + preferredMenuElementOrder = UIContextMenuConfigurationElementOrderFixed + } + viewController.view.insertSubview(button, atIndex = 0) + delegate.button = button + + onDispose { + button.removeFromSuperview() + delegate.button = null + } + } + + // 버튼 위치/크기 동기화 + LaunchedEffect(posX, posY, btnW, btnH) { + delegate.button?.setFrame(CGRectMake(posX, posY, btnW, btnH)) + } + + // 아이템/섹션 변경 시 메뉴 갱신 + LaunchedEffect(items, sections) { + delegate.button?.menu = delegate.buildMenu() + } + + // expanded 변경 → 메뉴 표시 + LaunchedEffect(expanded) { + val button = delegate.button ?: return@LaunchedEffect + if (!expanded) return@LaunchedEffect + + if (isPerformPrimaryActionAvailable()) { + button.performPrimaryAction() + } else { + // iOS 17.4 미만: gesture recognizer를 직접 트리거 + val gesture = button.gestureRecognizers + .orEmpty() + .filterIsInstance() + .firstOrNull { + it.`class`()?.toString() + ?.contains("UITouchDownGestureRecognizer", ignoreCase = true) == true + } + gesture?.touchesBegan(emptySet(), UIEvent()) + } + // 메뉴가 표시된 직후 Compose 측 상태를 닫힘으로 리셋 + onDismissRequest() + } + + // Compose 레이아웃으로 버튼 위치 추적 (투명 Box) + Box( + modifier = modifier + .onGloballyPositioned { coords -> + // positionInWindow()는 Compose 윈도우 기준 + // → UIKit view 좌표계로 변환하려면 rootView 기준 offset을 빼야 함 + val rootView = viewController.view + val windowOrigin = rootView.window + ?.convertPoint( + point = CGPointMake(0.0, 0.0), + fromView = rootView, + ) + + val pos = coords.positionInWindow() + val offsetY = windowOrigin?.useContents { y } ?: 0.0 + + posX = pos.x.toDouble() / density.density + // rootView가 window 기준으로 얼마나 내려와 있는지 보정 + posY = (pos.y.toDouble() / density.density) - offsetY + btnW = coords.size.width.toDouble() / density.density + btnH = coords.size.height.toDouble() / density.density + } + ) +} + +@OptIn(ExperimentalForeignApi::class) +internal class CupertinoDropdownMenuDelegate : NSObject() { + var items: List = emptyList() + var sections: List = emptyList() + var onDismissRequest: () -> Unit = {} + var button: UIButton? = null + + fun buildMenu(): UIMenu { + val topActions: List = items.map { it.toUIAction() } + val sectionMenus: List = sections.map { section -> + UIMenu.menuWithTitle( + title = section.title, + image = null, + identifier = null, + options = UIMenuOptionsDisplayInline, + children = section.items.map { it.toUIAction() }, + ) + } + return UIMenu.menuWithTitle( + title = "", + children = topActions + sectionMenus, + ) + } +} + +private fun CupertinoMenuItemData.toUIAction(): UIAction { + val action = UIAction.actionWithTitle( + title = title, +// image = UIImage.systemImageNamed("chevron.left"), + image = icon?.toUIImage()?.resized(20.0), + identifier = null, + ) { _ -> onClick() } + + var attributes: ULong = 0u + if (isDestructive) attributes = attributes or UIMenuElementAttributesDestructive + if (isDisabled) attributes = attributes or UIMenuElementAttributesDisabled + action.attributes = attributes + return action +} + +private fun isPerformPrimaryActionAvailable(): Boolean { + val systemVersion = UIDevice.currentDevice.systemVersion + val major = systemVersion.split(".").firstOrNull()?.toIntOrNull() ?: 0 + val minor = systemVersion.split(".").getOrNull(1)?.toIntOrNull() ?: 0 + return major >= 17 && minor >= 4 +} diff --git a/hig-native/src/iosMain/kotlin/zone/ien/hig/util.ios.kt b/hig-native/src/iosMain/kotlin/zone/ien/hig/util.ios.kt index 79adf456..a7db95af 100644 --- a/hig-native/src/iosMain/kotlin/zone/ien/hig/util.ios.kt +++ b/hig-native/src/iosMain/kotlin/zone/ien/hig/util.ios.kt @@ -20,9 +20,35 @@ package zone.ien.hig +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.asSkiaBitmap +import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.cinterop.addressOf +import kotlinx.cinterop.usePinned +import org.jetbrains.skia.Data +import org.jetbrains.skia.EncodedImageFormat +import org.jetbrains.skia.Image +import platform.Foundation.NSData +import platform.Foundation.dataWithBytes +import platform.UIKit.UIImage import platform.UIKit.UIUserInterfaceStyle import platform.UIKit.UIView import platform.UIKit.UIViewController +import androidx.compose.ui.graphics.Canvas +import androidx.compose.ui.graphics.drawscope.CanvasDrawScope +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.LayoutDirection +import kotlinx.cinterop.useContents +import platform.CoreGraphics.CGRectMake +import platform.CoreGraphics.CGSizeMake +import platform.UIKit.UIGraphicsBeginImageContextWithOptions +import platform.UIKit.UIGraphicsEndImageContext +import platform.UIKit.UIGraphicsGetImageFromCurrentImageContext +import platform.UIKit.UIImageRenderingMode +import kotlin.math.roundToInt internal fun UIViewController.applyTheme(dark: Boolean) { overrideUserInterfaceStyle = @@ -43,3 +69,79 @@ internal fun UIView.applyTheme(dark: Boolean) { } } } + +@OptIn(ExperimentalForeignApi::class) +fun ImageBitmap.toUIImage(): UIImage { + val skiaImage = Image.makeFromBitmap(this.asSkiaBitmap()) + val pngData = skiaImage.encodeToData(EncodedImageFormat.PNG) + ?: throw Exception("Failed to encode ImageBitmap to PNG") + val pngBytes = pngData.bytes + val nsData = pngBytes.usePinned { pinned -> + NSData.dataWithBytes(pinned.addressOf(0), pngBytes.size.toULong()) + } + return UIImage(data = nsData).imageWithRenderingMode(UIImageRenderingMode.UIImageRenderingModeAlwaysOriginal) +} + +fun Painter.toImageBitmap( + size: Size = intrinsicSize, + density: Density = Density(1f), + layoutDirection: LayoutDirection = LayoutDirection.Ltr, +): ImageBitmap { + val width = size.width.toInt().takeIf { it > 0 } ?: 64 + val height = size.height.toInt().takeIf { it > 0 } ?: 64 + val bitmap = ImageBitmap(width, height) + val canvas = Canvas(bitmap) + CanvasDrawScope().draw( + density = density, + layoutDirection = layoutDirection, + canvas = canvas, + size = Size(width.toFloat(), height.toFloat()) + ) { + // 흰색으로 강제 렌더링 → AlwaysTemplate이 tint를 올바르게 곱할 수 있음 + with(this) { + drawContext.canvas.let { c -> + val paint = androidx.compose.ui.graphics.Paint().apply { + colorFilter = androidx.compose.ui.graphics.ColorFilter.tint( + androidx.compose.ui.graphics.Color.White + ) + } + c.saveLayer( + bounds = androidx.compose.ui.geometry.Rect( + 0f, 0f, width.toFloat(), height.toFloat() + ), + paint = paint + ) + } + } + draw(Size(width.toFloat(), height.toFloat())) + drawContext.canvas.restore() + } + return bitmap +} + +@OptIn(ExperimentalForeignApi::class) +fun Painter.toUIImage(size: Size = intrinsicSize): UIImage { + return this.toImageBitmap(size).toUIImage() +} + +@OptIn(ExperimentalForeignApi::class) +fun UIImage.resized(maxSize: Double): UIImage { + val originalWidth = this.size.useContents { width } + val originalHeight = this.size.useContents { height } + + if (originalWidth <= 0.0 || originalHeight <= 0.0) return this + + val scale = maxSize / maxOf(originalWidth, originalHeight) + val targetWidth = originalWidth * scale + val targetHeight = originalHeight * scale + + val targetSize = CGSizeMake(targetWidth, targetHeight) + UIGraphicsBeginImageContextWithOptions(targetSize, false, 0.0) + this.imageWithRenderingMode(UIImageRenderingMode.UIImageRenderingModeAlwaysOriginal) + .drawInRect(CGRectMake(0.0, 0.0, targetWidth, targetHeight)) + val resizedImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + return resizedImage + ?.imageWithRenderingMode(UIImageRenderingMode.UIImageRenderingModeAlwaysTemplate) + ?: this +} \ No newline at end of file diff --git a/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.nonIos.kt b/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.nonIos.kt new file mode 100644 index 00000000..3c4ed65c --- /dev/null +++ b/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.nonIos.kt @@ -0,0 +1,87 @@ +package zone.ien.hig + +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.runtime.Composable +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.util.fastForEach +import androidx.compose.ui.window.PopupProperties +import com.kyant.backdrop.Backdrop + +@OptIn(markerClass = [ExperimentalComposeUiApi::class]) +@ExperimentalCupertinoApi +@Composable +actual fun CupertinoDropdownMenuNative( + expanded: Boolean, + onDismissRequest: () -> Unit, + modifier: Modifier, + offset: DpOffset, + paddingValues: PaddingValues, + containerColor: Color, + width: Dp, + scrollState: ScrollState, + properties: PopupProperties, + backdrop: Backdrop, + items: List, + sections: List +) { + CupertinoDropdownMenu( + expanded = expanded, + onDismissRequest = onDismissRequest, + modifier = modifier, + offset = offset, + paddingValues = paddingValues, + containerColor = containerColor, + width = width, + scrollState = scrollState, + properties = properties, + backdrop = backdrop + ) { + items.fastForEach { + MenuAction( + onClick = it.onClick, + enabled = !it.isDisabled, + icon = { + it.icon?.let { icon -> + CupertinoIcon( + painter = icon, + contentDescription = null + ) + } + }, + title = { + CupertinoText(text = it.title) + } + + ) + } + sections.fastForEach { section -> + MenuSection( + title = { CupertinoText(text = section.title) } + ) { + items.fastForEach { + MenuAction( + onClick = it.onClick, + enabled = !it.isDisabled, + icon = { + it.icon?.let { icon -> + CupertinoIcon( + painter = icon, + contentDescription = null + ) + } + }, + title = { + CupertinoText(text = it.title) + } + + ) + } + } + } + } +} \ No newline at end of file diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt index 3d11b142..80dd62ca 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt @@ -20,28 +20,102 @@ package zone.ien.hig +import androidx.compose.animation.core.LinearOutSlowInEasing +import androidx.compose.animation.core.MutableTransitionState +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.rememberTransition +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.calculateStartPadding +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CornerBasedShape +import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.Immutable import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.TransformOrigin +import androidx.compose.ui.graphics.drawscope.translate +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.takeOrElse +import androidx.compose.ui.layout.SubcomposeLayout +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.selected +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntRect +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastCoerceAtMost +import androidx.compose.ui.util.fastForEach +import androidx.compose.ui.util.fastForEachIndexed +import androidx.compose.ui.util.fastMap +import androidx.compose.ui.util.fastSumBy +import androidx.compose.ui.util.lerp +import androidx.compose.ui.window.Popup +import androidx.compose.ui.window.PopupPositionProvider import androidx.compose.ui.window.PopupProperties import com.kyant.backdrop.Backdrop +import com.kyant.backdrop.drawBackdrop +import com.kyant.backdrop.effects.blur +import com.kyant.backdrop.effects.lens +import com.kyant.backdrop.effects.vibrancy +import com.kyant.shapes.RoundedRectangle +import com.kyant.shapes.RoundedRectangularShape import zone.ien.hig.icons.CupertinoIcons import zone.ien.hig.icons.outlined.Checkmark import zone.ien.hig.section.CupertinoSectionDefaults +import zone.ien.hig.section.CupertinoSectionTokens import zone.ien.hig.section.SectionStyle +import zone.ien.hig.theme.BrightSeparatorColor import zone.ien.hig.theme.CupertinoColors import zone.ien.hig.theme.CupertinoTheme import zone.ien.hig.theme.systemGray5 +import zone.ien.hig.theme.systemRed +import zone.ien.hig.utils.InteractiveHighlight +import kotlin.math.abs +import kotlin.math.atan2 +import kotlin.math.cos +import kotlin.math.max +import kotlin.math.min +import kotlin.math.sin +import kotlin.math.tanh + +sealed interface CupertinoMenuScope /** * Cupertino elevated dropdown menu. Usually used for top bar actions. @@ -54,7 +128,7 @@ import zone.ien.hig.theme.systemGray5 * */ @Composable @ExperimentalCupertinoApi -expect fun CupertinoDropdownMenu( +fun CupertinoDropdownMenu( expanded: Boolean, onDismissRequest: () -> Unit, modifier: Modifier = Modifier, @@ -66,20 +140,314 @@ expect fun CupertinoDropdownMenu( properties: PopupProperties = PopupProperties(focusable = true, clippingEnabled = false), backdrop: Backdrop, content: @Composable CupertinoMenuScope.() -> Unit, +) { + val expandedStates = remember { MutableTransitionState(false) } + expandedStates.targetState = expanded + val safePadding = 32.dp + + if (expandedStates.currentState || expandedStates.targetState) { + var transformOrigin by remember { mutableStateOf(TransformOrigin.Center) } + val density = LocalDensity.current + val popupPositionProvider = DropdownMenuPositionProvider( + contentOffset = offset, + safePadding = safePadding, + verticalMargin = 0.dp, + density = density + ) { parentBounds, menuBounds -> + transformOrigin = calculateTransformOrigin(parentBounds, menuBounds) + } + + Popup( + onDismissRequest = onDismissRequest, + popupPositionProvider = popupPositionProvider, + properties = properties, + ) { + val scope = remember { CupertinoMenuScopeImpl() } + + DropdownMenuContent( + containerColor = containerColor, + expandedStates = expandedStates, + transformOriginState = transformOrigin, + scrollState = scrollState, + modifier = modifier.padding(safePadding), + content = { scope.run { content() } }, + width = width, + paddingValue = paddingValues, + backdrop = backdrop, + ) + } + } +} + +/** + * Plain menu item with manual padding control. + * Usually shouldn't be used directly. + * + * @param minHeight minimum item height + * @param content item content + * + * @see MenuSection + * @see MenuTitle + * @see MenuAction + * @see MenuPickerAction + * @see MenuDivider + * */ +@Composable +fun CupertinoMenuScope.MenuItem( + modifier: Modifier = Modifier, + minHeight: Dp = MinItemHeight, + content: @Composable (padding: PaddingValues) -> Unit, +) { + this as CupertinoMenuScopeImpl + + Box( + modifier = modifier.heightIn(minHeight), + contentAlignment = Alignment.CenterStart, + ) { + content( + MenuPaddingValues.let { + if (!hasPicker) { + it + } else { + it.copy( + start = it.calculateStartPadding(LocalLayoutDirection.current) + SelectorSize, + ) + } + }, + ) + } +} + +/** + * Group of buttons with top [MenuTitle] and bottom [MenuDivider] + * + * @see MenuTitle + * @see MenuDivider + * */ +@Composable +inline fun CupertinoMenuScope.MenuSection( + noinline title: (@Composable () -> Unit)? = null, + content: @Composable CupertinoMenuScope.() -> Unit, +) { + if (title != null) { + MenuTitle(title = title) + } + content() +} + +/** + * Title of the [MenuSection] + * */ +@Composable +fun CupertinoMenuScope.MenuTitle( + modifier: Modifier = Modifier, + title: @Composable () -> Unit, +) { + MenuItem( + modifier = modifier, + minHeight = MinTitleHeight, + ) { + CompositionLocalProvider( + LocalContentColor provides CupertinoTheme.colorScheme.secondaryLabel, + ) { + ProvideTextStyle( + CupertinoTheme.typography.footnote, + ) { + Box( + Modifier + .padding(it), + ) { + title() + } + } + } + } +} + +/** + * Default menu button + + * @param onClick block performed on action click + * @param modifier item modifier + * @param onClickLabel semantics label for the [onClick] action. Should be the same text as in [title] + * @param contentColor color of the item contend. + * Usually [CupertinoColors.systemRed] is used for destructive actions. + * @param icon action trailing icon + * @param caption content before [icon] + * @param title action title + * */ +@Composable +fun CupertinoMenuScope.MenuAction( + onClick: () -> Unit, + modifier: Modifier = Modifier, + onClickLabel: String? = null, + enabled: Boolean = true, + contentColor: Color = CupertinoDropdownMenuDefaults.ContentColor, + icon: (@Composable () -> Unit) = {}, + caption: @Composable () -> Unit = {}, + title: @Composable () -> Unit, +) = ActionWithoutPadding( + onClickLabel = onClickLabel, + modifier = modifier, + onClick = onClick, + enabled = enabled, + contentColor = contentColor, + icon = icon, + caption = caption, ) +{ + Box( + modifier = Modifier.padding(it), + ) { + title() + } +} + +/** + * Picker action with leading icon ([Checkmark] by default) if selected. + * + * If menu has at least one picker action (no matter selected or not) + * then all menu items will have additional start padding + * + * @param isSelected selection flag. If item is selected, it will have a [selectionIcon] + * @param onClick block performed on action click + * @param modifier item modifier + * @param onClickLabel semantics label for the [onClick] action. Should be the same text as in [title] + * @param contentColor color of the item contend. + * Usually [CupertinoColors.systemRed] is used for destructive actions. + * @param icon action trailing icon + * @param caption content before [icon] + * @param title action title + * */ +@Composable +fun CupertinoMenuScope.MenuPickerAction( + isSelected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier, + onClickLabel: String? = null, + enabled: Boolean = true, + contentColor: Color = CupertinoDropdownMenuDefaults.ContentColor, + selectionIcon: (@Composable () -> Unit) = { CupertinoDropdownMenuDefaults.PickerLeadingIcon() }, + icon: (@Composable () -> Unit) = {}, + caption: @Composable () -> Unit = {}, + title: @Composable () -> Unit, +) { + this as CupertinoMenuScopeImpl -sealed interface CupertinoMenu { + DisposableEffect(this) { + val prev = hasPicker + hasPicker = true + onDispose { + hasPicker = prev + } + } - data class MenuItem(): CupertinoMenu - data class MenuSection(): CupertinoMenu - data class MenuTitle(): CupertinoMenu - data class MenuAction(): CupertinoMenu - data class MenuPickerAction(): CupertinoMenu - data class MenuDevider(): CupertinoMenu + ActionWithoutPadding( + modifier = modifier.semantics(mergeDescendants = true) { + selected = isSelected + role = Role.DropdownList + }, + onClickLabel = onClickLabel, + onClick = onClick, + enabled = enabled, + contentColor = contentColor, + icon = icon, + caption = caption, + title = { pv -> + Box( + contentAlignment = Alignment.CenterStart, + ) { + Box( + modifier = Modifier.size(MinItemHeight), + contentAlignment = Alignment.Center, + ) { + if (isSelected) { + selectionIcon() + } + } + Box( + modifier = Modifier.padding(pv), + ) { + title() + } + } + }, + ) } /** - * Contains default values used for [hig.CupertinoDropdownMenu]. + * Separator for the menu actions groups + * */ +@Composable +fun CupertinoMenuScope.MenuDivider( + modifier: Modifier = Modifier, + color: Color? = null, + height: Dp = DividerHeight, +) = MenuItem( + minHeight = DividerHeight, +) { + Spacer( + modifier = modifier + .height(height) + .fillMaxWidth() + .padding(horizontal = MenuHorizontalMargin) + .background(color ?: CupertinoDropdownMenuDefaults.DividerColor), + ) +} + +@OptIn(ExperimentalCupertinoApi::class) +@Composable +private fun CupertinoMenuScope.ActionWithoutPadding( + onClick: () -> Unit, + modifier: Modifier = Modifier, + onClickLabel: String? = null, + enabled: Boolean = true, + contentColor: Color = Color.Unspecified, + icon: @Composable () -> Unit = {}, + caption: @Composable () -> Unit = {}, + title: @Composable (PaddingValues) -> Unit, +) = MenuItem { + val color = contentColor.takeOrElse { LocalContentColor.current }.let { if (enabled) it else it.copy(alpha = it.alpha / 4f) } + + ProvideTextStyle(CupertinoTheme.typography.callout) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(SplitPadding), + modifier = modifier + .heightIn(min = CupertinoSectionTokens.MinHeight) + .fillMaxWidth() + .padding(8.dp) + .clip(RoundedRectangle(24.dp)) + .clickable( + enabled = enabled, + onClick = onClick, + onClickLabel = onClickLabel, + role = Role.DropdownList, + ) + ) { + CompositionLocalProvider(LocalContentColor provides color) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(CupertinoSectionTokens.SplitPadding), + modifier = Modifier.padding(it.copy(end = 0.dp)) + ) { + caption.invoke() + + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.size(MinItemHeight / 3), + ) { + icon.invoke() + } + } + title(it.copy(start = 0.dp)) + } + } + } +} + +/** + * Contains default values used for [CupertinoDropdownMenu]. */ @Immutable object CupertinoDropdownMenuDefaults { @@ -118,4 +486,287 @@ object CupertinoDropdownMenuDefaults { contentDescription = null, ) } -} \ No newline at end of file +} + +@Composable +private fun DropdownMenuContent( + width: Dp, + containerColor: Color, + expandedStates: MutableTransitionState, + transformOriginState: TransformOrigin, + scrollState: ScrollState, + paddingValue: PaddingValues, + modifier: Modifier = Modifier, + backdrop: Backdrop, + content: @Composable () -> Unit, +) { + // Menu open/close animation. + val transition = rememberTransition(expandedStates, "DropDownMenu") + val animationScope = rememberCoroutineScope() + + val scale by transition.animateFloat( + transitionSpec = { + if (false isTransitioningTo true) { + // Dismissed to expanded + MenuEnterTransition + } else { + // Expanded to dismissed. + MenuExitTransition + } + }, + ) { + if (it) { + // Menu is expanded. + 1f + } else { + // Menu is dismissed. + .1f + } + } + val alpha by transition.animateFloat( + transitionSpec = { + if (false isTransitioningTo true) { + // Dismissed to expanded + MenuEnterTransition + } else { + MenuExitTransition + } + }, + ) { + if (it) { + // Menu is expanded. + 1f + } else { + // Menu is dismissed. + 0f + } + } + val shape = CupertinoDropdownMenuDefaults.Shape + val interactiveHighlight = remember(animationScope) { InteractiveHighlight(animationScope = animationScope) } + + CupertinoSurface( + color = Color.Transparent, + modifier = Modifier + .padding(paddingValue) + .graphicsLayer { + scaleX = scale + scaleY = scale + this.alpha = alpha + transformOrigin = transformOriginState + clip = false + } + .width(width) + ) { + CompositionLocalProvider( + LocalSeparatorColor provides BrightSeparatorColor, + ) { + ProvideTextStyle( + CupertinoTheme.typography.body + ) { + SubcomposeLayout( + modifier = modifier + .drawBackdrop( + backdrop = backdrop, + shape = { shape }, + effects = { + vibrancy() + blur(2.dp.toPx()) + if (shape is RoundedRectangularShape || shape is CornerBasedShape) { + lens(12.dp.toPx(), 24.dp.toPx()) + } + }, + layerBlock = { + val width = this.size.width + val height = this.size.height + + val progress = interactiveHighlight.pressProgress + val scale = lerp(1f, 1f + 4.dp.toPx() / height, progress) + + val maxOffset = this.size.minDimension + val initialDerivative = 0.05f + val offset = interactiveHighlight.offset + + translationX = maxOffset * tanh(initialDerivative * offset.x / maxOffset) + translationY = maxOffset * tanh(initialDerivative * offset.y / maxOffset) + + val maxDragScale = 4.dp.toPx() / height + val offsetAngle = atan2(offset.y, offset.x) + + scaleX = scale + maxDragScale * abs(cos(offsetAngle) * offset.x / this.size.maxDimension) * (width / height).fastCoerceAtMost(1f) + scaleY = scale + maxDragScale * abs(sin(offsetAngle) * offset.y / this.size.maxDimension) * (height / width).fastCoerceAtMost(1f) + }, + onDrawSurface = { + drawRect(containerColor.copy(alpha = 0.95f)) + }, + ) + .fillMaxWidth() + .heightIn(max = MenuMaxHeight) + .verticalScroll(scrollState), + ) { constraints -> + val layoutWidth = constraints.maxWidth + val itemPlaceables = subcompose(CupertinoDropdownMenuSlots.Item, content).fastMap { it.measure(constraints) } + val allPlacements = buildList(itemPlaceables.size * 2) { itemPlaceables.fastForEach { placeable -> add(placeable) } } + val height = allPlacements.fastSumBy { it.height } + + layout(layoutWidth, height) { + var y = 0 + allPlacements.fastForEach { + it.placeRelative(0, y) + y += it.height + } + } + } + } + } + } +} + +private enum class CupertinoDropdownMenuSlots { + Section, + Item, + Separator, +} + +internal fun calculateTransformOrigin( + parentBounds: IntRect, + menuBounds: IntRect, +): TransformOrigin { + val pivotX = + when { + menuBounds.left >= parentBounds.right -> 0f + menuBounds.right <= parentBounds.left -> 1f + menuBounds.width == 0 -> 0f + else -> { + val intersectionCenter = + ( + max(parentBounds.left, menuBounds.left) + + min( + parentBounds.right, + menuBounds.right, + ) + ) / 2 + (intersectionCenter - menuBounds.left).toFloat() / menuBounds.width + } + } + val pivotY = + when { + menuBounds.top >= parentBounds.bottom -> 0f + menuBounds.bottom <= parentBounds.top -> 1f + menuBounds.height == 0 -> 0f + else -> { + val intersectionCenter = + ( + max(parentBounds.top, menuBounds.top) + + min(parentBounds.bottom, menuBounds.bottom) + ) / 2 + (intersectionCenter - menuBounds.top).toFloat() / menuBounds.height + } + } + return TransformOrigin(pivotX, pivotY) +} + +@Immutable +internal data class DropdownMenuPositionProvider( + val contentOffset: DpOffset, + val density: Density, + val safePadding: Dp = 0.dp, + val verticalMargin: Dp = MenuVerticalMargin, + val onPositionCalculated: (IntRect, IntRect) -> Unit = { _, _ -> }, +) : PopupPositionProvider { + override fun calculatePosition( + anchorBounds: IntRect, + windowSize: IntSize, + layoutDirection: LayoutDirection, + popupContentSize: IntSize, + ): IntOffset { + val verticalMargin = with(density) { verticalMargin.roundToPx() } + val safePaddingPx = with(density) { safePadding.roundToPx() } + val contentOffsetX = with(density) { + contentOffset.x.roundToPx() * (if (layoutDirection == LayoutDirection.Ltr) 1 else -1) + } + val contentOffsetY = with(density) { contentOffset.y.roundToPx() } + + // popupContentSize는 이미 safePadding * 2를 포함한 크기 + // 실제 메뉴 크기 = popupContentSize - safePadding * 2 + // Popup 자체는 safePadding만큼 앞당겨서 배치해야 내부 컨텐츠가 anchor에 붙음 + val leftToAnchorLeft = anchorBounds.left + contentOffsetX - safePaddingPx + val rightToAnchorRight = anchorBounds.right - popupContentSize.width + contentOffsetX + safePaddingPx + val rightToWindowRight = windowSize.width - popupContentSize.width + val leftToWindowLeft = 0 + + val x = if (layoutDirection == LayoutDirection.Ltr) { + sequenceOf( + leftToAnchorLeft, + rightToAnchorRight, + if (anchorBounds.left >= 0) rightToWindowRight else leftToWindowLeft, + ) + } else { + sequenceOf( + rightToAnchorRight, + leftToAnchorLeft, + if (anchorBounds.right <= windowSize.width) leftToWindowLeft else rightToWindowRight, + ) + }.firstOrNull { + it >= -safePaddingPx && it + popupContentSize.width <= windowSize.width + safePaddingPx + } ?: rightToAnchorRight + +// val topToAnchorTop = maxOf( +// anchorBounds.top + contentOffsetY - safePaddingPx, +// verticalMargin - safePaddingPx +// ) + val topToAnchorTop = anchorBounds.top + contentOffsetY - safePaddingPx + val bottomToAnchorBottom = anchorBounds.bottom - popupContentSize.height + contentOffsetY + safePaddingPx + val bottomToWindowBottom = windowSize.height - popupContentSize.height - verticalMargin + safePaddingPx + +// val y = sequenceOf( +// topToAnchorTop, +// bottomToAnchorBottom, +// bottomToWindowBottom, +// ).firstOrNull { +// it + safePaddingPx >= verticalMargin && +// it + popupContentSize.height - safePaddingPx <= windowSize.height - verticalMargin +// } ?: bottomToAnchorBottom + + val y = sequenceOf( + topToAnchorTop, + bottomToAnchorBottom, + bottomToWindowBottom, + ).firstOrNull { + it + popupContentSize.height - safePaddingPx <= windowSize.height - verticalMargin + // 👆 상단 조건(verticalMargin 체크) 제거 + } ?: bottomToAnchorBottom + + onPositionCalculated( + anchorBounds, + IntRect(x, y, x + popupContentSize.width, y + popupContentSize.height), + ) + return IntOffset(x, y) + } +} + +internal class CupertinoMenuScopeImpl : CupertinoMenuScope { + var hasPicker: Boolean by mutableStateOf(false) +} + +private val MenuMaxHeight: Dp = 600.dp +private val SelectorSize = 20.dp + +private val MenuHorizontalMargin = 24.dp +private val MenuVerticalMargin = 24.dp +private val MinItemHeight = 48.dp +private val DividerHeight = 1.dp +private val MinTitleHeight = 32.dp +private val SplitPadding = 16.dp +private val MenuPaddingValues = PaddingValues(16.dp, 8.dp) + +private val MenuEnterTransition = + spring( + dampingRatio = .825f, + stiffness = Spring.StiffnessMediumLow, + ) + +private val MenuExitTransition = + tween( + durationMillis = 350, + easing = LinearOutSlowInEasing, + ) diff --git a/hig/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.ios.kt b/hig/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.ios.kt deleted file mode 100644 index ad3c8d8a..00000000 --- a/hig/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.ios.kt +++ /dev/null @@ -1,35 +0,0 @@ -package zone.ien.hig - -import androidx.compose.foundation.ScrollState -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.uikit.LocalUIViewController -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.DpOffset -import androidx.compose.ui.window.PopupProperties -import com.kyant.backdrop.Backdrop - -@Composable -@ExperimentalCupertinoApi -actual fun CupertinoDropdownMenu( - expanded: Boolean, - onDismissRequest: () -> Unit, - modifier: Modifier, - offset: DpOffset, - paddingValues: PaddingValues, - containerColor: Color, - width: Dp, - scrollState: ScrollState, - properties: PopupProperties, - backdrop: Backdrop, - content: @Composable (CupertinoMenuScope.() -> Unit) -) { - val density = LocalDensity.current - val viewController = LocalUIViewController.current - - -} \ No newline at end of file diff --git a/hig/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.nonIos.kt b/hig/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.nonIos.kt deleted file mode 100644 index 8f475442..00000000 --- a/hig/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.nonIos.kt +++ /dev/null @@ -1,729 +0,0 @@ -/* - * Copyright (c) 2023-2024. Compose Cupertino project and open source contributors. - * Copyright (c) 2025. Scott Lanoue. - * Copyright (c) 2026. IENGROUND of IENLAB. - * - * 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 zone.ien.hig - -import androidx.compose.animation.core.LinearOutSlowInEasing -import androidx.compose.animation.core.MutableTransitionState -import androidx.compose.animation.core.Spring -import androidx.compose.animation.core.animateFloat -import androidx.compose.animation.core.rememberTransition -import androidx.compose.animation.core.spring -import androidx.compose.animation.core.tween -import androidx.compose.foundation.ScrollState -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.calculateStartPadding -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.CornerBasedShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.ReadOnlyComposable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.graphics.TransformOrigin -import androidx.compose.ui.graphics.drawscope.translate -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.graphics.takeOrElse -import androidx.compose.ui.layout.SubcomposeLayout -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.role -import androidx.compose.ui.semantics.selected -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.DpOffset -import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.IntRect -import androidx.compose.ui.unit.IntSize -import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.dp -import androidx.compose.ui.util.fastCoerceAtMost -import androidx.compose.ui.util.fastForEach -import androidx.compose.ui.util.fastForEachIndexed -import androidx.compose.ui.util.fastMap -import androidx.compose.ui.util.fastSumBy -import androidx.compose.ui.util.lerp -import androidx.compose.ui.window.Popup -import androidx.compose.ui.window.PopupPositionProvider -import androidx.compose.ui.window.PopupProperties -import com.kyant.backdrop.Backdrop -import com.kyant.backdrop.drawBackdrop -import com.kyant.backdrop.effects.blur -import com.kyant.backdrop.effects.lens -import com.kyant.backdrop.effects.vibrancy -import com.kyant.shapes.RoundedRectangle -import com.kyant.shapes.RoundedRectangularShape -import zone.ien.hig.icons.CupertinoIcons -import zone.ien.hig.icons.outlined.Checkmark -import zone.ien.hig.section.CupertinoSectionDefaults -import zone.ien.hig.section.CupertinoSectionTokens -import zone.ien.hig.section.SectionStyle -import zone.ien.hig.theme.BrightSeparatorColor -import zone.ien.hig.theme.CupertinoColors -import zone.ien.hig.theme.CupertinoTheme -import zone.ien.hig.theme.systemGray5 -import zone.ien.hig.theme.systemRed -import zone.ien.hig.utils.InteractiveHighlight -import kotlin.math.abs -import kotlin.math.atan2 -import kotlin.math.cos -import kotlin.math.max -import kotlin.math.min -import kotlin.math.sin -import kotlin.math.tanh - -sealed interface CupertinoMenuScope - -/** - * Cupertino elevated dropdown menu. Usually used for top bar actions. - * - * @see MenuSection - * @see MenuTitle - * @see MenuAction - * @see MenuPickerAction - * @see MenuDivider - * */ -@Composable -@ExperimentalCupertinoApi -actual fun CupertinoDropdownMenu( - expanded: Boolean, - onDismissRequest: () -> Unit, - modifier: Modifier, - offset: DpOffset, - paddingValues: PaddingValues, - containerColor: Color, - width: Dp, - scrollState: ScrollState, - properties: PopupProperties, - backdrop: Backdrop, - content: @Composable CupertinoMenuScope.() -> Unit, -) { - val expandedStates = remember { MutableTransitionState(false) } - expandedStates.targetState = expanded - val safePadding = 32.dp - - if (expandedStates.currentState || expandedStates.targetState) { - var transformOrigin by remember { mutableStateOf(TransformOrigin.Center) } - val density = LocalDensity.current - val popupPositionProvider = DropdownMenuPositionProvider( - contentOffset = offset, - safePadding = safePadding, - verticalMargin = 0.dp, - density = density - ) { parentBounds, menuBounds -> - transformOrigin = calculateTransformOrigin(parentBounds, menuBounds) - } - - Popup( - onDismissRequest = onDismissRequest, - popupPositionProvider = popupPositionProvider, - properties = properties, - ) { - val scope = remember { CupertinoMenuScopeImpl() } - - DropdownMenuContent( - containerColor = containerColor, - expandedStates = expandedStates, - transformOriginState = transformOrigin, - scrollState = scrollState, - modifier = modifier.padding(safePadding), - content = { scope.run { content() } }, - width = width, - paddingValue = paddingValues, - backdrop = backdrop, - ) - } - } -} - -/** - * Plain menu item with manual padding control. - * Usually shouldn't be used directly. - * - * @param minHeight minimum item height - * @param content item content - * - * @see MenuSection - * @see MenuTitle - * @see MenuAction - * @see MenuPickerAction - * @see MenuDivider - * */ -@Composable -fun CupertinoMenuScope.MenuItem( - modifier: Modifier = Modifier, - minHeight: Dp = MinItemHeight, - content: @Composable (padding: PaddingValues) -> Unit, -) { - this as CupertinoMenuScopeImpl - - Box( - modifier = modifier.heightIn(minHeight), - contentAlignment = Alignment.CenterStart, - ) { - content( - MenuPaddingValues.let { - if (!hasPicker) { - it - } else { - it.copy( - start = it.calculateStartPadding(LocalLayoutDirection.current) + SelectorSize, - ) - } - }, - ) - } -} - -/** - * Group of buttons with top [MenuTitle] and bottom [MenuDivider] - * - * @see MenuTitle - * @see MenuDivider - * */ -@Composable -inline fun CupertinoMenuScope.MenuSection( - noinline title: (@Composable () -> Unit)? = null, - content: @Composable CupertinoMenuScope.() -> Unit, -) { - if (title != null) { - MenuTitle(title = title) - } - content() -} - -/** - * Title of the [MenuSection] - * */ -@Composable -fun CupertinoMenuScope.MenuTitle( - modifier: Modifier = Modifier, - title: @Composable () -> Unit, -) { - MenuItem( - modifier = modifier, - minHeight = MinTitleHeight, - ) { - CompositionLocalProvider( - LocalContentColor provides CupertinoTheme.colorScheme.secondaryLabel, - ) { - ProvideTextStyle( - CupertinoTheme.typography.footnote, - ) { - Box( - Modifier - .padding(it), - ) { - title() - } - } - } - } -} - -/** - * Default menu button - - * @param onClick block performed on action click - * @param modifier item modifier - * @param onClickLabel semantics label for the [onClick] action. Should be the same text as in [title] - * @param contentColor color of the item contend. - * Usually [CupertinoColors.systemRed] is used for destructive actions. - * @param icon action trailing icon - * @param caption content before [icon] - * @param title action title - * */ -@Composable -fun CupertinoMenuScope.MenuAction( - onClick: () -> Unit, - modifier: Modifier = Modifier, - onClickLabel: String? = null, - enabled: Boolean = true, - contentColor: Color = CupertinoDropdownMenuDefaults.ContentColor, - icon: (@Composable () -> Unit) = {}, - caption: @Composable () -> Unit = {}, - title: @Composable () -> Unit, -) = ActionWithoutPadding( - onClickLabel = onClickLabel, - modifier = modifier, - onClick = onClick, - enabled = enabled, - contentColor = contentColor, - icon = icon, - caption = caption, -) { - Box( - modifier = Modifier.padding(it), - ) { - title() - } -} - -/** - * Picker action with leading icon ([Checkmark] by default) if selected. - * - * If menu has at least one picker action (no matter selected or not) - * then all menu items will have additional start padding - * - * @param isSelected selection flag. If item is selected, it will have a [selectionIcon] - * @param onClick block performed on action click - * @param modifier item modifier - * @param onClickLabel semantics label for the [onClick] action. Should be the same text as in [title] - * @param contentColor color of the item contend. - * Usually [CupertinoColors.systemRed] is used for destructive actions. - * @param icon action trailing icon - * @param caption content before [icon] - * @param title action title - * */ -@Composable -fun CupertinoMenuScope.MenuPickerAction( - isSelected: Boolean, - onClick: () -> Unit, - modifier: Modifier = Modifier, - onClickLabel: String? = null, - enabled: Boolean = true, - contentColor: Color = CupertinoDropdownMenuDefaults.ContentColor, - selectionIcon: (@Composable () -> Unit) = { CupertinoDropdownMenuDefaults.PickerLeadingIcon() }, - icon: (@Composable () -> Unit) = {}, - caption: @Composable () -> Unit = {}, - title: @Composable () -> Unit, -) { - this as CupertinoMenuScopeImpl - - DisposableEffect(this) { - val prev = hasPicker - hasPicker = true - onDispose { - hasPicker = prev - } - } - - ActionWithoutPadding( - modifier = modifier.semantics(mergeDescendants = true) { - selected = isSelected - role = Role.DropdownList - }, - onClickLabel = onClickLabel, - onClick = onClick, - enabled = enabled, - contentColor = contentColor, - icon = icon, - caption = caption, - title = { pv -> - Box( - contentAlignment = Alignment.CenterStart, - ) { - Box( - modifier = Modifier.size(MinItemHeight), - contentAlignment = Alignment.Center, - ) { - if (isSelected) { - selectionIcon() - } - } - Box( - modifier = Modifier.padding(pv), - ) { - title() - } - } - }, - ) -} - -/** - * Separator for the menu actions groups - * */ -@Composable -fun CupertinoMenuScope.MenuDivider( - modifier: Modifier = Modifier, - color: Color? = null, - height: Dp = DividerHeight, -) = MenuItem( - minHeight = DividerHeight, -) { - Spacer( - modifier = modifier - .height(height) - .fillMaxWidth() - .padding(horizontal = MenuHorizontalMargin) - .background(color ?: CupertinoDropdownMenuDefaults.DividerColor), - ) -} - -@OptIn(ExperimentalCupertinoApi::class) -@Composable -private fun CupertinoMenuScope.ActionWithoutPadding( - onClick: () -> Unit, - modifier: Modifier = Modifier, - onClickLabel: String? = null, - enabled: Boolean = true, - contentColor: Color = Color.Unspecified, - icon: @Composable () -> Unit = {}, - caption: @Composable () -> Unit = {}, - title: @Composable (PaddingValues) -> Unit, -) = MenuItem { - val color = contentColor.takeOrElse { LocalContentColor.current }.let { if (enabled) it else it.copy(alpha = it.alpha / 4f) } - - ProvideTextStyle(CupertinoTheme.typography.callout) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(SplitPadding), - modifier = modifier - .heightIn(min = CupertinoSectionTokens.MinHeight) - .fillMaxWidth() - .padding(8.dp) - .clip(RoundedRectangle(24.dp)) - .clickable( - enabled = enabled, - onClick = onClick, - onClickLabel = onClickLabel, - role = Role.DropdownList, - ) - ) { - CompositionLocalProvider(LocalContentColor provides color) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(CupertinoSectionTokens.SplitPadding), - modifier = Modifier.padding(it.copy(end = 0.dp)) - ) { - caption.invoke() - - Box( - contentAlignment = Alignment.Center, - modifier = Modifier.size(MinItemHeight / 3), - ) { - icon.invoke() - } - } - title(it.copy(start = 0.dp)) - } - } - } -} - -@Composable -private fun DropdownMenuContent( - width: Dp, - containerColor: Color, - expandedStates: MutableTransitionState, - transformOriginState: TransformOrigin, - scrollState: ScrollState, - paddingValue: PaddingValues, - modifier: Modifier = Modifier, - backdrop: Backdrop, - content: @Composable () -> Unit, -) { - // Menu open/close animation. - val transition = rememberTransition(expandedStates, "DropDownMenu") - val animationScope = rememberCoroutineScope() - - val scale by transition.animateFloat( - transitionSpec = { - if (false isTransitioningTo true) { - // Dismissed to expanded - MenuEnterTransition - } else { - // Expanded to dismissed. - MenuExitTransition - } - }, - ) { - if (it) { - // Menu is expanded. - 1f - } else { - // Menu is dismissed. - .1f - } - } - val alpha by transition.animateFloat( - transitionSpec = { - if (false isTransitioningTo true) { - // Dismissed to expanded - MenuEnterTransition - } else { - MenuExitTransition - } - }, - ) { - if (it) { - // Menu is expanded. - 1f - } else { - // Menu is dismissed. - 0f - } - } - val shape = CupertinoDropdownMenuDefaults.Shape - val interactiveHighlight = remember(animationScope) { InteractiveHighlight(animationScope = animationScope) } - - CupertinoSurface( - color = Color.Transparent, - modifier = Modifier - .padding(paddingValue) - .graphicsLayer { - scaleX = scale - scaleY = scale - this.alpha = alpha - transformOrigin = transformOriginState - clip = false - } - .width(width) - ) { - CompositionLocalProvider( - LocalSeparatorColor provides BrightSeparatorColor, - ) { - ProvideTextStyle( - CupertinoTheme.typography.body - ) { - SubcomposeLayout( - modifier = modifier - .drawBackdrop( - backdrop = backdrop, - shape = { shape }, - effects = { - vibrancy() - blur(2.dp.toPx()) - if (shape is RoundedRectangularShape || shape is CornerBasedShape) { - lens(12.dp.toPx(), 24.dp.toPx()) - } - }, - layerBlock = { - val width = this.size.width - val height = this.size.height - - val progress = interactiveHighlight.pressProgress - val scale = lerp(1f, 1f + 4.dp.toPx() / height, progress) - - val maxOffset = this.size.minDimension - val initialDerivative = 0.05f - val offset = interactiveHighlight.offset - - translationX = maxOffset * tanh(initialDerivative * offset.x / maxOffset) - translationY = maxOffset * tanh(initialDerivative * offset.y / maxOffset) - - val maxDragScale = 4.dp.toPx() / height - val offsetAngle = atan2(offset.y, offset.x) - - scaleX = scale + maxDragScale * abs(cos(offsetAngle) * offset.x / this.size.maxDimension) * (width / height).fastCoerceAtMost(1f) - scaleY = scale + maxDragScale * abs(sin(offsetAngle) * offset.y / this.size.maxDimension) * (height / width).fastCoerceAtMost(1f) - }, - onDrawSurface = { - drawRect(containerColor.copy(alpha = 0.95f)) - }, - ) - .fillMaxWidth() - .heightIn(max = MenuMaxHeight) - .verticalScroll(scrollState), - ) { constraints -> - val layoutWidth = constraints.maxWidth - val itemPlaceables = subcompose(CupertinoDropdownMenuSlots.Item, content).fastMap { it.measure(constraints) } - val allPlacements = buildList(itemPlaceables.size * 2) { itemPlaceables.fastForEach { placeable -> add(placeable) } } - val height = allPlacements.fastSumBy { it.height } - - layout(layoutWidth, height) { - var y = 0 - allPlacements.fastForEach { - it.placeRelative(0, y) - y += it.height - } - } - } - } - } - } -} - -private enum class CupertinoDropdownMenuSlots { - Section, - Item, - Separator, -} - -internal fun calculateTransformOrigin( - parentBounds: IntRect, - menuBounds: IntRect, -): TransformOrigin { - val pivotX = - when { - menuBounds.left >= parentBounds.right -> 0f - menuBounds.right <= parentBounds.left -> 1f - menuBounds.width == 0 -> 0f - else -> { - val intersectionCenter = - ( - max(parentBounds.left, menuBounds.left) + - min( - parentBounds.right, - menuBounds.right, - ) - ) / 2 - (intersectionCenter - menuBounds.left).toFloat() / menuBounds.width - } - } - val pivotY = - when { - menuBounds.top >= parentBounds.bottom -> 0f - menuBounds.bottom <= parentBounds.top -> 1f - menuBounds.height == 0 -> 0f - else -> { - val intersectionCenter = - ( - max(parentBounds.top, menuBounds.top) + - min(parentBounds.bottom, menuBounds.bottom) - ) / 2 - (intersectionCenter - menuBounds.top).toFloat() / menuBounds.height - } - } - return TransformOrigin(pivotX, pivotY) -} - -@Immutable -internal data class DropdownMenuPositionProvider( - val contentOffset: DpOffset, - val density: Density, - val safePadding: Dp = 0.dp, - val verticalMargin: Dp = MenuVerticalMargin, - val onPositionCalculated: (IntRect, IntRect) -> Unit = { _, _ -> }, -) : PopupPositionProvider { - override fun calculatePosition( - anchorBounds: IntRect, - windowSize: IntSize, - layoutDirection: LayoutDirection, - popupContentSize: IntSize, - ): IntOffset { - val verticalMargin = with(density) { verticalMargin.roundToPx() } - val safePaddingPx = with(density) { safePadding.roundToPx() } - val contentOffsetX = with(density) { - contentOffset.x.roundToPx() * (if (layoutDirection == LayoutDirection.Ltr) 1 else -1) - } - val contentOffsetY = with(density) { contentOffset.y.roundToPx() } - - // popupContentSize는 이미 safePadding * 2를 포함한 크기 - // 실제 메뉴 크기 = popupContentSize - safePadding * 2 - // Popup 자체는 safePadding만큼 앞당겨서 배치해야 내부 컨텐츠가 anchor에 붙음 - val leftToAnchorLeft = anchorBounds.left + contentOffsetX - safePaddingPx - val rightToAnchorRight = anchorBounds.right - popupContentSize.width + contentOffsetX + safePaddingPx - val rightToWindowRight = windowSize.width - popupContentSize.width - val leftToWindowLeft = 0 - - val x = if (layoutDirection == LayoutDirection.Ltr) { - sequenceOf( - leftToAnchorLeft, - rightToAnchorRight, - if (anchorBounds.left >= 0) rightToWindowRight else leftToWindowLeft, - ) - } else { - sequenceOf( - rightToAnchorRight, - leftToAnchorLeft, - if (anchorBounds.right <= windowSize.width) leftToWindowLeft else rightToWindowRight, - ) - }.firstOrNull { - it >= -safePaddingPx && it + popupContentSize.width <= windowSize.width + safePaddingPx - } ?: rightToAnchorRight - -// val topToAnchorTop = maxOf( -// anchorBounds.top + contentOffsetY - safePaddingPx, -// verticalMargin - safePaddingPx -// ) - val topToAnchorTop = anchorBounds.top + contentOffsetY - safePaddingPx - val bottomToAnchorBottom = anchorBounds.bottom - popupContentSize.height + contentOffsetY + safePaddingPx - val bottomToWindowBottom = windowSize.height - popupContentSize.height - verticalMargin + safePaddingPx - -// val y = sequenceOf( -// topToAnchorTop, -// bottomToAnchorBottom, -// bottomToWindowBottom, -// ).firstOrNull { -// it + safePaddingPx >= verticalMargin && -// it + popupContentSize.height - safePaddingPx <= windowSize.height - verticalMargin -// } ?: bottomToAnchorBottom - - val y = sequenceOf( - topToAnchorTop, - bottomToAnchorBottom, - bottomToWindowBottom, - ).firstOrNull { - it + popupContentSize.height - safePaddingPx <= windowSize.height - verticalMargin - // 👆 상단 조건(verticalMargin 체크) 제거 - } ?: bottomToAnchorBottom - - onPositionCalculated( - anchorBounds, - IntRect(x, y, x + popupContentSize.width, y + popupContentSize.height), - ) - return IntOffset(x, y) - } -} - -internal class CupertinoMenuScopeImpl : CupertinoMenuScope { - var hasPicker: Boolean by mutableStateOf(false) -} - -private val MenuMaxHeight: Dp = 600.dp -private val SelectorSize = 20.dp - -private val MenuHorizontalMargin = 24.dp -private val MenuVerticalMargin = 24.dp -private val MinItemHeight = 48.dp -private val DividerHeight = 1.dp -private val MinTitleHeight = 32.dp -private val SplitPadding = 16.dp -private val MenuPaddingValues = PaddingValues(16.dp, 8.dp) - -private val MenuEnterTransition = - spring( - dampingRatio = .825f, - stiffness = Spring.StiffnessMediumLow, - ) - -private val MenuExitTransition = - tween( - durationMillis = 350, - easing = LinearOutSlowInEasing, - ) From 297ea9f5318c8b21e91c1770e7f6034d0577d102 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Sun, 29 Mar 2026 20:39:43 +0900 Subject: [PATCH 012/109] =?UTF-8?q?=F0=9F=94=A7chore(version):=20=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EB=B2=84=EC=A0=84?= =?UTF-8?q?=EC=9D=84=201.0.2-alpha07=EB=A1=9C=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gradle/libs.versions.toml 파일의 lib-version-name 버전을 1.0.2-alpha02에서 1.0.2-alpha07로 업데이트합니다. --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 469d22c8..de15aa14 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "9.0.0" kotlin = "2.3.20" -lib-version-name = "1.0.2-alpha02" +lib-version-name = "1.0.2-alpha07" # plugin compose-plugin = "1.10.3" From 491f572903350fd78d661aa3ad71938417a51b79 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Sun, 29 Mar 2026 20:42:03 +0900 Subject: [PATCH 013/109] =?UTF-8?q?=F0=9F=93=A6=20chore(deps):=20backdrop?= =?UTF-8?q?=20=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EA=B7=B8?= =?UTF-8?q?=EB=A3=B9=20=EB=B0=8F=20=EB=B2=84=EC=A0=84=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - backdrop 라이브러리의 그룹을 `io.github.kyant0`에서 `zone.ien.backdrop`으로 변경 - 버전을 `2.0.0-alpha04+dev1`에서 `2.0.0-alpha04`로 업데이트 --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index de15aa14..90ea0655 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ compose-navigation3 = "1.0.0-alpha06" # ui activity-compose = "1.13.0" lifecycle = "2.10.0" -backdrop = "2.0.0-alpha04+dev1" +backdrop = "2.0.0-alpha04" capsule = "1.2.0" haze = "1.7.2" @@ -43,7 +43,7 @@ compose-ui-util = { group = "org.jetbrains.compose.ui", name = "ui-util", versio compose-resources = { group = "org.jetbrains.compose.components", name = "components-resources", version.ref = "compose-plugin" } compose-navigation3 = { group = "org.jetbrains.androidx.navigation3", name = "navigation3-ui", version.ref = "compose-navigation3" } -backdrop = { group = "io.github.kyant0", name = "backdrop", version.ref = "backdrop"} +backdrop = { group = "zone.ien.backdrop", name = "backdrop", version.ref = "backdrop"} capsule = { group = "io.github.kyant0", name = "shapes", version.ref = "capsule"} haze = { group = "dev.chrisbanes.haze", name = "haze", version.ref = "haze" } From 8e323077a3ef378e6293c21b3435dedc42becc0e Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Sun, 29 Mar 2026 21:03:03 +0900 Subject: [PATCH 014/109] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(hig):=20M?= =?UTF-8?q?enuAction=EC=9D=98=20icon=20=ED=8C=8C=EB=9D=BC=EB=AF=B8?= =?UTF-8?q?=ED=84=B0=EB=A5=BC=20leadingIcon=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MenuAction의 icon 파라미터 이름을 leadingIcon으로 변경하여 명확성 개선 - CupertinoWidgetsScreen, TestScreen 및 네이티브 구현부 코드 반영 - 라이브러리 버전을 1.0.2-alpha08으로 업데이트 --- .../kotlin/cupertino/CupertinoWidgetsScreen.kt | 12 ++++++------ .../src/commonMain/kotlin/test/TestScreen.kt | 6 +++--- gradle/libs.versions.toml | 2 +- .../ien/hig/CupertinoDropdownMenuNative.nonIos.kt | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt index 6affacdf..9e5e166c 100644 --- a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt +++ b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt @@ -1803,7 +1803,7 @@ private fun DropdownExample( onClick = { dropdownVisible = false }, - icon = { + leadingIcon = { CupertinoIcon( imageVector = CupertinoIcons.Default.SquareAndArrowUp, contentDescription = null @@ -1817,7 +1817,7 @@ private fun DropdownExample( onClick = { dropdownVisible = false }, - icon = { + leadingIcon = { CupertinoIcon( imageVector = CupertinoIcons.Default.Bookmark, contentDescription = null @@ -1834,7 +1834,7 @@ private fun DropdownExample( }, contentColor = red, - icon = { + leadingIcon = { CupertinoIcon( imageVector = CupertinoIcons.Default.Trash, contentDescription = null @@ -1905,7 +1905,7 @@ private fun DropdownExample2( onClick = { dropdownVisible = false }, - icon = { + leadingIcon = { CupertinoIcon( imageVector = CupertinoIcons.Default.SquareAndArrowUp, contentDescription = null @@ -1919,7 +1919,7 @@ private fun DropdownExample2( onClick = { dropdownVisible = false }, - icon = { + leadingIcon = { CupertinoIcon( imageVector = CupertinoIcons.Default.Bookmark, contentDescription = null @@ -1936,7 +1936,7 @@ private fun DropdownExample2( }, contentColor = red, - icon = { + leadingIcon = { CupertinoIcon( imageVector = CupertinoIcons.Default.Trash, contentDescription = null diff --git a/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt b/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt index d4fca4d1..116e126e 100644 --- a/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt +++ b/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt @@ -276,7 +276,7 @@ fun TestScreen( ) { MenuAction( onClick = { expanded = false }, - icon = { + leadingIcon = { CupertinoIcon( imageVector = CupertinoIcons.Default.CheckmarkCircle, contentDescription = null @@ -287,7 +287,7 @@ fun TestScreen( } MenuAction( onClick = { expanded = false }, - icon = { + leadingIcon = { CupertinoIcon( imageVector = CupertinoIcons.Default.Pin, contentDescription = null @@ -298,7 +298,7 @@ fun TestScreen( } MenuAction( onClick = { expanded = false }, - icon = { + leadingIcon = { CupertinoIcon( imageVector = CupertinoIcons.Default.PersonCropCircle, contentDescription = null diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 48578b2d..2d2ec97d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "9.0.0" kotlin = "2.3.20" -lib-version-name = "1.0.2-alpha07" +lib-version-name = "1.0.2-alpha08" # plugin compose-plugin = "1.10.3" diff --git a/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.nonIos.kt b/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.nonIos.kt index 3c4ed65c..9e580817 100644 --- a/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.nonIos.kt +++ b/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.nonIos.kt @@ -45,7 +45,7 @@ actual fun CupertinoDropdownMenuNative( MenuAction( onClick = it.onClick, enabled = !it.isDisabled, - icon = { + leadingIcon = { it.icon?.let { icon -> CupertinoIcon( painter = icon, @@ -67,7 +67,7 @@ actual fun CupertinoDropdownMenuNative( MenuAction( onClick = it.onClick, enabled = !it.isDisabled, - icon = { + leadingIcon = { it.icon?.let { icon -> CupertinoIcon( painter = icon, From b5b7e95ac1d186b62057fdb57a84358a02493955 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Mon, 30 Mar 2026 16:01:07 +0900 Subject: [PATCH 015/109] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(hig):=20C?= =?UTF-8?q?upertinoMenuItemData=EC=9D=98=20isDisabled=EB=A5=BC=20enabled?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `CupertinoMenuItemData`에서 부정형 변수인 `isDisabled`를 긍정형인 `enabled`로 변경하여 직관성 개선 - `nonIosMain` 및 `iosMain` 소스 세트의 관련 로직을 새로운 변수명에 맞게 업데이트 - iOS 환경에서 `toUIAction` 변환 시 `OptIn` 어노테이션 추가 및 불필요한 주석 제거 - Foundation 및 UIKit 관련 미사용 임포트 구문 정리 및 추가 --- .../zone/ien/hig/CupertinoDropdownMenuNative.kt | 2 +- .../zone/ien/hig/CupertinoDropdownMenuNative.ios.kt | 12 ++++++++++-- .../ien/hig/CupertinoDropdownMenuNative.nonIos.kt | 4 ++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt index 8e0bbb4a..68d5ef44 100644 --- a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt +++ b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt @@ -16,9 +16,9 @@ import com.kyant.backdrop.Backdrop data class CupertinoMenuItemData( val title: String, + val enabled: Boolean = true, val icon: Painter? = null, val isDestructive: Boolean = false, - val isDisabled: Boolean = false, val onClick: () -> Unit, ) diff --git a/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt index 1f89cb8c..cb4d6bee 100644 --- a/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt +++ b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt @@ -26,6 +26,13 @@ import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.cinterop.useContents import platform.CoreGraphics.CGPointMake import platform.CoreGraphics.CGRectMake +import platform.Foundation.NSAttributedString +import platform.Foundation.NSMakeRange +import platform.Foundation.NSMutableAttributedString +import platform.Foundation.NSMutableAttributedStringMeta +import platform.Foundation.create +import platform.Foundation.setAttributedString +import platform.UIKit.NSForegroundColorAttributeName import platform.UIKit.UIAction import platform.UIKit.UIButton import platform.UIKit.UIButtonTypePlain @@ -42,6 +49,7 @@ import platform.UIKit.UIMenuElement import platform.UIKit.UIMenuElementAttributesDestructive import platform.UIKit.UIMenuElementAttributesDisabled import platform.UIKit.UIMenuOptionsDisplayInline +import platform.UIKit.secondaryLabelColor import platform.UIKit.touchesBegan import platform.darwin.NSObject @@ -174,17 +182,17 @@ internal class CupertinoDropdownMenuDelegate : NSObject() { } } +@OptIn(ExperimentalForeignApi::class, BetaInteropApi::class) private fun CupertinoMenuItemData.toUIAction(): UIAction { val action = UIAction.actionWithTitle( title = title, -// image = UIImage.systemImageNamed("chevron.left"), image = icon?.toUIImage()?.resized(20.0), identifier = null, ) { _ -> onClick() } var attributes: ULong = 0u if (isDestructive) attributes = attributes or UIMenuElementAttributesDestructive - if (isDisabled) attributes = attributes or UIMenuElementAttributesDisabled + if (!enabled) attributes = attributes or UIMenuElementAttributesDisabled action.attributes = attributes return action } diff --git a/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.nonIos.kt b/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.nonIos.kt index 9e580817..b2473c1f 100644 --- a/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.nonIos.kt +++ b/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.nonIos.kt @@ -44,7 +44,7 @@ actual fun CupertinoDropdownMenuNative( items.fastForEach { MenuAction( onClick = it.onClick, - enabled = !it.isDisabled, + enabled = it.enabled, leadingIcon = { it.icon?.let { icon -> CupertinoIcon( @@ -66,7 +66,7 @@ actual fun CupertinoDropdownMenuNative( items.fastForEach { MenuAction( onClick = it.onClick, - enabled = !it.isDisabled, + enabled = it.enabled, leadingIcon = { it.icon?.let { icon -> CupertinoIcon( From e0eaa7b3b5e2d3377b722139ee1005907fe88555 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Mon, 30 Mar 2026 22:16:55 +0900 Subject: [PATCH 016/109] =?UTF-8?q?=E2=9C=A8=20feat(adaptive):=20AdaptiveN?= =?UTF-8?q?avigationBarNative=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20iOS=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=ED=8B=B0=EB=B8=8C=20=ED=83=AD=EB=B0=94=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - iOS에서 네이티브 `UITabBar`를 사용하는 `AdaptiveNavigationBarNative` 컴포넌트 추가 - 네이티브 탭바의 높이를 측정하고 `LocalIosTabBarPadding`을 통해 `AdaptiveScaffold`에 전달하는 로직 구현 - 탭바 높이 계산 시 레이아웃 점프를 방지하기 위한 `IosBarHeightCache` 캐싱 메커니즘 도입 - `CupertinoWidgetsScreen` 예제에 네이티브 탭바 테스트 모드 추가 - `hig-native` 모듈에 `material3` 의존성 추가 및 공통 인터페이스 정의 --- .../composeApp/src/commonMain/kotlin/App.kt | 10 +- .../cupertino/CupertinoWidgetsScreen.kt | 72 +++++--- .../ien/hig/adaptive/AdaptiveNavigationBar.kt | 67 +++++++ hig-native/build.gradle.kts | 1 + .../ien/hig/CupertinoNavigationBarNative.kt | 26 +++ .../hig/CupertinoNavigationBarNative.ios.kt | 170 ++++++++++++++++++ .../ien/hig/navigation/IosBarHeightCache.kt | 44 +++++ .../hig/navigation/LocalIosTabBarPadding.kt | 20 +++ .../zone/ien/hig/navigation/TabBarManager.kt | 77 ++++++++ .../CupertinoNavigationBarNative.nonIos.kt | 46 +++++ 10 files changed, 503 insertions(+), 30 deletions(-) create mode 100644 hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBarNative.kt create mode 100644 hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoNavigationBarNative.ios.kt create mode 100644 hig-native/src/iosMain/kotlin/zone/ien/hig/navigation/IosBarHeightCache.kt create mode 100644 hig-native/src/iosMain/kotlin/zone/ien/hig/navigation/LocalIosTabBarPadding.kt create mode 100644 hig-native/src/iosMain/kotlin/zone/ien/hig/navigation/TabBarManager.kt create mode 100644 hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoNavigationBarNative.nonIos.kt diff --git a/example/composeApp/src/commonMain/kotlin/App.kt b/example/composeApp/src/commonMain/kotlin/App.kt index 858a6a5b..fd071327 100644 --- a/example/composeApp/src/commonMain/kotlin/App.kt +++ b/example/composeApp/src/commonMain/kotlin/App.kt @@ -59,11 +59,11 @@ fun App() { lightAccent else darkAccent, useDarkTheme = isDark ) { - TestScreen() -// RootNavigationGraph( -// backStack = backStack, -// viewModel = viewModel -// ) +// TestScreen() + RootNavigationGraph( + backStack = backStack, + viewModel = viewModel + ) } } } diff --git a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt index 9e5e166c..a11ddb6c 100644 --- a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt +++ b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt @@ -81,6 +81,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalLayoutDirection @@ -124,6 +125,7 @@ import zone.ien.hig.CupertinoLiquidButtonDefaults import zone.ien.hig.CupertinoLiquidIconButton import zone.ien.hig.CupertinoMenuItemData import zone.ien.hig.CupertinoMenuSectionData +import zone.ien.hig.CupertinoNavigationBarItemData import zone.ien.hig.CupertinoNavigationTitle import zone.ien.hig.CupertinoPickerState import zone.ien.hig.CupertinoRangeSlider @@ -148,6 +150,7 @@ import zone.ien.hig.MenuSection import zone.ien.hig.PresentationStyle import zone.ien.hig.adaptive.AdaptiveNavigationBar import zone.ien.hig.adaptive.AdaptiveNavigationBarItem +import zone.ien.hig.adaptive.AdaptiveNavigationBarNative import zone.ien.hig.adaptive.ExperimentalAdaptiveApi import zone.ien.hig.adaptive.Theme import zone.ien.hig.adaptive.icons.AdaptiveIcons @@ -220,7 +223,7 @@ private enum class PickerTab { fun CupertinoWidgetsScreen( uiState: RootUiState, onItemValueChanged: (RootDetails) -> Unit, - onNavigate: (NavKey) -> Unit + onNavigate: (NavKey) -> Unit, ) { val scrollState = rememberScrollState() @@ -273,7 +276,8 @@ fun CupertinoWidgetsScreen( }, bottomBar = { BottomBarSample( - backdrop = backdrop + backdrop = backdrop, + isNative = nativePickers.value ) }, ) { pv -> @@ -847,7 +851,8 @@ private fun TopBarSample( @OptIn(ExperimentalAdaptiveApi::class) @Composable private fun BottomBarSample( - backdrop: LayerBackdrop + backdrop: LayerBackdrop, + isNative: Boolean ) { var tab by remember { mutableStateOf(0) } val content = listOf( @@ -856,28 +861,45 @@ private fun BottomBarSample( "Settings" to AdaptiveIcons.Outlined.Settings, ) - AdaptiveNavigationBar( - selectedTabIndex = { tab }, - onTabSelected = { tab = it }, - tabsCount = 3, - adaptation = { - cupertino { this.backdrop = backdrop } - }, - ) { - content.forEachIndexed { index, pair -> - AdaptiveNavigationBarItem( - index = index, - onClick = { tab = index }, - icon = { - CupertinoIcon( - imageVector = pair.second, - contentDescription = pair.first, - ) - }, - label = { - Text(pair.first) - }, - ) + if (isNative) { + AdaptiveNavigationBarNative( + selectedTabIndex = { tab }, + onTabSelected = { tab = it }, + adaptation = { + cupertino { this.backdrop = backdrop } + }, + items = content.mapIndexed { index, item -> + CupertinoNavigationBarItemData( + onClick = { tab = index }, + icon = rememberVectorPainter(item.second), + label = item.first + ) + } + ) + } else { + AdaptiveNavigationBar( + selectedTabIndex = { tab }, + onTabSelected = { tab = it }, + tabsCount = 3, + adaptation = { + cupertino { this.backdrop = backdrop } + }, + ) { + content.forEachIndexed { index, pair -> + AdaptiveNavigationBarItem( + index = index, + onClick = { tab = index }, + icon = { + CupertinoIcon( + imageVector = pair.second, + contentDescription = pair.first, + ) + }, + label = { + Text(pair.first) + }, + ) + } } } } diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveNavigationBar.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveNavigationBar.kt index cef965ce..bb56396b 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveNavigationBar.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveNavigationBar.kt @@ -23,11 +23,13 @@ package zone.ien.hig.adaptive import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.material3.Icon import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBarDefaults import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.NavigationBarItemColors import androidx.compose.material3.NavigationBarItemDefaults +import androidx.compose.material3.Text import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -47,6 +49,8 @@ import zone.ien.hig.CupertinoNavigationBar import zone.ien.hig.CupertinoNavigationBarColors import zone.ien.hig.CupertinoNavigationBarDefaults import zone.ien.hig.CupertinoNavigationBarItem +import zone.ien.hig.CupertinoNavigationBarItemData +import zone.ien.hig.CupertinoNavigationBarNative import zone.ien.hig.ExperimentalCupertinoApi internal data class NavigationBarState( @@ -103,6 +107,69 @@ fun AdaptiveNavigationBar( } } +@OptIn(ExperimentalAdaptiveApi::class, ExperimentalCupertinoApi::class) +@Composable +fun AdaptiveNavigationBarNative( + modifier: Modifier = Modifier, + selectedTabIndex: () -> Int, + onTabSelected: (index: Int) -> Unit, + adaptation: AdaptationScope.() -> Unit = {}, + items: List +) { + CompositionLocalProvider( + LocalNavigationBarState provides NavigationBarState( + selectedTabIndex = selectedTabIndex, + onTabSelected = onTabSelected, + ) + ) { + AdaptiveWidget( + adaptation = remember { + NavigationBarAdaptation() + }, + adaptationScope = adaptation, + cupertino = { + CupertinoNavigationBarNative( + modifier = modifier, + colors = it.colors, + windowInsets = it.windowInsets, + backdrop = it.backdrop, + selectedTabIndex = selectedTabIndex, + onTabSelected = onTabSelected, + items = items + ) + }, + material = { + NavigationBar( + modifier = modifier, + containerColor = it.containerColor, + contentColor = it.contentColor, + tonalElevation = it.tonalElevation, + windowInsets = it.windowInsets, + content = { + items.forEachIndexed { index, item -> + val selected = index == selectedTabIndex() + NavigationBarItem( + selected = selected, + onClick = item.onClick, + icon = { + Icon( + painter = + if (selected && item.selectedIcon != null) item.selectedIcon!! + else item.icon + , + contentDescription = item.label, + ) + }, + label = { Text(text = item.label) }, + ) + } + } + ) + } + ) + } +} + @OptIn(ExperimentalCupertinoApi::class, ExperimentalAdaptiveApi::class) @Composable fun RowScope.AdaptiveNavigationBarItem( diff --git a/hig-native/build.gradle.kts b/hig-native/build.gradle.kts index 33f8d695..f3c45144 100644 --- a/hig-native/build.gradle.kts +++ b/hig-native/build.gradle.kts @@ -32,6 +32,7 @@ kotlin { implementation(projects.hig) implementation(libs.compose.runtime) implementation(libs.compose.foundation) + implementation(libs.compose.material3) implementation(libs.capsule) implementation(libs.backdrop) } diff --git a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBarNative.kt b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBarNative.kt new file mode 100644 index 00000000..9b323d70 --- /dev/null +++ b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBarNative.kt @@ -0,0 +1,26 @@ +package zone.ien.hig + +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.Painter +import com.kyant.backdrop.backdrops.LayerBackdrop + +data class CupertinoNavigationBarItemData( + val onClick: () -> Unit, + val icon: Painter, + val selectedIcon: Painter? = null, + val label: String +) + +@OptIn(ExperimentalCupertinoApi::class) +@Composable +expect fun CupertinoNavigationBarNative( + modifier: Modifier = Modifier, + colors: CupertinoNavigationBarColors = CupertinoNavigationBarDefaults.colors(), + windowInsets: WindowInsets = CupertinoNavigationBarDefaults.windowInsets, + backdrop: LayerBackdrop, + selectedTabIndex: () -> Int, + onTabSelected: (index: Int) -> Unit, + items: List +) \ No newline at end of file diff --git a/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoNavigationBarNative.ios.kt b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoNavigationBarNative.ios.kt new file mode 100644 index 00000000..4edb9b97 --- /dev/null +++ b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoNavigationBarNative.ios.kt @@ -0,0 +1,170 @@ +package zone.ien.hig + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.setValue +import androidx.compose.runtime.withFrameMillis +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.layout.onPlaced +import androidx.compose.ui.layout.positionInRoot +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.uikit.LocalUIViewController +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp +import com.kyant.backdrop.backdrops.LayerBackdrop +import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.cinterop.useContents +import platform.UIKit.NSLayoutConstraint +import platform.UIKit.UITabBar +import zone.ien.hig.navigation.IosBarHeightCache +import zone.ien.hig.navigation.LocalIosTabBarPadding +import zone.ien.hig.navigation.TabBarManager + +@OptIn(ExperimentalCupertinoApi::class, ExperimentalForeignApi::class) +@Composable +actual fun CupertinoNavigationBarNative( + modifier: Modifier, + colors: CupertinoNavigationBarColors, + windowInsets: WindowInsets, + backdrop: LayerBackdrop, + selectedTabIndex: () -> Int, + onTabSelected: (index: Int) -> Unit, + items: List +) { + val density = LocalDensity.current + val viewController = LocalUIViewController.current + + // Write the measured tab bar height to the CompositionLocal so AdaptiveScaffold + // can automatically adjust content padding on iOS. + val iosTabBarPaddingState = LocalIosTabBarPadding.current + + val tabBarView = remember { + UITabBar().apply { + translatesAutoresizingMaskIntoConstraints = false + } + } + val isLiquidGlassEnabled = true + + val onItemSelectedState by rememberUpdatedState(onTabSelected) + + val tabBarManager = remember(tabBarView) { + TabBarManager( + tabBar = tabBarView, + onItemSelected = { onItemSelectedState(it) }, + ) + } + + LaunchedEffect(items, selectedTabIndex) { + tabBarManager.setItems(items, selectedTabIndex()) + } + + val initialSafeAreaBottom = remember { 0.dp } + val cachedTabBarHeight = remember { IosBarHeightCache.lastTabBarHeight } + + var topLeft by remember { mutableStateOf(DpOffset.Zero) } + var positionInRoot by remember { mutableStateOf(DpOffset.Zero) } + var tabBarWidth by remember { mutableStateOf(0.dp) } + var tabBarHeight by remember { + val tabBarHeight = initialSafeAreaBottom + cachedTabBarHeight + if (tabBarHeight.value > 0f) { + iosTabBarPaddingState.value = PaddingValues(bottom = tabBarHeight) + } + mutableStateOf(tabBarHeight) + } + + DisposableEffect(tabBarView, viewController) { + viewController.view.addSubview(tabBarView) + + NSLayoutConstraint.activateConstraints( + listOf( + tabBarView.leadingAnchor.constraintEqualToAnchor(viewController.view.leadingAnchor), + tabBarView.trailingAnchor.constraintEqualToAnchor(viewController.view.trailingAnchor), + tabBarView.bottomAnchor.constraintEqualToAnchor( + if (isLiquidGlassEnabled) + viewController.view.bottomAnchor + else + viewController.view.safeAreaLayoutGuide.bottomAnchor + ), + ) + ) + + onDispose { + tabBarView.removeFromSuperview() + } + } + + LaunchedEffect(Unit) { + var tabBarHeightConsistencyCounter = 0 + + while (true) { + tabBarView.frame.useContents { + topLeft = DpOffset( + x = origin.x.dp, + y = origin.y.dp, + ) + tabBarWidth = size.width.dp + val safeAreaBottom = + if (isLiquidGlassEnabled) + 0.dp + else + viewController.view.safeAreaInsets.useContents { bottom.dp } + val newTabBarHeight = size.height.dp + safeAreaBottom + + IosBarHeightCache.updateTabBarHeight(size.height.dp) + + if (tabBarHeight != newTabBarHeight) { + tabBarHeight = newTabBarHeight + tabBarHeightConsistencyCounter = 0 + + if (newTabBarHeight.value > 0f) { + iosTabBarPaddingState.value = PaddingValues(bottom = newTabBarHeight) + } + } else { + tabBarHeightConsistencyCounter++ + } + } + + if (tabBarHeight.value > 0f && tabBarHeightConsistencyCounter > 6) + break + + withFrameMillis { } + } + } + + // Clean up the padding when this composable leaves the composition + DisposableEffect(Unit) { + onDispose { + iosTabBarPaddingState.value = PaddingValues() + } + } + + Box( + modifier = Modifier + .onPlaced { + val positionInRootPx = it.positionInRoot() + positionInRoot = with(density) { + DpOffset( + x = positionInRootPx.x.toDp(), + y = positionInRootPx.y.toDp(), + ) + } + } + .graphicsLayer { + translationX = (topLeft.x - positionInRoot.x).toPx() + translationY = (topLeft.y - positionInRoot.y).toPx() + } + .width(tabBarWidth) + .height(tabBarHeight) + ) +} \ No newline at end of file diff --git a/hig-native/src/iosMain/kotlin/zone/ien/hig/navigation/IosBarHeightCache.kt b/hig-native/src/iosMain/kotlin/zone/ien/hig/navigation/IosBarHeightCache.kt new file mode 100644 index 00000000..fb3bc2cb --- /dev/null +++ b/hig-native/src/iosMain/kotlin/zone/ien/hig/navigation/IosBarHeightCache.kt @@ -0,0 +1,44 @@ +package zone.ien.hig.navigation + +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +/** + * Simple in-memory cache for the last measured iOS native bar heights. + * + * When [AdaptiveTopBar] or [AdaptiveNavigationBar] measures the actual native bar height, + * it writes the value here. On subsequent compositions (e.g. navigating to a new screen), + * the cached value is used as the initial estimate instead of the hardcoded default, + * reducing the chance of a visible layout jump. + */ +internal object IosBarHeightCache { + private val isLiquidGlassEnabled = true + private val DEFAULT_NAV_BAR_HEIGHT = + if (isLiquidGlassEnabled) + 54.dp + else + 44.dp + private val DEFAULT_TAB_BAR_HEIGHT = + if (isLiquidGlassEnabled) + 83.dp + else + 49.dp + + var lastNavBarHeight: Dp = DEFAULT_NAV_BAR_HEIGHT + private set + + var lastTabBarHeight: Dp = DEFAULT_TAB_BAR_HEIGHT + private set + + fun updateNavBarHeight(height: Dp) { + if (height.value > 0f) { + lastNavBarHeight = height + } + } + + fun updateTabBarHeight(height: Dp) { + if (height.value > 0f) { + lastTabBarHeight = height + } + } +} \ No newline at end of file diff --git a/hig-native/src/iosMain/kotlin/zone/ien/hig/navigation/LocalIosTabBarPadding.kt b/hig-native/src/iosMain/kotlin/zone/ien/hig/navigation/LocalIosTabBarPadding.kt new file mode 100644 index 00000000..343158b2 --- /dev/null +++ b/hig-native/src/iosMain/kotlin/zone/ien/hig/navigation/LocalIosTabBarPadding.kt @@ -0,0 +1,20 @@ +package zone.ien.hig.navigation + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.mutableStateOf + +/** + * Internal CompositionLocal used to communicate the native iOS UITabBar height + * from [AdaptiveNavigationBar] to [AdaptiveScaffold]. + * + * When [AdaptiveNavigationBar] is used inside an [AdaptiveScaffold]'s `bottomBar`, + * it measures the native UITabBar height and writes it to this local. + * The [AdaptiveScaffold] reads this value and adds it to the bottom padding + * passed to the `content` lambda, so consumers don't need to handle iOS tab bar + * insets manually. + */ +internal val LocalIosTabBarPadding = compositionLocalOf> { + mutableStateOf(PaddingValues()) +} diff --git a/hig-native/src/iosMain/kotlin/zone/ien/hig/navigation/TabBarManager.kt b/hig-native/src/iosMain/kotlin/zone/ien/hig/navigation/TabBarManager.kt new file mode 100644 index 00000000..b354ff8c --- /dev/null +++ b/hig-native/src/iosMain/kotlin/zone/ien/hig/navigation/TabBarManager.kt @@ -0,0 +1,77 @@ +package zone.ien.hig.navigation + +import kotlinx.cinterop.ExperimentalForeignApi +import platform.UIKit.UITabBar +import platform.UIKit.UITabBarDelegateProtocol +import platform.UIKit.UITabBarItem +import platform.darwin.NSObject +import zone.ien.hig.CupertinoNavigationBarItemData +import zone.ien.hig.resized +import zone.ien.hig.toUIImage + +/** + * Calf code + * com.mohamedrejeb.calf.ui.navigation + */ + +/** + * A helper class that manages a native iOS UITabBar, handling item setup and selection. + * + * @param tabBar The native UITabBar instance to manage. + * @param onItemSelected Callback invoked when a tab bar item is selected, with the item index. + */ +@OptIn(ExperimentalForeignApi::class) +internal class TabBarManager( + private val tabBar: UITabBar, + onItemSelected: (Int) -> Unit, +) { + private val uiTabBarDelegate = UITabBarDelegate( + onItemSelected = onItemSelected, + ) + + private var currentItems = emptyList() + + init { + tabBar.delegate = uiTabBarDelegate + } + + fun setItems( + items: List, + selectedIndex: Int, + ) { + if (items.isEmpty()) return + + if (items != currentItems) { + val uiKitItems = items.mapIndexed { index, item -> + item.toUITabBarItem(index.toLong()) + } + tabBar.setItems(uiKitItems) + currentItems = items + val safeSelectedIndex = selectedIndex.coerceIn(uiKitItems.indices) + tabBar.selectedItem = uiKitItems[safeSelectedIndex] + } else { + val uiKitItems = tabBar.items?.filterIsInstance() ?: return + val safeSelectedIndex = selectedIndex.coerceIn(uiKitItems.indices) + tabBar.selectedItem = uiKitItems[safeSelectedIndex] + } + } +} + +private fun CupertinoNavigationBarItemData.toUITabBarItem(tag: Long): UITabBarItem { + val item = UITabBarItem( + title = label, + image = icon.toUIImage().resized(20.0), + tag = tag, + ) + selectedIcon?.toUIImage()?.let { item.selectedImage = it } + return item +} + +internal class UITabBarDelegate( + private val onItemSelected: (Int) -> Unit, +) : NSObject(), UITabBarDelegateProtocol { + override fun tabBar(tabBar: UITabBar, didSelectItem: UITabBarItem) { + tabBar.selectedItem = didSelectItem + onItemSelected(didSelectItem.tag.toInt()) + } +} diff --git a/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoNavigationBarNative.nonIos.kt b/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoNavigationBarNative.nonIos.kt new file mode 100644 index 00000000..4e169b2b --- /dev/null +++ b/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoNavigationBarNative.nonIos.kt @@ -0,0 +1,46 @@ +package zone.ien.hig + +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.kyant.backdrop.backdrops.LayerBackdrop + +@OptIn(markerClass = [ExperimentalCupertinoApi::class]) +@Composable +actual fun CupertinoNavigationBarNative( + modifier: Modifier, + colors: CupertinoNavigationBarColors, + windowInsets: WindowInsets, + backdrop: LayerBackdrop, + selectedTabIndex: () -> Int, + onTabSelected: (index: Int) -> Unit, + items: List +) { + CupertinoNavigationBar( + modifier = modifier, + colors = colors, + windowInsets = windowInsets, + backdrop = backdrop, + selectedTabIndex = selectedTabIndex, + onTabSelected = onTabSelected, + tabsCount = items.size + ) { + items.forEachIndexed { index, item -> + CupertinoNavigationBarItem( + onClick = item.onClick, + icon = { + Icon( + painter = + if (index == selectedTabIndex() && item.selectedIcon != null) item.selectedIcon + else item.icon + , + contentDescription = item.label, + ) + }, + label = { Text(text = item.label) }, + ) + } + } +} \ No newline at end of file From 6acee4d42e8e96f09f7741d24587748fed0f5776 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 31 Mar 2026 00:11:47 +0900 Subject: [PATCH 017/109] =?UTF-8?q?=F0=9F=94=A7=20chore(build):=20?= =?UTF-8?q?=EB=B9=8C=EB=93=9C=20=EC=84=A4=EC=A0=95=20=EC=B5=9C=EC=A0=81?= =?UTF-8?q?=ED=99=94=20=EB=B0=8F=20=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC?= =?UTF-8?q?=EB=A6=AC=20=EB=B2=84=EC=A0=84=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `publishToMavenLocal` 작업 시 서명 과정을 건너뛰도록 조건 추가 - 스냅샷 버전이 아니거나 서명 키가 있는 경우에만 서명을 수행하도록 로직 복구 및 개선 - 라이브러리 버전을 `1.0.2-alpha09`로 업데이트 --- build.gradle.kts | 15 +++++++++------ gradle/libs.versions.toml | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index a13e1683..ab98210e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -73,12 +73,15 @@ subprojects { } } -// val isSnapshot = version.toString().endsWith("SNAPSHOT") -// val hasSigningKey = !(project.findProperty("signingInMemoryKeyId") as String?).isNullOrBlank() -// -// if (!isSnapshot && hasSigningKey) { - signAllPublications() -// } + val isPublishingToMavenLocal = gradle.startParameter.taskNames + .any { it.contains("publishToMavenLocal", ignoreCase = true) } + + val isSnapshot = version.toString().endsWith("SNAPSHOT") + val hasSigningKey = !(project.findProperty("signingInMemoryKeyId") as String?).isNullOrBlank() + + if (!isSnapshot && hasSigningKey && !isPublishingToMavenLocal) { + signAllPublications() + } } } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2d2ec97d..ed979d9f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "9.0.0" kotlin = "2.3.20" -lib-version-name = "1.0.2-alpha08" +lib-version-name = "1.0.2-alpha09" # plugin compose-plugin = "1.10.3" From afc6cbfd95b32b7cea3a0884036fdf90119a0a8e Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 31 Mar 2026 01:24:30 +0900 Subject: [PATCH 018/109] =?UTF-8?q?=F0=9F=90=9B=20fix(hig-native):=20TabBa?= =?UTF-8?q?r=20=EC=84=A0=ED=83=9D=20=EC=8B=9C=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=EC=BD=98=20=ED=81=AC=EA=B8=B0=20=EB=88=84=EB=9D=BD=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EB=B2=84=EC=A0=84=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - iOS `TabBarManager`에서 `selectedIcon` 설정 시 리사이징(`resized(20.0)`)이 적용되지 않던 문제 수정 - 라이브러리 버전을 `1.0.2-alpha10`으로 업데이트 --- gradle/libs.versions.toml | 2 +- .../src/iosMain/kotlin/zone/ien/hig/navigation/TabBarManager.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ed979d9f..d32201d1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "9.0.0" kotlin = "2.3.20" -lib-version-name = "1.0.2-alpha09" +lib-version-name = "1.0.2-alpha10" # plugin compose-plugin = "1.10.3" diff --git a/hig-native/src/iosMain/kotlin/zone/ien/hig/navigation/TabBarManager.kt b/hig-native/src/iosMain/kotlin/zone/ien/hig/navigation/TabBarManager.kt index b354ff8c..981c7ae1 100644 --- a/hig-native/src/iosMain/kotlin/zone/ien/hig/navigation/TabBarManager.kt +++ b/hig-native/src/iosMain/kotlin/zone/ien/hig/navigation/TabBarManager.kt @@ -63,7 +63,7 @@ private fun CupertinoNavigationBarItemData.toUITabBarItem(tag: Long): UITabBarIt image = icon.toUIImage().resized(20.0), tag = tag, ) - selectedIcon?.toUIImage()?.let { item.selectedImage = it } + selectedIcon?.toUIImage()?.resized(20.0)?.let { item.selectedImage = it } return item } From f9385f8e6745ec7a571a66e29d5715487c9f223a Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 1 Apr 2026 08:49:33 +0900 Subject: [PATCH 019/109] =?UTF-8?q?=E2=9C=A8=20feat(example):=20CupertinoD?= =?UTF-8?q?ropdownMenuNative=20=EC=98=88=EC=A0=9C=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `DropdownExample`에 `isNative` 파라미터를 추가하여 네이티브 드롭다운 메뉴 표시 기능 지원 - `CupertinoDropdownMenuNative`를 사용하는 예제 코드 및 아이콘 데이터 추가 - `nativePickers` 상태 저장 방식을 `rememberSaveable`로 변경하여 구성 변경 시에도 상태 유지 - `CupertinoWidgetsScreen`의 섹션 아이템에 네이티브 설정 값 전달 로직 추가 --- .../cupertino/CupertinoWidgetsScreen.kt | 163 +++++++++++++----- 1 file changed, 120 insertions(+), 43 deletions(-) diff --git a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt index a11ddb6c..fc517a6a 100644 --- a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt +++ b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt @@ -77,6 +77,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -168,6 +169,7 @@ import zone.ien.hig.icons.filled.Archivebox import zone.ien.hig.icons.filled.Banknote import zone.ien.hig.icons.filled.Pin import zone.ien.hig.icons.filled.Trash +import zone.ien.hig.icons.outlined.Book import zone.ien.hig.icons.outlined.Bookmark import zone.ien.hig.icons.outlined.FaceSmiling import zone.ien.hig.icons.outlined.Heart @@ -415,7 +417,8 @@ private fun Body( } SectionItem { DropdownExample( - backdrop = backdrop + backdrop = backdrop, + isNative = nativePickers.value ) } SectionItem { @@ -449,7 +452,7 @@ private fun ScreenPreview() { paddingValues = PaddingValues.Zero, scrollState = rememberScrollState(), scaffoldState = rememberCupertinoBottomSheetScaffoldState(), - nativePickers = remember { mutableStateOf(false) }, + nativePickers = rememberSaveable { mutableStateOf(false) }, onNavigate = {}, backdrop = rememberDefaultBackdrop() ) @@ -1743,7 +1746,8 @@ private fun SheetsExamples() { @Composable private fun DropdownExample( - backdrop: Backdrop + backdrop: Backdrop, + isNative: Boolean ) { var dropdownVisible by remember { mutableStateOf(false) } var pickerSheetVisible by remember { mutableStateOf(false) } @@ -1811,59 +1815,106 @@ private fun DropdownExample( val red = CupertinoColors.systemRed - CupertinoDropdownMenu( - expanded = dropdownVisible, - onDismissRequest = { dropdownVisible = false }, - backdrop = backdrop - ) { - MenuSection( - title = { - Text("Menu") - } - ) { - MenuAction( - onClick = { - dropdownVisible = false - }, - leadingIcon = { - CupertinoIcon( - imageVector = CupertinoIcons.Default.SquareAndArrowUp, - contentDescription = null + if (isNative) { + CupertinoDropdownMenuNative( + expanded = dropdownVisible, + onDismissRequest = { dropdownVisible = false }, + backdrop = backdrop, + sections = listOf( + CupertinoMenuSectionData( + title = "Menu", + items = listOf( + CupertinoMenuItemData( + title = "Share", + onClick = { + dropdownVisible = false + }, + icon = AdaptiveIcons.painter( + material = { CupertinoIcons.Default.SquareAndArrowUp }, + cupertino = { "square.and.arrow.up" } + ) + ), + CupertinoMenuItemData( + title = "Add to Favorites", + enabled = false, + onClick = { + dropdownVisible = false + }, + icon = AdaptiveIcons.painter( + material = { CupertinoIcons.Default.Bookmark}, + cupertino = { "bookmark" } + ) + ), + CupertinoMenuItemData( + title = "Delete", + onClick = { + dropdownVisible = false + }, + isDestructive = true, + icon = AdaptiveIcons.painter( + material = { CupertinoIcons.Default.Trash}, + cupertino = { "trash" } + ) + ), ) + ) + ) + ) + } else { + CupertinoDropdownMenu( + expanded = dropdownVisible, + onDismissRequest = { dropdownVisible = false }, + backdrop = backdrop + ) { + MenuSection( + title = { + Text("Menu") } ) { - CupertinoText("Share") + MenuAction( + onClick = { + dropdownVisible = false + }, + leadingIcon = { + CupertinoIcon( + imageVector = CupertinoIcons.Default.SquareAndArrowUp, + contentDescription = null + ) + } + ) { + CupertinoText("Share") + } + MenuAction( + enabled = false, + onClick = { + dropdownVisible = false + }, + leadingIcon = { + CupertinoIcon( + imageVector = CupertinoIcons.Default.Bookmark, + contentDescription = null + ) + } + ) { + CupertinoText("Add to Favorites") + } } + MenuAction( - enabled = false, onClick = { dropdownVisible = false + }, + contentColor = red, leadingIcon = { CupertinoIcon( - imageVector = CupertinoIcons.Default.Bookmark, + imageVector = CupertinoIcons.Default.Trash, contentDescription = null ) } ) { - CupertinoText("Add to Favorites") - } - } - - MenuAction( - onClick = { - dropdownVisible = false - - }, - contentColor = red, - leadingIcon = { - CupertinoIcon( - imageVector = CupertinoIcons.Default.Trash, - contentDescription = null - ) + CupertinoText("Delete") } - ) { - CupertinoText("Delete") } } } @@ -1906,8 +1957,34 @@ private fun DropdownExample2( title = "Share", onClick = { dropdownVisible = false - } - ) + }, + icon = AdaptiveIcons.painter( + material = { CupertinoIcons.Default.SquareAndArrowUp }, + cupertino = { "square.and.arrow.up" } + ) + ), + CupertinoMenuItemData( + title = "Add to Favorites", + enabled = false, + onClick = { + dropdownVisible = false + }, + icon = AdaptiveIcons.painter( + material = { CupertinoIcons.Default.Bookmark}, + cupertino = { "bookmark" } + ) + ), + CupertinoMenuItemData( + title = "Delete", + onClick = { + dropdownVisible = false + }, + isDestructive = true, + icon = AdaptiveIcons.painter( + material = { CupertinoIcons.Default.Trash}, + cupertino = { "trash" } + ) + ), ) ) ) From f85166e18b307d0ab401c6ea1d5cfa052dbece6c Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 1 Apr 2026 08:50:52 +0900 Subject: [PATCH 020/109] =?UTF-8?q?=F0=9F=94=96=20chore(version):=20?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=EC=9D=84=201.0.2-alpha11=EB=A1=9C=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gradle/libs.versions.toml 파일의 lib-version-name을 1.0.2-alpha10에서 1.0.2-alpha11로 변경함. --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d32201d1..a9120647 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "9.0.0" kotlin = "2.3.20" -lib-version-name = "1.0.2-alpha10" +lib-version-name = "1.0.2-alpha11" # plugin compose-plugin = "1.10.3" From a376c1f312cc341f66e8b48eca05d8f53fb23f15 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 1 Apr 2026 08:54:48 +0900 Subject: [PATCH 021/109] =?UTF-8?q?=E2=9C=A8feat(hig):=20MenuSection=20tit?= =?UTF-8?q?le=20nullable=20=EB=8C=80=EC=9D=91=20=EB=B0=8F=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `CupertinoMenuSectionData`의 `title` 속성을 `String?` 타입으로 변경하고 기본값을 `null`로 설정하여 선택적 제목 지원 - non-iOS 구현체에서 `title`이 `null`인 경우를 대응하도록 로직 수정 - 라이브러리 버전을 `1.0.2-alpha12`로 업데이트 --- gradle/libs.versions.toml | 2 +- .../kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt | 2 +- .../kotlin/zone/ien/hig/CupertinoDropdownMenuNative.nonIos.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a9120647..f87171f5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "9.0.0" kotlin = "2.3.20" -lib-version-name = "1.0.2-alpha11" +lib-version-name = "1.0.2-alpha12" # plugin compose-plugin = "1.10.3" diff --git a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt index 68d5ef44..dfeed39d 100644 --- a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt +++ b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt @@ -23,7 +23,7 @@ data class CupertinoMenuItemData( ) data class CupertinoMenuSectionData( - val title: String = "", + val title: String? = null, val items: List, ) diff --git a/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.nonIos.kt b/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.nonIos.kt index b2473c1f..ca3d089e 100644 --- a/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.nonIos.kt +++ b/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.nonIos.kt @@ -61,7 +61,7 @@ actual fun CupertinoDropdownMenuNative( } sections.fastForEach { section -> MenuSection( - title = { CupertinoText(text = section.title) } + title = section.title?.let { { CupertinoText(text = it) } } ) { items.fastForEach { MenuAction( From bc5594259af1c32aeac36969f2c23268d438a7d4 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 1 Apr 2026 09:37:38 +0900 Subject: [PATCH 022/109] =?UTF-8?q?=E2=9C=A8feat(hig):=20CupertinoDropdown?= =?UTF-8?q?Menu=20=EC=84=B9=EC=85=98=20=ED=83=80=EC=9D=B4=ED=8B=80=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20=ED=8C=8C?= =?UTF-8?q?=EA=B4=B4=EC=A0=81=20=EC=95=A1=EC=85=98=20=EC=83=89=EC=83=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - iOS: 섹션 타이틀이 없는 경우 `UIMenu.menuWithChildren`을 사용하도록 수정 - non-iOS: `MenuSection` 내에서 잘못된 리스트를 참조하던 버그 수정 및 `isDestructive` 옵션에 따른 `systemRed` 색상 적용 - 라이브러리 버전을 `1.0.2-alpha13`으로 업데이트 --- gradle/libs.versions.toml | 2 +- .../hig/CupertinoDropdownMenuNative.ios.kt | 21 ++++++++++++------- .../hig/CupertinoDropdownMenuNative.nonIos.kt | 8 ++++--- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f87171f5..180a293b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "9.0.0" kotlin = "2.3.20" -lib-version-name = "1.0.2-alpha12" +lib-version-name = "1.0.2-alpha13" # plugin compose-plugin = "1.10.3" diff --git a/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt index cb4d6bee..6ef8d3d0 100644 --- a/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt +++ b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt @@ -167,14 +167,21 @@ internal class CupertinoDropdownMenuDelegate : NSObject() { fun buildMenu(): UIMenu { val topActions: List = items.map { it.toUIAction() } val sectionMenus: List = sections.map { section -> - UIMenu.menuWithTitle( - title = section.title, - image = null, - identifier = null, - options = UIMenuOptionsDisplayInline, - children = section.items.map { it.toUIAction() }, - ) + if (section.title != null) { + UIMenu.menuWithTitle( + title = section.title, + image = null, + identifier = null, + options = UIMenuOptionsDisplayInline, + children = section.items.map { it.toUIAction() } + ) + } else { + UIMenu.menuWithChildren( + children = section.items.map { it.toUIAction() } + ) + } } + return UIMenu.menuWithTitle( title = "", children = topActions + sectionMenus, diff --git a/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.nonIos.kt b/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.nonIos.kt index ca3d089e..9c4d7ca5 100644 --- a/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.nonIos.kt +++ b/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.nonIos.kt @@ -11,6 +11,8 @@ import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.util.fastForEach import androidx.compose.ui.window.PopupProperties import com.kyant.backdrop.Backdrop +import zone.ien.hig.theme.CupertinoColors +import zone.ien.hig.theme.systemRed @OptIn(markerClass = [ExperimentalComposeUiApi::class]) @ExperimentalCupertinoApi @@ -63,7 +65,7 @@ actual fun CupertinoDropdownMenuNative( MenuSection( title = section.title?.let { { CupertinoText(text = it) } } ) { - items.fastForEach { + section.items.fastForEach { MenuAction( onClick = it.onClick, enabled = it.enabled, @@ -77,8 +79,8 @@ actual fun CupertinoDropdownMenuNative( }, title = { CupertinoText(text = it.title) - } - + }, + contentColor = if (it.isDestructive) CupertinoColors.systemRed else CupertinoDropdownMenuDefaults.ContentColor ) } } From efa52fbc1fc4ad34f1a4f9d500331e3519af1662 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 1 Apr 2026 18:08:37 +0900 Subject: [PATCH 023/109] =?UTF-8?q?=E2=9C=A8=20feat(hig):=20CupertinoDropd?= =?UTF-8?q?ownMenu=20=EC=84=B9=EC=85=98=20=EC=98=B5=EC=85=98=20=EB=B0=8F?= =?UTF-8?q?=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=A7=80=EC=9B=90=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CupertinoMenuSectionData에 icon 및 HigMenuOptions 필드 추가 - iOS 네이티브 메뉴 빌드 시 UIMenuOptions 연동 및 섹션 아이콘 적용 - 비 iOS 환경에서 파괴적 작업(isDestructive) 색상 적용 및 메뉴 구분선 추가 - 메뉴 섹션 타이틀의 가로 패딩(8.dp) 추가 및 레이아웃 조정 - CupertinoMenuSectionData의 title 필드를 필수 값으로 변경하여 구조 개선 - 예제 코드에 아이콘 및 단일 선택 옵션이 적용된 메뉴 사례 추가 --- .../cupertino/CupertinoWidgetsScreen.kt | 40 +++++++++++++++++++ .../ien/hig/CupertinoDropdownMenuNative.kt | 8 +++- .../hig/CupertinoDropdownMenuNative.ios.kt | 32 ++++++++------- .../hig/CupertinoDropdownMenuNative.nonIos.kt | 16 ++++---- .../zone/ien/hig/CupertinoDropdownMenu.kt | 6 ++- 5 files changed, 78 insertions(+), 24 deletions(-) diff --git a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt index fc517a6a..6510ecfe 100644 --- a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt +++ b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt @@ -146,6 +146,7 @@ import zone.ien.hig.CupertinoTopAppBar import zone.ien.hig.CupertinoTriStateCheckBox import zone.ien.hig.CupertinoWheelPicker import zone.ien.hig.ExperimentalCupertinoApi +import zone.ien.hig.HigMenuOptions import zone.ien.hig.MenuAction import zone.ien.hig.MenuSection import zone.ien.hig.PresentationStyle @@ -1820,9 +1821,48 @@ private fun DropdownExample( expanded = dropdownVisible, onDismissRequest = { dropdownVisible = false }, backdrop = backdrop, + items = listOf( + CupertinoMenuItemData( + title = "Share", + onClick = { + dropdownVisible = false + }, + icon = AdaptiveIcons.painter( + material = { CupertinoIcons.Default.SquareAndArrowUp }, + cupertino = { "square.and.arrow.up" } + ) + ), + CupertinoMenuItemData( + title = "Add to Favorites", + enabled = false, + onClick = { + dropdownVisible = false + }, + icon = AdaptiveIcons.painter( + material = { CupertinoIcons.Default.Bookmark}, + cupertino = { "bookmark" } + ) + ), + CupertinoMenuItemData( + title = "Delete", + onClick = { + dropdownVisible = false + }, + isDestructive = true, + icon = AdaptiveIcons.painter( + material = { CupertinoIcons.Default.Trash}, + cupertino = { "trash" } + ) + ), + ), sections = listOf( CupertinoMenuSectionData( title = "Menu", + options = HigMenuOptions.SingleSelection, + icon = AdaptiveIcons.painter( + material = { CupertinoIcons.Default.SquareAndArrowUp }, + cupertino = { "square.and.arrow.up" } + ), items = listOf( CupertinoMenuItemData( title = "Share", diff --git a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt index dfeed39d..b449d2e7 100644 --- a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt +++ b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt @@ -23,10 +23,16 @@ data class CupertinoMenuItemData( ) data class CupertinoMenuSectionData( - val title: String? = null, + val title: String, + val icon: Painter? = null, + val options: HigMenuOptions = HigMenuOptions.DisplayInline, val items: List, ) +enum class HigMenuOptions { + DisplayInline, SingleSelection, DisplayAsPalette, Destructive +} + @OptIn(ExperimentalComposeUiApi::class) @ExperimentalCupertinoApi @Composable diff --git a/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt index 6ef8d3d0..ff51be45 100644 --- a/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt +++ b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt @@ -48,11 +48,22 @@ import platform.UIKit.UIMenu import platform.UIKit.UIMenuElement import platform.UIKit.UIMenuElementAttributesDestructive import platform.UIKit.UIMenuElementAttributesDisabled +import platform.UIKit.UIMenuOptionsDestructive +import platform.UIKit.UIMenuOptionsDisplayAsPalette import platform.UIKit.UIMenuOptionsDisplayInline +import platform.UIKit.UIMenuOptionsSingleSelection +import platform.UIKit.UIMenuOptionsVar import platform.UIKit.secondaryLabelColor import platform.UIKit.touchesBegan import platform.darwin.NSObject +fun HigMenuOptions.toUIMenuOptions() = when (this) { + HigMenuOptions.DisplayInline -> UIMenuOptionsDisplayInline + HigMenuOptions.SingleSelection -> UIMenuOptionsSingleSelection + HigMenuOptions.DisplayAsPalette -> UIMenuOptionsDisplayAsPalette + HigMenuOptions.Destructive -> UIMenuOptionsDestructive +} + @OptIn(ExperimentalForeignApi::class, BetaInteropApi::class) @ExperimentalCupertinoApi @Composable @@ -118,7 +129,6 @@ actual fun CupertinoDropdownMenuNative( if (isPerformPrimaryActionAvailable()) { button.performPrimaryAction() } else { - // iOS 17.4 미만: gesture recognizer를 직접 트리거 val gesture = button.gestureRecognizers .orEmpty() .filterIsInstance() @@ -167,19 +177,13 @@ internal class CupertinoDropdownMenuDelegate : NSObject() { fun buildMenu(): UIMenu { val topActions: List = items.map { it.toUIAction() } val sectionMenus: List = sections.map { section -> - if (section.title != null) { - UIMenu.menuWithTitle( - title = section.title, - image = null, - identifier = null, - options = UIMenuOptionsDisplayInline, - children = section.items.map { it.toUIAction() } - ) - } else { - UIMenu.menuWithChildren( - children = section.items.map { it.toUIAction() } - ) - } + UIMenu.menuWithTitle( + title = section.title, + image = section.icon?.toUIImage()?.resized(20.0), + identifier = null, + options = section.options.toUIMenuOptions(), + children = section.items.map { it.toUIAction() } + ) } return UIMenu.menuWithTitle( diff --git a/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.nonIos.kt b/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.nonIos.kt index 9c4d7ca5..f6361b04 100644 --- a/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.nonIos.kt +++ b/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.nonIos.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.util.fastForEach import androidx.compose.ui.window.PopupProperties import com.kyant.backdrop.Backdrop +import zone.ien.hig.MenuSection import zone.ien.hig.theme.CupertinoColors import zone.ien.hig.theme.systemRed @@ -55,15 +56,16 @@ actual fun CupertinoDropdownMenuNative( ) } }, - title = { - CupertinoText(text = it.title) - } - + title = { CupertinoText(text = it.title) }, + contentColor = if (it.isDestructive) CupertinoColors.systemRed else CupertinoDropdownMenuDefaults.ContentColor ) } + if (items.isNotEmpty() && sections.isNotEmpty()) { + MenuDivider() + } sections.fastForEach { section -> MenuSection( - title = section.title?.let { { CupertinoText(text = it) } } + title = { CupertinoText(text = section.title) } ) { section.items.fastForEach { MenuAction( @@ -77,9 +79,7 @@ actual fun CupertinoDropdownMenuNative( ) } }, - title = { - CupertinoText(text = it.title) - }, + title = { CupertinoText(text = it.title) }, contentColor = if (it.isDestructive) CupertinoColors.systemRed else CupertinoDropdownMenuDefaults.ContentColor ) } diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt index 4da6279c..9fa12a9b 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt @@ -206,7 +206,11 @@ fun CupertinoMenuScope.MenuTitle( LocalContentColor provides CupertinoTheme.colorScheme.secondaryLabel, ) { ProvideTextStyle(CupertinoTheme.typography.footnote) { - Box(Modifier.padding(it)) { title() } + Box( + modifier = Modifier + .padding(it) + .padding(horizontal = 8.dp) + ) { title() } } } } From 6593590c4bf7cbd2a0c0779bd85ffbdbe69a3ddf Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 1 Apr 2026 18:12:37 +0900 Subject: [PATCH 024/109] =?UTF-8?q?=F0=9F=94=96=20chore:=20=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EB=B2=84=EC=A0=84?= =?UTF-8?q?=EC=9D=84=201.0.2-alpha14=EB=A1=9C=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `lib-version-name`을 1.0.2-alpha13에서 1.0.2-alpha14로 상향 조정합니다. --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 180a293b..d99f4b22 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "9.0.0" kotlin = "2.3.20" -lib-version-name = "1.0.2-alpha13" +lib-version-name = "1.0.2-alpha14" # plugin compose-plugin = "1.10.3" From 43ed4bfe51e1cda2ed000a349f1a31c46efd0308 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 7 Apr 2026 16:00:24 +0900 Subject: [PATCH 025/109] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(adaptive)?= =?UTF-8?q?:=20AdaptiveScaffold=20=EB=82=B4=20=EA=B0=80=EC=8B=9C=EC=84=B1?= =?UTF-8?q?=20=EC=A0=9C=EC=96=B4=EC=9E=90=20=EC=88=98=EC=A0=95=20=EB=B0=8F?= =?UTF-8?q?=20=EB=B2=84=EC=A0=84=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AdaptiveScaffold 관련 클래스의 contentColor 및 containerColor 가시성 제어자를 internal에서 public으로 변경 - 라이브러리 버전을 1.0.2-alpha14에서 1.0.2-alpha15로 상향 조정 --- gradle/libs.versions.toml | 2 +- .../kotlin/zone/ien/hig/adaptive/AdaptiveScaffold.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d99f4b22..394fd241 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "9.0.0" kotlin = "2.3.20" -lib-version-name = "1.0.2-alpha14" +lib-version-name = "1.0.2-alpha15" # plugin compose-plugin = "1.10.3" diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveScaffold.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveScaffold.kt index d0730d12..3bb44f62 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveScaffold.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveScaffold.kt @@ -157,8 +157,8 @@ class ScaffoldAdaptation internal constructor( contentColor: Color, containerColor: Color ) { - internal var contentColor by mutableStateOf(contentColor) - internal var containerColor by mutableStateOf(containerColor) + var contentColor by mutableStateOf(contentColor) + var containerColor by mutableStateOf(containerColor) } @OptIn(ExperimentalAdaptiveApi::class) @Stable From f104c04bab2f1e9b5bdf2b6ce2ae115d7b2a5f84 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 14 Apr 2026 23:32:09 +0900 Subject: [PATCH 026/109] =?UTF-8?q?=F0=9F=94=96=20chore(deps):=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EB=B2=84=EC=A0=84=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20=EB=B0=8F=20capsule=20=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EA=B2=BD=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - serialization 버전을 1.10.0에서 1.11.0으로 상향 조정 - koin 버전을 4.2.0에서 4.2.1로 상향 조정 - capsule 라이브러리의 그룹 경로를 io.github.kyant0에서 zone.ien.shapes로 변경 --- gradle/libs.versions.toml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 394fd241..2c79a6f2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,10 +20,10 @@ datetime = "0.7.1" material-kolor = "4.1.1" # backend -serialization = "1.10.0" +serialization = "1.11.0" vanniktech-mavenPublish = "0.36.0" atomicfu = "0.23.2" -koin = "4.2.0" +koin = "4.2.1" [libraries] @@ -45,7 +45,8 @@ compose-navigation3 = { group = "org.jetbrains.androidx.navigation3", name = "na backdrop = { group = "zone.ien.backdrop", name = "backdrop", version.ref = "backdrop"} #backdrop = { group = "io.github.kyant0", name = "backdrop", version.ref = "backdrop"} -capsule = { group = "io.github.kyant0", name = "shapes", version.ref = "capsule"} +#capsule = { group = "io.github.kyant0", name = "shapes", version.ref = "capsule"} +capsule = { group = "zone.ien.shapes", name = "shapes", version.ref = "capsule"} haze = { group = "dev.chrisbanes.haze", name = "haze", version.ref = "haze" } # functionality From 128ce265d1c101a691db3bded8097d68ff8083f0 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 14 Apr 2026 23:33:43 +0900 Subject: [PATCH 027/109] =?UTF-8?q?=F0=9F=94=96=20chore:=20=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EB=B2=84=EC=A0=84?= =?UTF-8?q?=EC=9D=84=201.0.2-alpha16=EC=9C=BC=EB=A1=9C=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `lib-version-name`을 1.0.2-alpha15에서 1.0.2-alpha16으로 상향 조정합니다. --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2c79a6f2..714758d6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "9.0.0" kotlin = "2.3.20" -lib-version-name = "1.0.2-alpha15" +lib-version-name = "1.0.2-alpha16" # plugin compose-plugin = "1.10.3" From 090dface18d5b88ac4357df121619ed023f77a67 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 15 Apr 2026 00:33:36 +0900 Subject: [PATCH 028/109] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20chore(deps):=20bac?= =?UTF-8?q?kdrop=20=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20?= =?UTF-8?q?=EB=B2=84=EC=A0=84=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - backdrop 버전을 2.0.0-alpha04에서 2.0.0-alpha05로 상향 조정합니다. --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 714758d6..2fcb34a9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ compose-navigation3 = "1.0.0-alpha06" # ui activity-compose = "1.13.0" lifecycle = "2.10.0" -backdrop = "2.0.0-alpha04" +backdrop = "2.0.0-alpha05" capsule = "1.2.0" haze = "1.7.2" From 6794bc297fca35aded8295acfdf804bcc0613791 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 15 Apr 2026 00:35:37 +0900 Subject: [PATCH 029/109] =?UTF-8?q?=F0=9F=94=96=20chore:=20=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EB=B2=84=EC=A0=84?= =?UTF-8?q?=EC=9D=84=201.0.2-alpha17=EB=A1=9C=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `lib-version-name`을 1.0.2-alpha16에서 1.0.2-alpha17로 상향 조정합니다. --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2fcb34a9..dc839110 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "9.0.0" kotlin = "2.3.20" -lib-version-name = "1.0.2-alpha16" +lib-version-name = "1.0.2-alpha17" # plugin compose-plugin = "1.10.3" From 647eb1874bb4943a911984ed49fb4e54cc10b28b Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 15 Apr 2026 01:05:26 +0900 Subject: [PATCH 030/109] =?UTF-8?q?=F0=9F=94=A7=20chore(build):=20Maven=20?= =?UTF-8?q?Central=20=EB=B0=B0=ED=8F=AC=20=EB=B0=8F=20=EC=84=9C=EB=AA=85?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - gradle.properties에 Maven Central 인증 정보 및 GPG 서명 설정 추가 - build.gradle.kts에서 불필요한 서명 키 체크 로직 제거 및 서명 조건 단순화 --- build.gradle.kts | 7 ++----- gradle.properties | 8 +++++++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index ab98210e..225f9c9e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -73,13 +73,10 @@ subprojects { } } - val isPublishingToMavenLocal = gradle.startParameter.taskNames - .any { it.contains("publishToMavenLocal", ignoreCase = true) } - + val isPublishingToMavenLocal = gradle.startParameter.taskNames.any { it.contains("publishToMavenLocal", ignoreCase = true) } val isSnapshot = version.toString().endsWith("SNAPSHOT") - val hasSigningKey = !(project.findProperty("signingInMemoryKeyId") as String?).isNullOrBlank() - if (!isSnapshot && hasSigningKey && !isPublishingToMavenLocal) { + if (!isSnapshot && !isPublishingToMavenLocal) { signAllPublications() } } diff --git a/gradle.properties b/gradle.properties index ac6d0600..76c4eea4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -45,4 +45,10 @@ org.jetbrains.compose.experimental.macos.enabled=true kotlin.mpp.stability.nowarn=true kotlin.mpp.enableCInteropCommonization=true kotlin.mpp.androidSourceSetLayoutVersion=2 -kotlin.native.ignoreDisabledTargets=true \ No newline at end of file +kotlin.native.ignoreDisabledTargets=true + +mavenCentralUsername = vkBvsK +mavenCentralPassword = n8V4ktdFm5tmMmi7CbZ4FDwI3tDid74xy +signing.keyId = 2100B93E +signing.password = thisis1en9r0vnd +signing.secretKeyRingFile = /Users/ienground/.gnupg/secring.gpg \ No newline at end of file From fb804cf7050e2941822f226634e31d85000e8248 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 21 Apr 2026 07:28:11 +0900 Subject: [PATCH 031/109] =?UTF-8?q?=F0=9F=94=A7=20chore(publish):=20Maven?= =?UTF-8?q?=20Central=20=EC=9E=90=EB=8F=99=20=EB=A6=B4=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `publishToMavenCentral` 호출 시 `automaticRelease = true` 옵션을 추가하여 배포 프로세스를 자동화합니다. --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 225f9c9e..19d5d0e1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -39,7 +39,7 @@ allprojects { subprojects { plugins.withId("com.vanniktech.maven.publish") { configure { - publishToMavenCentral() + publishToMavenCentral(automaticRelease = true) // Artifact ID만 각 프로젝트의 이름으로 자동 설정 group = "zone.ien.hig" From 2a8ea6e17fa730bc461f9b24ee1466cee11a6bbb Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Sat, 2 May 2026 20:00:49 +0900 Subject: [PATCH 032/109] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(ui):=20Cu?= =?UTF-8?q?pertinoNavigationBar=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EB=B0=8F=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 네비게이션 아이템을 가중치 방식에서 고정 너비(90.dp) 기반 중앙 정렬로 변경 - 아이콘 크기 축소(18.dp) 및 라벨 텍스트 스타일(10.sp, Bold) 조정 - 슬라이딩 인디케이터 및 배경 애니메이션 로직을 고정 너비 대응으로 수정 - 하단 여백(24.dp) 추가 및 WindowInsets 기본값 조정 --- .../zone/ien/hig/CupertinoNavigationBar.kt | 547 +++++++----------- 1 file changed, 222 insertions(+), 325 deletions(-) diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt index 5f7791e0..4ff03683 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt @@ -16,15 +16,13 @@ * limitations under the License. */ - - package zone.ien.hig import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.EaseOut import androidx.compose.animation.core.spring +import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.gestures.ScrollableState import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement @@ -37,10 +35,12 @@ import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Immutable @@ -68,6 +68,7 @@ import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -78,7 +79,6 @@ import com.kyant.backdrop.backdrops.LayerBackdrop import com.kyant.backdrop.backdrops.layerBackdrop import com.kyant.backdrop.backdrops.rememberCombinedBackdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop -import zone.ien.hig.utils.rememberDefaultBackdrop import com.kyant.backdrop.drawBackdrop import com.kyant.backdrop.effects.blur import com.kyant.backdrop.effects.lens @@ -93,39 +93,19 @@ import kotlinx.coroutines.launch import zone.ien.hig.theme.CupertinoTheme import zone.ien.hig.utils.DampedDragAnimation import zone.ien.hig.utils.InteractiveHighlight +import zone.ien.hig.utils.rememberDefaultBackdrop import kotlin.math.abs import kotlin.math.sign -/** - * Cupertino bottom navigation tab bar - * - * [CupertinoNavigationBarItem]s should be used as navigation bar content - * - * Note: navigation bar itself does not produce cupertino thin material glass effect. - * This effect works only inside [CupertinoScaffold], [CupertinoBottomSheetScaffold], [CupertinoBottomSheetContent]. - * To achieve this effect with custom bottom bar use [cupertinoTranslucentTopBarColor] - * function that will communicate with scaffold and return either - * [Color.Transparent] if color was successfully applied to scaffold (and top bar itself - * should be transparent) or passed color if scaffold wasn't found. - * - * @param modifier the [Modifier] to be applied to this top app bar. - * @param windowInsets a window insets that app bar will respect. - * @param containerColor color of the navigation bar background. - * @param isTransparent navigation bar is usually transparent if scroll container reached bottom. - * [ScrollableState.isNavigationBarTransparent] and [LazyListState.isNavigationBarTransparent] can be used to track it - * @param isTranslucent works only inside [CupertinoScaffold]. Blurred content behind navigation bar will be - * visible if navigation bar is translucent. Simulates iOS app bars material. - * @param divider top divider when [isTransparent] is false - * @param content navigation bar content, usually [CupertinoNavigationBarItem] - * - * @see CupertinoNavigationBarItem - */ +private val NavBarPadding = 4.dp +private val NavBarItemGap = 0.dp // 아이템 사이 고정 간격. 필요 시 조절 + @Composable @ExperimentalCupertinoApi fun CupertinoNavigationBar( modifier: Modifier = Modifier, colors: CupertinoNavigationBarColors = CupertinoNavigationBarDefaults.colors(), - windowInsets: WindowInsets = CupertinoNavigationBarDefaults.windowInsets, + windowInsets: WindowInsets = WindowInsets.navigationBars, backdrop: LayerBackdrop, selectedTabIndex: () -> Int, onTabSelected: (index: Int) -> Unit, @@ -137,134 +117,113 @@ fun CupertinoNavigationBar( val accentColor = colors.accentColor val containerColor = colors.containerColor.copy(0.6f) - BoxWithConstraints( - contentAlignment = Alignment.CenterStart, + Box( modifier = modifier - .navigationBarsPadding() + .fillMaxWidth() + .padding(bottom = 24.dp) + .wrapContentWidth() .windowInsetsPadding(windowInsets) ) { - val density = LocalDensity.current - val tabWidth = with(density) { - (constraints.maxWidth.toFloat() - 8.dp.toPx()) / tabsCount - } + BoxWithConstraints( + contentAlignment = Alignment.CenterStart, + ) { + val density = LocalDensity.current + + val paddingPx = with(density) { NavBarPadding.toPx() } + val itemWidthPx = with(density) { CupertinoNavigationBarItemWidth.toPx() } + val gapPx = with(density) { NavBarItemGap.toPx() } + + // NavBar 전체 너비 = padding*2 + itemWidth*n + gap*(n-1) + // CupertinoNavigationBarItemWidth 바꾸면 이 값이 자동으로 연동됨 + val rowWidth = paddingPx * 2f + itemWidthPx * tabsCount + gapPx * (tabsCount - 1) - val offsetAnimation = remember { Animatable(0f) } - val panelOffset by remember(density) { - derivedStateOf { - val fraction = (offsetAnimation.value / constraints.maxWidth).fastCoerceIn(-1f, 1f) - with(density) { - 4.dp.toPx() * fraction.sign * EaseOut.transform(abs(fraction)) + // 아이템 i 왼쪽 X (BoxWithConstraints 절대좌표) + // = paddingPx + (itemWidthPx + gapPx) * i + fun itemLeftX(index: Float): Float = paddingPx + (itemWidthPx + gapPx) * index + fun itemCenterX(index: Float): Float = itemLeftX(index) + itemWidthPx / 2f + + // indicator 이동 step = itemWidth + gap + val tabStep = itemWidthPx + gapPx + + val offsetAnimation = remember { Animatable(0f) } + val panelOffset by remember(density) { + derivedStateOf { + val fraction = (offsetAnimation.value / rowWidth).fastCoerceIn(-1f, 1f) + with(density) { + 4.dp.toPx() * fraction.sign * EaseOut.transform(abs(fraction)) + } } } - } - val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr - val animationScope = rememberCoroutineScope() - var currentIndex by remember(selectedTabIndex) { - mutableIntStateOf(selectedTabIndex()) - } - val dampedDragAnimation = remember(animationScope) { - DampedDragAnimation( - animationScope = animationScope, - initialValue = selectedTabIndex().toFloat(), - valueRange = 0f..(tabsCount - 1).toFloat(), - visibilityThreshold = 0.001f, - initialScale = 1f, - pressedScale = 78f / 56f, - onDragStarted = {}, - onDragStopped = { - val targetIndex = targetValue.fastRoundToInt().fastCoerceIn(0, tabsCount - 1) - currentIndex = targetIndex - animateToValue(targetIndex.toFloat()) - animationScope.launch { - offsetAnimation.animateTo( - 0f, - spring(1f, 300f, 0.5f) + val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr + val animationScope = rememberCoroutineScope() + var currentIndex by remember(selectedTabIndex) { + mutableIntStateOf(selectedTabIndex()) + } + val dampedDragAnimation = remember(animationScope) { + DampedDragAnimation( + animationScope = animationScope, + initialValue = selectedTabIndex().toFloat(), + valueRange = 0f..(tabsCount - 1).toFloat(), + visibilityThreshold = 0.001f, + initialScale = 1f, + pressedScale = 78f / 56f, + onDragStarted = {}, + onDragStopped = { + val targetIndex = targetValue.fastRoundToInt().fastCoerceIn(0, tabsCount - 1) + currentIndex = targetIndex + animateToValue(targetIndex.toFloat()) + animationScope.launch { + offsetAnimation.animateTo( + 0f, + spring(1f, 300f, 0.5f) + ) + } + }, + onDrag = { _, dragAmount -> + updateValue( + (targetValue + dragAmount.x / tabStep * if (isLtr) 1f else -1f) + .fastCoerceIn(0f, (tabsCount - 1).toFloat()) ) + animationScope.launch { + offsetAnimation.snapTo(offsetAnimation.value + dragAmount.x) + } } - }, - onDrag = { _, dragAmount -> - updateValue( - (targetValue + dragAmount.x / tabWidth * if (isLtr) 1f else -1f) - .fastCoerceIn(0f, (tabsCount - 1).toFloat()) - ) - animationScope.launch { - offsetAnimation.snapTo(offsetAnimation.value + dragAmount.x) - } - } - ) - } - LaunchedEffect(selectedTabIndex) { - snapshotFlow { selectedTabIndex() } - .collectLatest { index -> - currentIndex = index - } - } - LaunchedEffect(dampedDragAnimation) { - snapshotFlow { currentIndex } - .drop(1) - .collectLatest { index -> - dampedDragAnimation.animateToValue(index.toFloat()) - onTabSelected(index) - } - } + ) + } - val interactiveHighlight = remember(animationScope) { - InteractiveHighlight( - animationScope = animationScope, - position = { size, offset -> - Offset( - if (isLtr) (dampedDragAnimation.value + 0.5f) * tabWidth + panelOffset - else size.width - (dampedDragAnimation.value + 0.5f) * tabWidth + panelOffset, - size.height / 2f - ) - } - ) - } + LaunchedEffect(selectedTabIndex) { + snapshotFlow { selectedTabIndex() } + .collectLatest { index -> + currentIndex = index + } + } + LaunchedEffect(dampedDragAnimation) { + snapshotFlow { currentIndex } + .drop(1) + .collectLatest { index -> + dampedDragAnimation.animateToValue(index.toFloat()) + onTabSelected(index) + } + } - Row( - Modifier - .graphicsLayer { - translationX = panelOffset - } - .drawBackdrop( - backdrop = backdrop, - shape = { Capsule() }, - effects = { - vibrancy() - blur(2.dp.toPx()) - lens(24.dp.toPx(), 24.dp.toPx()) - }, - layerBlock = { - val progress = dampedDragAnimation.pressProgress - val scale = lerp(1f, 1f + 16.dp.toPx() / size.width, progress) - scaleX = scale - scaleY = scale - }, - onDrawSurface = { - drawRect(containerColor) + val interactiveHighlight = remember(animationScope) { + InteractiveHighlight( + animationScope = animationScope, + position = { size, _ -> + val cx = itemCenterX(dampedDragAnimation.value) + Offset( + if (isLtr) cx + panelOffset + else size.width - cx + panelOffset, + size.height / 2f + ) } ) - .then(interactiveHighlight.modifier) - .height(64.dp) - .fillMaxWidth() - .padding(4.dp), - verticalAlignment = Alignment.CenterVertically, - content = content - ) - - CompositionLocalProvider( - LocalLiquidBottomTabScale provides { - lerp(1f, 1.2f, dampedDragAnimation.pressProgress) } - ) { + + // ── 배경 Row ────────────────────────────────────────────────────────── Row( - verticalAlignment = Alignment.CenterVertically, - content = content, modifier = Modifier - .clearAndSetSemantics {} - .alpha(0f) - .layerBackdrop(tabsBackdrop) .graphicsLayer { translationX = panelOffset } @@ -272,168 +231,136 @@ fun CupertinoNavigationBar( backdrop = backdrop, shape = { Capsule() }, effects = { - val progress = dampedDragAnimation.pressProgress vibrancy() - blur(8.dp.toPx()) - lens( - 24.dp.toPx() * progress, - 24.dp.toPx() * progress - ) + blur(2.dp.toPx()) + lens(24.dp.toPx(), 24.dp.toPx()) }, - highlight = { + layerBlock = { val progress = dampedDragAnimation.pressProgress - Highlight.Default.copy(alpha = progress) + val scale = lerp(1f, 1f + 16.dp.toPx() / size.width, progress) + scaleX = scale + scaleY = scale }, onDrawSurface = { - drawRect(if (isLightTheme) Color.White else Color.Black) drawRect(containerColor) } ) .then(interactiveHighlight.modifier) - .height(56.dp) - .fillMaxWidth() - .padding(horizontal = 4.dp) - .graphicsLayer(colorFilter = ColorFilter.tint(accentColor)) + .wrapContentWidth() + .height(64.dp) + .padding(NavBarPadding), + horizontalArrangement = Arrangement.spacedBy(NavBarItemGap), + verticalAlignment = Alignment.CenterVertically, + content = content ) - } - Box( - modifier = Modifier - .padding(horizontal = 4.dp) - .graphicsLayer { - translationX = - if (isLtr) dampedDragAnimation.value * tabWidth + panelOffset - else size.width - (dampedDragAnimation.value + 1f) * tabWidth + panelOffset + // ── 액센트 컬러 오버레이 Row ────────────────────────────────────────── + CompositionLocalProvider( + LocalLiquidBottomTabScale provides { + lerp(1f, 1.2f, dampedDragAnimation.pressProgress) } - .then(interactiveHighlight.gestureModifier) - .then(dampedDragAnimation.modifier) - .drawBackdrop( - backdrop = rememberCombinedBackdrop(backdrop, tabsBackdrop), - shape = { Capsule() }, - effects = { - val progress = dampedDragAnimation.pressProgress - lens( - 10.dp.toPx() * progress, - 14.dp.toPx() * progress, - chromaticAberration = true - ) - }, - highlight = { - val progress = dampedDragAnimation.pressProgress - Highlight.Default.copy(alpha = progress) - }, - shadow = { - val progress = dampedDragAnimation.pressProgress - Shadow(alpha = progress) - }, - innerShadow = { - val progress = dampedDragAnimation.pressProgress - InnerShadow( - radius = 8.dp * progress, - alpha = progress - ) - }, - layerBlock = { - scaleX = dampedDragAnimation.scaleX - scaleY = dampedDragAnimation.scaleY - val velocity = dampedDragAnimation.velocity / 10f - scaleX /= 1f - (velocity * 0.75f).fastCoerceIn(-0.2f, 0.2f) - scaleY *= 1f - (velocity * 0.25f).fastCoerceIn(-0.2f, 0.2f) - }, - onDrawSurface = { - val progress = dampedDragAnimation.pressProgress - drawRect( - if (isLightTheme) Color.Black.copy(0.1f) - else Color.White.copy(0.1f), - alpha = 1f - progress + ) { + Row( + modifier = Modifier + .clearAndSetSemantics {} + .alpha(0f) + .layerBackdrop(tabsBackdrop) + .graphicsLayer { + translationX = panelOffset + } + .drawBackdrop( + backdrop = backdrop, + shape = { Capsule() }, + effects = { + val progress = dampedDragAnimation.pressProgress + vibrancy() + blur(8.dp.toPx()) + lens( + 24.dp.toPx() * progress, + 24.dp.toPx() * progress + ) + }, + highlight = { + val progress = dampedDragAnimation.pressProgress + Highlight.Default.copy(alpha = progress) + }, + onDrawSurface = {} ) - drawRect(Color.Black.copy(alpha = 0.03f * progress)) - } + .then(interactiveHighlight.modifier) + .wrapContentWidth() + .height(56.dp) + .padding(horizontal = NavBarPadding) + .graphicsLayer(colorFilter = ColorFilter.tint(accentColor)), + horizontalArrangement = Arrangement.spacedBy(NavBarItemGap), + verticalAlignment = Alignment.CenterVertically, + content = content ) - .height(56.dp) - .fillMaxWidth(1f / tabsCount) - ) - } -} - -/** - * Item of the [CupertinoNavigationBar] - * - * @param selected if tab with this item is selected - * @param onClick action performed on item click - * @param icon item icon - * @param modifier modifier applied to item container - * @param enabled if this item is clickable - * @param label item label located below the [icon] - * @param alwaysShowLabel if label should always be visible. If this flag is false then - * label will only be visible if this item is selected - * @param colors item colors. See [CupertinoNavigationBarDefaults.itemColors] - * @param interactionSource interaction source of the item click modifier - * */ -/* -@Composable -@ExperimentalCupertinoApi -fun RowScope.CupertinoNavigationBarItem2( - selected: Boolean, - onClick: () -> Unit, - icon: @Composable () -> Unit, - modifier: Modifier = Modifier, - enabled: Boolean = true, - label: @Composable (() -> Unit)? = null, - alwaysShowLabel: Boolean = true, - pressIndicationEnabled: Boolean = false, - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, -) { - val pressed by interactionSource.collectIsPressedAsState() - - Column( - modifier - .selectable( - selected = selected, - onClick = onClick, - enabled = enabled, - role = Role.Tab, - interactionSource = interactionSource, - indication = null, - ).weight(1f) - .padding(top = 6.dp) - .fillMaxHeight(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(6.dp), - ) { - val iconColor = colors.iconColor(selected, enabled) - val textColor = colors.textColor(selected, enabled) - - ProvideTextStyle( - value = CupertinoTheme.typography.caption2, - ) { - val alpha = - if (pressIndicationEnabled && pressed && !selected) { - textColor.alpha * CupertinoButtonTokens.PressedPlainButonAlpha - } else { - textColor.alpha - } - - CompositionLocalProvider( - LocalContentColor provides iconColor.copy(alpha = alpha), - ) { - Box( - modifier = Modifier.size(CupertinoIconDefaults.MediumSize), - contentAlignment = Alignment.Center, - ) { - icon() - } - - if (label != null && (alwaysShowLabel || selected)) { - label() - } } + + // ── 슬라이딩 선택 indicator Box ─────────────────────────────────────── + Box( + modifier = Modifier + .graphicsLayer { + val leftX = itemLeftX(dampedDragAnimation.value) + translationX = if (isLtr) { + leftX + panelOffset + } else { + rowWidth - leftX - itemWidthPx + panelOffset + } + } + .then(interactiveHighlight.gestureModifier) + .then(dampedDragAnimation.modifier) + .drawBackdrop( + backdrop = rememberCombinedBackdrop(backdrop, tabsBackdrop), + shape = { Capsule() }, + effects = { + val progress = dampedDragAnimation.pressProgress + lens( + 10.dp.toPx() * progress, + 14.dp.toPx() * progress, + chromaticAberration = true + ) + }, + highlight = { + val progress = dampedDragAnimation.pressProgress + Highlight.Default.copy(alpha = progress) + }, + shadow = { + val progress = dampedDragAnimation.pressProgress + Shadow(alpha = progress) + }, + innerShadow = { + val progress = dampedDragAnimation.pressProgress + InnerShadow( + radius = 8.dp * progress, + alpha = progress + ) + }, + layerBlock = { + scaleX = dampedDragAnimation.scaleX + scaleY = dampedDragAnimation.scaleY + val velocity = dampedDragAnimation.velocity / 10f + scaleX /= 1f - (velocity * 0.75f).fastCoerceIn(-0.2f, 0.2f) + scaleY *= 1f - (velocity * 0.25f).fastCoerceIn(-0.2f, 0.2f) + }, + onDrawSurface = { + val progress = dampedDragAnimation.pressProgress + drawRect( + if (isLightTheme) Color.Black.copy(0.1f) + else Color.White.copy(0.1f), + alpha = 1f - progress + ) + drawRect(Color.Black.copy(alpha = 0.03f * progress)) + } + ) + .align(Alignment.CenterStart) + .height(56.dp) + .width(CupertinoNavigationBarItemWidth) + ) } } } - */ - @Composable fun RowScope.CupertinoNavigationBarItem( onClick: () -> Unit, @@ -446,7 +373,7 @@ fun RowScope.CupertinoNavigationBarItem( val scale = LocalLiquidBottomTabScale.current Column( - verticalArrangement = Arrangement.spacedBy(2.dp, Alignment.CenterVertically), + verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterVertically), horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier .clip(Capsule()) @@ -458,21 +385,21 @@ fun RowScope.CupertinoNavigationBarItem( onClick = onClick ) .fillMaxHeight() - .weight(1f) + .width(CupertinoNavigationBarItemWidth) .graphicsLayer { - val scale = scale() - scaleX = scale - scaleY = scale + val s = scale() + scaleX = s + scaleY = s } ) { Box( contentAlignment = Alignment.Center, - modifier = Modifier.size(28.dp) + modifier = Modifier.size(18.dp) ) { icon() } ProvideTextStyle( - value = TextStyle(fontSize = 12.sp) + value = TextStyle(fontSize = 10.sp, fontWeight = FontWeight.Bold) ) { label?.invoke() } @@ -491,34 +418,16 @@ class CupertinoNavigationBarColors internal constructor( private val disabledIconColor: Color, private val disabledTextColor: Color, ) { - /** - * Represents the icon color for this item, depending on whether it is [selected]. - * - * @param selected whether the item is selected - * @param enabled whether the item is enabled - */ @Composable - internal fun iconColor( - selected: Boolean, - enabled: Boolean, - ): Color = + internal fun iconColor(selected: Boolean, enabled: Boolean): Color = when { !enabled -> disabledIconColor selected -> selectedIconColor else -> unselectedIconColor } - /** - * Represents the text color for this item, depending on whether it is [selected]. - * - * @param selected whether the item is selected - * @param enabled whether the item is enabled - */ @Composable - internal fun textColor( - selected: Boolean, - enabled: Boolean, - ): Color = + internal fun textColor(selected: Boolean, enabled: Boolean): Color = when { !enabled -> disabledTextColor selected -> selectedTextColor @@ -528,7 +437,6 @@ class CupertinoNavigationBarColors internal constructor( override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || other !is CupertinoNavigationBarColors) return false - if (selectedIconColor != other.selectedIconColor) return false if (unselectedIconColor != other.unselectedIconColor) return false if (selectedTextColor != other.selectedTextColor) return false @@ -544,25 +452,13 @@ class CupertinoNavigationBarColors internal constructor( result = 31 * result + unselectedTextColor.hashCode() result = 31 * result + disabledIconColor.hashCode() result = 31 * result + disabledTextColor.hashCode() - return result } } - @ExperimentalCupertinoApi @Immutable object CupertinoNavigationBarDefaults { - /** - * Default container color of the [CupertinoNavigationBar] - * - * Note: navigation bar itself does not produce cupertino thin material glass effect. - * This effect works only inside [CupertinoScaffold], [CupertinoBottomSheetScaffold], [CupertinoBottomSheetContent]. - * To achieve this effect with custom top app bar use [cupertinoTranslucentTopBarColor] - * function that will communicate with scaffold and return either - * [Color.Transparent] if color was successfully applied to scaffold (and top bar itself - * should be transparent) or passed color if scaffold wasn't found. - * */ val containerColor: Color @Composable @ReadOnlyComposable @@ -594,3 +490,4 @@ object CupertinoNavigationBarDefaults { } internal val LocalLiquidBottomTabScale = staticCompositionLocalOf { { 1f } } +internal val CupertinoNavigationBarItemWidth = 90.dp \ No newline at end of file From 045785dcc746e4d6849bc8c42cb26137498af5a5 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Sat, 2 May 2026 20:04:27 +0900 Subject: [PATCH 033/109] Update hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt index 4ff03683..7b18254a 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt @@ -124,7 +124,7 @@ fun CupertinoNavigationBar( .wrapContentWidth() .windowInsetsPadding(windowInsets) ) { - BoxWithConstraints( + Box( contentAlignment = Alignment.CenterStart, ) { val density = LocalDensity.current From 051f24ec666a93295f5645480158a7b030a3d5a2 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Sat, 2 May 2026 20:04:43 +0900 Subject: [PATCH 034/109] Update hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt index 7b18254a..02b40e9d 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt @@ -120,7 +120,7 @@ fun CupertinoNavigationBar( Box( modifier = modifier .fillMaxWidth() - .padding(bottom = 24.dp) + .padding(bottom = CupertinoNavigationBarDefaults.BottomPadding) .wrapContentWidth() .windowInsetsPadding(windowInsets) ) { From 30f555e341e8cdca906bfe7e92e735dcc4c2197e Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Sat, 2 May 2026 20:05:38 +0900 Subject: [PATCH 035/109] Update hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt index 02b40e9d..c0149857 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt @@ -105,7 +105,7 @@ private val NavBarItemGap = 0.dp // 아이템 사이 고정 간격. 필요 시 fun CupertinoNavigationBar( modifier: Modifier = Modifier, colors: CupertinoNavigationBarColors = CupertinoNavigationBarDefaults.colors(), - windowInsets: WindowInsets = WindowInsets.navigationBars, + windowInsets: WindowInsets = CupertinoNavigationBarDefaults.windowInsets, backdrop: LayerBackdrop, selectedTabIndex: () -> Int, onTabSelected: (index: Int) -> Unit, From 41455b9f8105c4ded96a0b278c466fd4159c39ac Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Sat, 2 May 2026 20:07:51 +0900 Subject: [PATCH 036/109] =?UTF-8?q?=F0=9F=93=9D=20docs(navigation):=20Cupe?= =?UTF-8?q?rtinoNavigationBar=20KDoc=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?=ED=8C=A8=EB=94=A9=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CupertinoNavigationBar 컴포저블에 파라미터 상세 설명을 위한 KDoc 주석을 추가합니다. - 하단 레이아웃 구성을 위한 BottomPadding 변수를 추가합니다. --- .../kotlin/zone/ien/hig/CupertinoNavigationBar.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt index c0149857..6e935977 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt @@ -100,6 +100,12 @@ import kotlin.math.sign private val NavBarPadding = 4.dp private val NavBarItemGap = 0.dp // 아이템 사이 고정 간격. 필요 시 조절 +/** + * Cupertino 스타일의 리퀴드 네비게이션 바입니다. + * + * @param tabsCount 네비게이션 아이템의 총 개수입니다. [content] 내의 아이템 개수와 일치해야 합니다. + * @param content 네비게이션 아이템들을 포함하는 블록입니다. + */ @Composable @ExperimentalCupertinoApi fun CupertinoNavigationBar( @@ -487,6 +493,7 @@ object CupertinoNavigationBarDefaults { ) val windowInsets = WindowInsets(left = 36.dp, right = 36.dp) + val BottomPadding = 24.dp } internal val LocalLiquidBottomTabScale = staticCompositionLocalOf { { 1f } } From 4e03fe13d667ea2fb53d65a069cd39d0f69eb352 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Sat, 2 May 2026 20:13:22 +0900 Subject: [PATCH 037/109] =?UTF-8?q?=F0=9F=94=A7=20chore(deps):=20=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EB=B0=8F=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=EC=A0=9D=ED=8A=B8=20=EB=B2=84=EC=A0=84=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Kotlin 버전을 2.3.20에서 2.3.21로 업데이트합니다. - Compose Navigation3 버전을 1.0.0-alpha06에서 1.1.0으로 업데이트합니다. - 라이브러리 배포 버전(lib-version-name)을 1.1.0-alpha01로 변경합니다. --- gradle/libs.versions.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dc839110..70cb020a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,12 +1,12 @@ [versions] agp = "9.0.0" -kotlin = "2.3.20" -lib-version-name = "1.0.2-alpha17" +kotlin = "2.3.21" +lib-version-name = "1.1.0-alpha01" # plugin compose-plugin = "1.10.3" compose-plugin-experimental = "1.10.0-alpha05" -compose-navigation3 = "1.0.0-alpha06" +compose-navigation3 = "1.1.0" # ui activity-compose = "1.13.0" From 16bdba8e7f3268c8a18f5f6173732719f76e1937 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Sat, 2 May 2026 22:22:04 +0900 Subject: [PATCH 038/109] =?UTF-8?q?=E2=9C=A8=20feat(ui):=20CupertinoNaviga?= =?UTF-8?q?tionBar=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20=EB=B2=84=EC=A0=84?= =?UTF-8?q?=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CupertinoNavigationBarItem 라벨의 TextStyle에 lineHeight 속성 추가 - 예제 앱에서 AdaptiveNavigationBar 대신 CupertinoNavigationBar 사용 - 라이브러리 버전을 1.1.0-alpha01에서 1.1.0-alpha02로 상향 업데이트 --- .../cupertino/CupertinoWidgetsScreen.kt | 36 ++++++++++++------- gradle/libs.versions.toml | 2 +- .../zone/ien/hig/CupertinoNavigationBar.kt | 2 +- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt index 6510ecfe..7441ee5d 100644 --- a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt +++ b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt @@ -45,6 +45,7 @@ import RootUiState import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.AnchoredDraggableState import androidx.compose.foundation.gestures.ScrollableState @@ -68,6 +69,10 @@ import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Save +import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -126,6 +131,8 @@ import zone.ien.hig.CupertinoLiquidButtonDefaults import zone.ien.hig.CupertinoLiquidIconButton import zone.ien.hig.CupertinoMenuItemData import zone.ien.hig.CupertinoMenuSectionData +import zone.ien.hig.CupertinoNavigationBar +import zone.ien.hig.CupertinoNavigationBarItem import zone.ien.hig.CupertinoNavigationBarItemData import zone.ien.hig.CupertinoNavigationTitle import zone.ien.hig.CupertinoPickerState @@ -860,9 +867,11 @@ private fun BottomBarSample( ) { var tab by remember { mutableStateOf(0) } val content = listOf( - "Profile" to AdaptiveIcons.Outlined.Person, - "Menu" to AdaptiveIcons.Outlined.Menu, - "Settings" to AdaptiveIcons.Outlined.Settings, + "Profile" to Icons.Default.Delete, + "Menu" to Icons.Default.Save, +// "Profile" to AdaptiveIcons.Outlined.Person, +// "Menu" to AdaptiveIcons.Outlined.Menu, +// "Settings" to AdaptiveIcons.Outlined.Settings, ) if (isNative) { @@ -881,26 +890,29 @@ private fun BottomBarSample( } ) } else { - AdaptiveNavigationBar( + CupertinoNavigationBar( +// AdaptiveNavigationBar( selectedTabIndex = { tab }, onTabSelected = { tab = it }, - tabsCount = 3, - adaptation = { - cupertino { this.backdrop = backdrop } - }, + tabsCount = content.size, + backdrop = backdrop, +// adaptation = { +// cupertino { this.backdrop = backdrop } +// }, ) { content.forEachIndexed { index, pair -> - AdaptiveNavigationBarItem( - index = index, + CupertinoNavigationBarItem( +// AdaptiveNavigationBarItem( +// index = index, onClick = { tab = index }, icon = { - CupertinoIcon( + Icon( imageVector = pair.second, contentDescription = pair.first, ) }, label = { - Text(pair.first) + Text(pair.first, modifier= Modifier.background(Color.Red)) }, ) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 70cb020a..0e40077d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "9.0.0" kotlin = "2.3.21" -lib-version-name = "1.1.0-alpha01" +lib-version-name = "1.1.0-alpha02" # plugin compose-plugin = "1.10.3" diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt index 6e935977..cb313b21 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt @@ -405,7 +405,7 @@ fun RowScope.CupertinoNavigationBarItem( icon() } ProvideTextStyle( - value = TextStyle(fontSize = 10.sp, fontWeight = FontWeight.Bold) + value = TextStyle(fontSize = 10.sp, lineHeight = 10.sp, fontWeight = FontWeight.Bold) ) { label?.invoke() } From b121649f69f2da9cd5e3ed31fb121ce115635433 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Sun, 3 May 2026 01:54:40 +0900 Subject: [PATCH 039/109] =?UTF-8?q?=E2=9C=A8=20feat(ui):=20CupertinoNaviga?= =?UTF-8?q?tionBar=20=EC=95=84=EC=9D=B4=ED=85=9C=20=EB=84=88=EB=B9=84=20?= =?UTF-8?q?=EB=8F=99=EC=A0=81=20=EA=B3=84=EC=82=B0=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BoxWithConstraints를 활용해 가용 너비에 따른 아이템 너비 자동 계산 추가 - 아이템 개수가 적을 때 최소 너비를 보장하고 공간 부족 시 균등 분할 처리 - CompositionLocal을 사용하여 하위 아이템에 동적 너비 정보 전달 - 라이브러리 버전을 1.1.0-alpha03으로 업데이트 --- .../cupertino/CupertinoWidgetsScreen.kt | 4 +- gradle/libs.versions.toml | 2 +- .../zone/ien/hig/CupertinoNavigationBar.kt | 259 ++++++++++-------- 3 files changed, 143 insertions(+), 122 deletions(-) diff --git a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt index 7441ee5d..7f3f1010 100644 --- a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt +++ b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt @@ -868,7 +868,7 @@ private fun BottomBarSample( var tab by remember { mutableStateOf(0) } val content = listOf( "Profile" to Icons.Default.Delete, - "Menu" to Icons.Default.Save, +// "Menu" to Icons.Default.Save, // "Profile" to AdaptiveIcons.Outlined.Person, // "Menu" to AdaptiveIcons.Outlined.Menu, // "Settings" to AdaptiveIcons.Outlined.Settings, @@ -912,7 +912,7 @@ private fun BottomBarSample( ) }, label = { - Text(pair.first, modifier= Modifier.background(Color.Red)) + Text(pair.first) }, ) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0e40077d..bc0e7596 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "9.0.0" kotlin = "2.3.21" -lib-version-name = "1.1.0-alpha02" +lib-version-name = "1.1.0-alpha03" # plugin compose-plugin = "1.10.3" diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt index cb313b21..b465b255 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt @@ -39,6 +39,7 @@ import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.runtime.Composable @@ -69,6 +70,7 @@ import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -98,7 +100,8 @@ import kotlin.math.abs import kotlin.math.sign private val NavBarPadding = 4.dp -private val NavBarItemGap = 0.dp // 아이템 사이 고정 간격. 필요 시 조절 +private val NavBarItemGap = 0.dp +private val NavBarItemMinWidth = 90.dp // 아이템이 소수일 때의 고정 너비 /** * Cupertino 스타일의 리퀴드 네비게이션 바입니다. @@ -130,25 +133,37 @@ fun CupertinoNavigationBar( .wrapContentWidth() .windowInsetsPadding(windowInsets) ) { - Box( + // BoxWithConstraints로 windowInsets 적용 후 실제 가용 너비를 측정 + BoxWithConstraints( contentAlignment = Alignment.CenterStart, ) { val density = LocalDensity.current val paddingPx = with(density) { NavBarPadding.toPx() } - val itemWidthPx = with(density) { CupertinoNavigationBarItemWidth.toPx() } val gapPx = with(density) { NavBarItemGap.toPx() } + val minItemWidthPx = with(density) { NavBarItemMinWidth.toPx() } + + // 가용 너비 기준으로 균등 분할 시 아이템 너비 계산 + // NavBar 전체 너비 = padding*2 + itemWidth*n + gap*(n-1) + // → itemWidth = (availableWidth - padding*2 - gap*(n-1)) / n + val availableWidthPx = constraints.maxWidth.toFloat() + val calculatedItemWidthPx = + (availableWidthPx - paddingPx * 2f - gapPx * (tabsCount - 1)) / tabsCount + + // 균등 분할 너비가 최솟값(90dp) 이상이면 고정, 미만이면 균등 분할 + val itemWidthPx = if (calculatedItemWidthPx >= minItemWidthPx) { + minItemWidthPx + } else { + calculatedItemWidthPx + } + val itemWidthDp: Dp = with(density) { itemWidthPx.toDp() } // NavBar 전체 너비 = padding*2 + itemWidth*n + gap*(n-1) - // CupertinoNavigationBarItemWidth 바꾸면 이 값이 자동으로 연동됨 val rowWidth = paddingPx * 2f + itemWidthPx * tabsCount + gapPx * (tabsCount - 1) - // 아이템 i 왼쪽 X (BoxWithConstraints 절대좌표) - // = paddingPx + (itemWidthPx + gapPx) * i fun itemLeftX(index: Float): Float = paddingPx + (itemWidthPx + gapPx) * index fun itemCenterX(index: Float): Float = itemLeftX(index) + itemWidthPx / 2f - // indicator 이동 step = itemWidth + gap val tabStep = itemWidthPx + gapPx val offsetAnimation = remember { Animatable(0f) } @@ -227,50 +242,13 @@ fun CupertinoNavigationBar( ) } - // ── 배경 Row ────────────────────────────────────────────────────────── - Row( - modifier = Modifier - .graphicsLayer { - translationX = panelOffset - } - .drawBackdrop( - backdrop = backdrop, - shape = { Capsule() }, - effects = { - vibrancy() - blur(2.dp.toPx()) - lens(24.dp.toPx(), 24.dp.toPx()) - }, - layerBlock = { - val progress = dampedDragAnimation.pressProgress - val scale = lerp(1f, 1f + 16.dp.toPx() / size.width, progress) - scaleX = scale - scaleY = scale - }, - onDrawSurface = { - drawRect(containerColor) - } - ) - .then(interactiveHighlight.modifier) - .wrapContentWidth() - .height(64.dp) - .padding(NavBarPadding), - horizontalArrangement = Arrangement.spacedBy(NavBarItemGap), - verticalAlignment = Alignment.CenterVertically, - content = content - ) - - // ── 액센트 컬러 오버레이 Row ────────────────────────────────────────── + // CompositionLocal로 동적 itemWidthDp 전달 CompositionLocalProvider( - LocalLiquidBottomTabScale provides { - lerp(1f, 1.2f, dampedDragAnimation.pressProgress) - } + LocalCupertinoNavItemWidth provides itemWidthDp, ) { + // ── 배경 Row ────────────────────────────────────────────────────────── Row( modifier = Modifier - .clearAndSetSemantics {} - .alpha(0f) - .layerBackdrop(tabsBackdrop) .graphicsLayer { translationX = panelOffset } @@ -278,91 +256,133 @@ fun CupertinoNavigationBar( backdrop = backdrop, shape = { Capsule() }, effects = { - val progress = dampedDragAnimation.pressProgress vibrancy() - blur(8.dp.toPx()) - lens( - 24.dp.toPx() * progress, - 24.dp.toPx() * progress - ) + blur(2.dp.toPx()) + lens(24.dp.toPx(), 24.dp.toPx()) }, - highlight = { + layerBlock = { val progress = dampedDragAnimation.pressProgress - Highlight.Default.copy(alpha = progress) + val scale = lerp(1f, 1f + 16.dp.toPx() / size.width, progress) + scaleX = scale + scaleY = scale }, - onDrawSurface = {} + onDrawSurface = { + drawRect(containerColor) + } ) .then(interactiveHighlight.modifier) .wrapContentWidth() - .height(56.dp) - .padding(horizontal = NavBarPadding) - .graphicsLayer(colorFilter = ColorFilter.tint(accentColor)), + .height(64.dp) + .padding(NavBarPadding), horizontalArrangement = Arrangement.spacedBy(NavBarItemGap), verticalAlignment = Alignment.CenterVertically, content = content ) - } - // ── 슬라이딩 선택 indicator Box ─────────────────────────────────────── - Box( - modifier = Modifier - .graphicsLayer { - val leftX = itemLeftX(dampedDragAnimation.value) - translationX = if (isLtr) { - leftX + panelOffset - } else { - rowWidth - leftX - itemWidthPx + panelOffset - } + // ── 액센트 컬러 오버레이 Row ────────────────────────────────────────── + CompositionLocalProvider( + LocalLiquidBottomTabScale provides { + lerp(1f, 1.2f, dampedDragAnimation.pressProgress) } - .then(interactiveHighlight.gestureModifier) - .then(dampedDragAnimation.modifier) - .drawBackdrop( - backdrop = rememberCombinedBackdrop(backdrop, tabsBackdrop), - shape = { Capsule() }, - effects = { - val progress = dampedDragAnimation.pressProgress - lens( - 10.dp.toPx() * progress, - 14.dp.toPx() * progress, - chromaticAberration = true - ) - }, - highlight = { - val progress = dampedDragAnimation.pressProgress - Highlight.Default.copy(alpha = progress) - }, - shadow = { - val progress = dampedDragAnimation.pressProgress - Shadow(alpha = progress) - }, - innerShadow = { - val progress = dampedDragAnimation.pressProgress - InnerShadow( - radius = 8.dp * progress, - alpha = progress + ) { + Row( + modifier = Modifier + .clearAndSetSemantics {} + .alpha(0f) + .layerBackdrop(tabsBackdrop) + .graphicsLayer { + translationX = panelOffset + } + .drawBackdrop( + backdrop = backdrop, + shape = { Capsule() }, + effects = { + val progress = dampedDragAnimation.pressProgress + vibrancy() + blur(8.dp.toPx()) + lens( + 24.dp.toPx() * progress, + 24.dp.toPx() * progress + ) + }, + highlight = { + val progress = dampedDragAnimation.pressProgress + Highlight.Default.copy(alpha = progress) + }, + onDrawSurface = {} ) - }, - layerBlock = { - scaleX = dampedDragAnimation.scaleX - scaleY = dampedDragAnimation.scaleY - val velocity = dampedDragAnimation.velocity / 10f - scaleX /= 1f - (velocity * 0.75f).fastCoerceIn(-0.2f, 0.2f) - scaleY *= 1f - (velocity * 0.25f).fastCoerceIn(-0.2f, 0.2f) - }, - onDrawSurface = { - val progress = dampedDragAnimation.pressProgress - drawRect( - if (isLightTheme) Color.Black.copy(0.1f) - else Color.White.copy(0.1f), - alpha = 1f - progress - ) - drawRect(Color.Black.copy(alpha = 0.03f * progress)) - } + .then(interactiveHighlight.modifier) + .wrapContentWidth() + .height(56.dp) + .padding(horizontal = NavBarPadding) + .graphicsLayer(colorFilter = ColorFilter.tint(accentColor)), + horizontalArrangement = Arrangement.spacedBy(NavBarItemGap), + verticalAlignment = Alignment.CenterVertically, + content = content ) - .align(Alignment.CenterStart) - .height(56.dp) - .width(CupertinoNavigationBarItemWidth) - ) + } + + // ── 슬라이딩 선택 indicator Box ─────────────────────────────────────── + Box( + modifier = Modifier + .graphicsLayer { + val leftX = itemLeftX(dampedDragAnimation.value) + translationX = if (isLtr) { + leftX + panelOffset + } else { + rowWidth - leftX - itemWidthPx + panelOffset + } + } + .then(interactiveHighlight.gestureModifier) + .then(dampedDragAnimation.modifier) + .drawBackdrop( + backdrop = rememberCombinedBackdrop(backdrop, tabsBackdrop), + shape = { Capsule() }, + effects = { + val progress = dampedDragAnimation.pressProgress + lens( + 10.dp.toPx() * progress, + 14.dp.toPx() * progress, + chromaticAberration = true + ) + }, + highlight = { + val progress = dampedDragAnimation.pressProgress + Highlight.Default.copy(alpha = progress) + }, + shadow = { + val progress = dampedDragAnimation.pressProgress + Shadow(alpha = progress) + }, + innerShadow = { + val progress = dampedDragAnimation.pressProgress + InnerShadow( + radius = 8.dp * progress, + alpha = progress + ) + }, + layerBlock = { + scaleX = dampedDragAnimation.scaleX + scaleY = dampedDragAnimation.scaleY + val velocity = dampedDragAnimation.velocity / 10f + scaleX /= 1f - (velocity * 0.75f).fastCoerceIn(-0.2f, 0.2f) + scaleY *= 1f - (velocity * 0.25f).fastCoerceIn(-0.2f, 0.2f) + }, + onDrawSurface = { + val progress = dampedDragAnimation.pressProgress + drawRect( + if (isLightTheme) Color.Black.copy(0.1f) + else Color.White.copy(0.1f), + alpha = 1f - progress + ) + drawRect(Color.Black.copy(alpha = 0.03f * progress)) + } + ) + .align(Alignment.CenterStart) + .height(56.dp) + .width(itemWidthDp) // ★ 동적 너비 적용 + ) + } } } } @@ -377,6 +397,7 @@ fun RowScope.CupertinoNavigationBarItem( interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, ) { val scale = LocalLiquidBottomTabScale.current + val itemWidth = LocalCupertinoNavItemWidth.current // ★ CompositionLocal에서 동적 너비 읽기 Column( verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterVertically), @@ -391,7 +412,7 @@ fun RowScope.CupertinoNavigationBarItem( onClick = onClick ) .fillMaxHeight() - .width(CupertinoNavigationBarItemWidth) + .width(itemWidth) // ★ 고정 90.dp 대신 동적 너비 사용 .graphicsLayer { val s = scale() scaleX = s @@ -497,4 +518,4 @@ object CupertinoNavigationBarDefaults { } internal val LocalLiquidBottomTabScale = staticCompositionLocalOf { { 1f } } -internal val CupertinoNavigationBarItemWidth = 90.dp \ No newline at end of file +internal val LocalCupertinoNavItemWidth = staticCompositionLocalOf { 90.dp } // ★ 동적 너비 전달용 \ No newline at end of file From 3b08e20103d41e5a0ea5565664c246e6f16d02e5 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Thu, 7 May 2026 02:00:10 +0900 Subject: [PATCH 040/109] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(core):=20?= =?UTF-8?q?CupertinoTextField=20=ED=94=8C=EB=9E=AB=ED=8F=BC=EB=B3=84=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CupertinoTextField에 iOS 및 non-IOS 플랫폼별 구현 추가 - KeyboardOptions.enableNativeInput 함수를 통해 네이티브 입력 활성화 로직 구현 (현재는 비활성화) - compose-plugin 및 관련 라이브러리 버전 업데이트 --- gradle/libs.versions.toml | 8 ++++---- .../zone/ien/hig/CupertinoSearchTextField.kt | 2 +- .../kotlin/zone/ien/hig/CupertinoTextField.kt | 18 ++++++++++-------- .../zone/ien/hig/section/LazySectionScope.kt | 3 ++- .../zone/ien/hig/section/SectionScope.kt | 3 ++- .../zone/ien/hig/CupertinoTextField.ios.kt | 8 ++++++++ .../zone/ien/hig/CupertinoTextField.nonIos.kt | 5 +++++ .../kotlin/zone/ien/hig/HazePlatform.skiko.kt | 2 ++ settings.gradle.kts | 4 ++-- 9 files changed, 36 insertions(+), 17 deletions(-) create mode 100644 hig/src/iosMain/kotlin/zone/ien/hig/CupertinoTextField.ios.kt create mode 100644 hig/src/nonIosMain/kotlin/zone/ien/hig/CupertinoTextField.nonIos.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bc0e7596..9c49974d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,14 +4,14 @@ kotlin = "2.3.21" lib-version-name = "1.1.0-alpha03" # plugin -compose-plugin = "1.10.3" -compose-plugin-experimental = "1.10.0-alpha05" -compose-navigation3 = "1.1.0" +compose-plugin = "1.11.0-rc01" +compose-plugin-experimental = "1.11.0-alpha07" +compose-navigation3 = "1.1.1" # ui activity-compose = "1.13.0" lifecycle = "2.10.0" -backdrop = "2.0.0-alpha05" +backdrop = "2.0.0-alpha06" capsule = "1.2.0" haze = "1.7.2" diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoSearchTextField.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoSearchTextField.kt index 9ea7ea86..c2e3e817 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoSearchTextField.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoSearchTextField.kt @@ -286,7 +286,7 @@ fun CupertinoSearchTextField( colors = colors, textStyle = textStyle, shape = shape, - keyboardOptions = keyboardOptions, + keyboardOptions = keyboardOptions.enableNativeInput(), keyboardActions = keyboardActions, singleLine = true, maxLines = 1, diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoTextField.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoTextField.kt index b55c2db1..14b28301 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoTextField.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoTextField.kt @@ -74,6 +74,8 @@ import zone.ien.hig.theme.CupertinoColors import zone.ien.hig.theme.CupertinoTheme import zone.ien.hig.theme.systemRed +internal expect fun KeyboardOptions.enableNativeInput(): KeyboardOptions + @Composable fun CupertinoTextField( value: String, @@ -121,7 +123,7 @@ fun CupertinoTextField( textStyle = mergedTextStyle, cursorBrush = SolidColor(colors.cursorColor(isError).value), visualTransformation = visualTransformation, - keyboardOptions = keyboardOptions, + keyboardOptions = keyboardOptions.enableNativeInput(), keyboardActions = keyboardActions, interactionSource = interactionSource, singleLine = singleLine, @@ -196,7 +198,7 @@ fun CupertinoTextField( textStyle = mergedTextStyle, cursorBrush = SolidColor(colors.cursorColor(isError).value), visualTransformation = visualTransformation, - keyboardOptions = keyboardOptions, + keyboardOptions = keyboardOptions.enableNativeInput(), keyboardActions = keyboardActions, interactionSource = interactionSource, singleLine = singleLine, @@ -267,7 +269,7 @@ fun CupertinoTextField( inputTransformation = inputTransformation, textStyle = mergedTextStyle, cursorBrush = SolidColor(colors.cursorColor(isError).value), - keyboardOptions = keyboardOptions, + keyboardOptions = keyboardOptions.enableNativeInput(), onKeyboardAction = onKeyboardAction, lineLimits = lineLimits, interactionSource = interactionSource, @@ -340,7 +342,7 @@ fun CupertinoSecureTextField( inputTransformation = inputTransformation, textStyle = mergedTextStyle, cursorBrush = SolidColor(colors.cursorColor(isError).value), - keyboardOptions = keyboardOptions, + keyboardOptions = keyboardOptions.enableNativeInput(), onKeyboardAction = onKeyboardAction, interactionSource = interactionSource, onTextLayout = { @@ -417,7 +419,7 @@ fun CupertinoBorderedTextField( trailingIcon = trailingIcon, isError = isError, visualTransformation = visualTransformation, - keyboardOptions = keyboardOptions, + keyboardOptions = keyboardOptions.enableNativeInput(), keyboardActions = keyboardActions, singleLine = singleLine, maxLines = maxLines, @@ -475,7 +477,7 @@ fun CupertinoBorderedTextField( trailingIcon = trailingIcon, isError = isError, visualTransformation = visualTransformation, - keyboardOptions = keyboardOptions, + keyboardOptions = keyboardOptions.enableNativeInput(), keyboardActions = keyboardActions, singleLine = singleLine, maxLines = maxLines, @@ -530,7 +532,7 @@ fun CupertinoBorderedTextField( leadingIcon = leadingIcon, trailingIcon = trailingIcon, isError = isError, - keyboardOptions = keyboardOptions, + keyboardOptions = keyboardOptions.enableNativeInput(), onKeyboardAction = onKeyboardAction, interactionSource = interactionSource, contentAlignment = contentAlignment, @@ -582,7 +584,7 @@ fun CupertinoBorderedSecureTextField( leadingIcon = leadingIcon, trailingIcon = trailingIcon, isError = isError, - keyboardOptions = keyboardOptions, + keyboardOptions = keyboardOptions.enableNativeInput(), onKeyboardAction = onKeyboardAction, interactionSource = interactionSource, contentAlignment = contentAlignment, diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/section/LazySectionScope.kt b/hig/src/commonMain/kotlin/zone/ien/hig/section/LazySectionScope.kt index 8804f955..01b69498 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/section/LazySectionScope.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/section/LazySectionScope.kt @@ -90,6 +90,7 @@ import zone.ien.hig.icons.outlined.ChevronUp import zone.ien.hig.theme.CupertinoTheme import zone.ien.hig.toStringWithLeadingZero import zone.ien.hig.defaultLocale +import zone.ien.hig.enableNativeInput @Stable sealed interface LazySectionScope { @@ -495,7 +496,7 @@ fun LazySectionScope.textField( enabled = enabled, readOnly = readOnly, visualTransformation = visualTransformation, - keyboardOptions = keyboardOptions, + keyboardOptions = keyboardOptions.enableNativeInput(), keyboardActions = keyboardActions, singleLine = singleLine, maxLines = maxLines, diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/section/SectionScope.kt b/hig/src/commonMain/kotlin/zone/ien/hig/section/SectionScope.kt index bf5d9a47..93de1225 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/section/SectionScope.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/section/SectionScope.kt @@ -82,6 +82,7 @@ import zone.ien.hig.icons.outlined.ChevronUp import zone.ien.hig.theme.CupertinoTheme import zone.ien.hig.toStringWithLeadingZero import zone.ien.hig.defaultLocale +import zone.ien.hig.enableNativeInput @Stable internal object SectionScopeImpl: SectionScope @@ -392,7 +393,7 @@ fun SectionScope.SectionTextField( enabled = enabled, readOnly = readOnly, visualTransformation = visualTransformation, - keyboardOptions = keyboardOptions, + keyboardOptions = keyboardOptions.enableNativeInput(), keyboardActions = keyboardActions, singleLine = singleLine, maxLines = maxLines, diff --git a/hig/src/iosMain/kotlin/zone/ien/hig/CupertinoTextField.ios.kt b/hig/src/iosMain/kotlin/zone/ien/hig/CupertinoTextField.ios.kt new file mode 100644 index 00000000..4917bf50 --- /dev/null +++ b/hig/src/iosMain/kotlin/zone/ien/hig/CupertinoTextField.ios.kt @@ -0,0 +1,8 @@ +package zone.ien.hig + +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.text.input.PlatformImeOptions + +@OptIn(ExperimentalComposeUiApi::class) +internal actual fun KeyboardOptions.enableNativeInput() = copy()//copy(platformImeOptions = PlatformImeOptions { usingNativeTextInput(true) }) \ No newline at end of file diff --git a/hig/src/nonIosMain/kotlin/zone/ien/hig/CupertinoTextField.nonIos.kt b/hig/src/nonIosMain/kotlin/zone/ien/hig/CupertinoTextField.nonIos.kt new file mode 100644 index 00000000..d1a49b36 --- /dev/null +++ b/hig/src/nonIosMain/kotlin/zone/ien/hig/CupertinoTextField.nonIos.kt @@ -0,0 +1,5 @@ +package zone.ien.hig + +import androidx.compose.foundation.text.KeyboardOptions + +internal actual fun KeyboardOptions.enableNativeInput(): KeyboardOptions = copy() \ No newline at end of file diff --git a/hig/src/skikoMain/kotlin/zone/ien/hig/HazePlatform.skiko.kt b/hig/src/skikoMain/kotlin/zone/ien/hig/HazePlatform.skiko.kt index c848411c..08eb1015 100644 --- a/hig/src/skikoMain/kotlin/zone/ien/hig/HazePlatform.skiko.kt +++ b/hig/src/skikoMain/kotlin/zone/ien/hig/HazePlatform.skiko.kt @@ -22,6 +22,7 @@ package zone.ien.hig // Copyright 2023, Christopher Banes and the Haze project contributors // SPDX-License-Identifier: Apache-2.0 +import androidx.compose.ui.InternalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.BlurEffect @@ -118,6 +119,7 @@ internal actual class HazeNode actual constructor( } } + @OptIn(InternalComposeUiApi::class) private fun createBlurImageFilter(blurRadius: Dp): ImageFilter { val blurRadiusPx = with(density) { diff --git a/settings.gradle.kts b/settings.gradle.kts index cd6eed93..f3d1161a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,7 +22,7 @@ pluginManagement { google() gradlePluginPortal() mavenCentral() -// mavenLocal() + mavenLocal() } } @@ -31,7 +31,7 @@ dependencyResolutionManagement { repositories { google() mavenCentral() -// mavenLocal() + mavenLocal() } } From 3e79e5169ef6ce1161e4863bd2317c219fdda08d Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Thu, 7 May 2026 02:42:33 +0900 Subject: [PATCH 041/109] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(core):=20?= =?UTF-8?q?=ED=82=A4=EB=B3=B4=EB=93=9C=20=EC=98=B5=EC=85=98=20native=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=20=EA=B4=80=EB=A0=A8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - enableNativeInput 함수 구현을 iOS와 non-IOS 플랫폼별로 분리하여 코드 정리 - keyboardOptions에 대한 native 입력 관련 설정을 각 플랫폼별로 명확하게 처리 - 기존 enableNativeInput 함수 호출 방식을 단순화하여 코드 간결화 --- .../kotlin/zone/ien/hig/CupertinoSearchTextField.kt | 2 +- hig/src/commonMain/kotlin/zone/ien/hig/CupertinoTextField.kt | 2 +- .../kotlin/zone/ien/hig/section/LazySectionScope.kt | 2 +- .../commonMain/kotlin/zone/ien/hig/section/SectionScope.kt | 2 +- hig/src/iosMain/kotlin/zone/ien/hig/CupertinoTextField.ios.kt | 4 +++- .../kotlin/zone/ien/hig/CupertinoTextField.nonIos.kt | 2 +- settings.gradle.kts | 4 ++-- 7 files changed, 10 insertions(+), 8 deletions(-) diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoSearchTextField.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoSearchTextField.kt index c2e3e817..9ea7ea86 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoSearchTextField.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoSearchTextField.kt @@ -286,7 +286,7 @@ fun CupertinoSearchTextField( colors = colors, textStyle = textStyle, shape = shape, - keyboardOptions = keyboardOptions.enableNativeInput(), + keyboardOptions = keyboardOptions, keyboardActions = keyboardActions, singleLine = true, maxLines = 1, diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoTextField.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoTextField.kt index 14b28301..8a7af482 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoTextField.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoTextField.kt @@ -419,7 +419,7 @@ fun CupertinoBorderedTextField( trailingIcon = trailingIcon, isError = isError, visualTransformation = visualTransformation, - keyboardOptions = keyboardOptions.enableNativeInput(), + keyboardOptions = keyboardOptions, keyboardActions = keyboardActions, singleLine = singleLine, maxLines = maxLines, diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/section/LazySectionScope.kt b/hig/src/commonMain/kotlin/zone/ien/hig/section/LazySectionScope.kt index 01b69498..b84ebc80 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/section/LazySectionScope.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/section/LazySectionScope.kt @@ -496,7 +496,7 @@ fun LazySectionScope.textField( enabled = enabled, readOnly = readOnly, visualTransformation = visualTransformation, - keyboardOptions = keyboardOptions.enableNativeInput(), + keyboardOptions = keyboardOptions, keyboardActions = keyboardActions, singleLine = singleLine, maxLines = maxLines, diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/section/SectionScope.kt b/hig/src/commonMain/kotlin/zone/ien/hig/section/SectionScope.kt index 93de1225..bc2321c8 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/section/SectionScope.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/section/SectionScope.kt @@ -393,7 +393,7 @@ fun SectionScope.SectionTextField( enabled = enabled, readOnly = readOnly, visualTransformation = visualTransformation, - keyboardOptions = keyboardOptions.enableNativeInput(), + keyboardOptions = keyboardOptions, keyboardActions = keyboardActions, singleLine = singleLine, maxLines = maxLines, diff --git a/hig/src/iosMain/kotlin/zone/ien/hig/CupertinoTextField.ios.kt b/hig/src/iosMain/kotlin/zone/ien/hig/CupertinoTextField.ios.kt index 4917bf50..72a345d7 100644 --- a/hig/src/iosMain/kotlin/zone/ien/hig/CupertinoTextField.ios.kt +++ b/hig/src/iosMain/kotlin/zone/ien/hig/CupertinoTextField.ios.kt @@ -5,4 +5,6 @@ import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.text.input.PlatformImeOptions @OptIn(ExperimentalComposeUiApi::class) -internal actual fun KeyboardOptions.enableNativeInput() = copy()//copy(platformImeOptions = PlatformImeOptions { usingNativeTextInput(true) }) \ No newline at end of file +internal actual fun KeyboardOptions.enableNativeInput() = + this +// copy(platformImeOptions = PlatformImeOptions { usingNativeTextInput(true) }) \ No newline at end of file diff --git a/hig/src/nonIosMain/kotlin/zone/ien/hig/CupertinoTextField.nonIos.kt b/hig/src/nonIosMain/kotlin/zone/ien/hig/CupertinoTextField.nonIos.kt index d1a49b36..f93d9e26 100644 --- a/hig/src/nonIosMain/kotlin/zone/ien/hig/CupertinoTextField.nonIos.kt +++ b/hig/src/nonIosMain/kotlin/zone/ien/hig/CupertinoTextField.nonIos.kt @@ -2,4 +2,4 @@ package zone.ien.hig import androidx.compose.foundation.text.KeyboardOptions -internal actual fun KeyboardOptions.enableNativeInput(): KeyboardOptions = copy() \ No newline at end of file +internal actual fun KeyboardOptions.enableNativeInput(): KeyboardOptions = this \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index f3d1161a..cd6eed93 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,7 +22,7 @@ pluginManagement { google() gradlePluginPortal() mavenCentral() - mavenLocal() +// mavenLocal() } } @@ -31,7 +31,7 @@ dependencyResolutionManagement { repositories { google() mavenCentral() - mavenLocal() +// mavenLocal() } } From 77bd45a70e3dcb9f8521ac2a6eaf99fb166e2284 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Thu, 7 May 2026 02:43:25 +0900 Subject: [PATCH 042/109] =?UTF-8?q?=E2=9A=A1=20perf(core):=20=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EB=B2=84=EC=A0=84=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - libs.versions.toml 파일의 lib-version-name 버전을 1.1.0-alpha03에서 1.1.0-alpha04로 변경 - 의존성 버전 업데이트로 인한 성능 개선 및 버그 수정 - 빌드 시스템의 안정성 향상을 위한 최소한의 변경사항 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9c49974d..fff8dd46 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "9.0.0" kotlin = "2.3.21" -lib-version-name = "1.1.0-alpha03" +lib-version-name = "1.1.0-alpha04" # plugin compose-plugin = "1.11.0-rc01" From 9d7224c66f5657cc72feb1e6e99c49bf0bf4d39c Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Sat, 9 May 2026 00:28:27 +0900 Subject: [PATCH 043/109] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(core):=20?= =?UTF-8?q?=ED=85=8C=EB=A7=88=20=EA=B4=80=EB=A0=A8=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EB=AC=B8=EC=84=9C=ED=99=94=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ColorScheme, Typography, Shapes 등 테마 컴포넌트에 대한 문서화 추가 - CupertinoHapticFeedback, CupertinoTween 등 커스터마이징 컴포넌트의 API 명확화 - Accessibility 및 SystemBarAppearance 관련 코드 리팩토링 및 개선 --- .../kotlin/zone/ien/hig/Accessibility.kt | 37 ++++++++++++-- .../zone/ien/hig/CupertinoHapticFeedback.kt | 44 +++++++++++++++- .../kotlin/zone/ien/hig/CupertinoTween.kt | 14 ++++- .../zone/ien/hig/SystemBarAppearance.kt | 6 +++ .../zone/ien/hig/section/SectionScope.kt | 3 ++ .../kotlin/zone/ien/hig/theme/ColorScheme.kt | 51 +++++++++++++++++++ .../kotlin/zone/ien/hig/theme/Shapes.kt | 21 ++++++++ .../kotlin/zone/ien/hig/theme/Typography.kt | 30 +++++++++++ 8 files changed, 199 insertions(+), 7 deletions(-) diff --git a/hig-core/src/commonMain/kotlin/zone/ien/hig/Accessibility.kt b/hig-core/src/commonMain/kotlin/zone/ien/hig/Accessibility.kt index 581d7277..fe00e713 100644 --- a/hig-core/src/commonMain/kotlin/zone/ien/hig/Accessibility.kt +++ b/hig-core/src/commonMain/kotlin/zone/ien/hig/Accessibility.kt @@ -30,7 +30,22 @@ "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused" + "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", + "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", + "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", + "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", + "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", + "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", + "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", + "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", + "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", + "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", + "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", + "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", + "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", + "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", + "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", + "unused", "unused", "unused" ) package zone.ien.hig @@ -44,23 +59,37 @@ import zone.ien.hig.theme.isDark /** * This object provides access to native accessibility preferences - * */ + * + * It allows for checking system accessibility settings such as high contrast mode and + * reduced transparency. + */ object Accessibility +/** + * Checks if high contrast mode is enabled in the system. + */ expect val Accessibility.isHighContrastEnabled: Boolean +/** + * Checks if reduced transparency is enabled in the system. + */ expect val Accessibility.isReduceTransparencyEnabled: Boolean /** * Adjust color contrast if corresponding OS accessibility system preference is enabled - * */ + * + * @return the accessible color version based on system settings + */ val Color.accessible: Color @Composable get() = accessible(isDark()) /** * Adjust color contrast if corresponding accessibility system preference is enabled - * */ + * + * @param isDark whether the theme is dark or light + * @return the accessible color version based on system settings + */ fun Color.accessible(isDark: Boolean): Color = if (Accessibility.isHighContrastEnabled) { lerp(this, if (isDark) CupertinoColors.White else Color.Black, .2f) diff --git a/hig-core/src/commonMain/kotlin/zone/ien/hig/CupertinoHapticFeedback.kt b/hig-core/src/commonMain/kotlin/zone/ien/hig/CupertinoHapticFeedback.kt index ab5e14bb..00bdc5a4 100644 --- a/hig-core/src/commonMain/kotlin/zone/ien/hig/CupertinoHapticFeedback.kt +++ b/hig-core/src/commonMain/kotlin/zone/ien/hig/CupertinoHapticFeedback.kt @@ -24,24 +24,64 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.hapticfeedback.HapticFeedback import androidx.compose.ui.hapticfeedback.HapticFeedbackType +/** + * Creates a haptic feedback instance for Cupertino styling. + * + * @return a [HapticFeedback] instance for Cupertino styling + */ @Composable expect fun rememberCupertinoHapticFeedback(): HapticFeedback /** - * This haptic feedback types work only on iOS. They are available for public usage in iosMain as + * Cupertino haptic feedback types that work only on iOS. + * + * These haptic feedback types are available for public usage in iosMain as * extension properties of [HapticFeedbackType.Companion] - * */ + */ @InternalCupertinoApi object CupertinoHapticFeedback { + /** + * Selection changed haptic feedback type. + */ val SelectionChanged: HapticFeedbackType = HapticFeedbackType(1001) + /** + * Success haptic feedback type. + */ val Success: HapticFeedbackType = HapticFeedbackType(2001) + + /** + * Warning haptic feedback type. + */ val Warning: HapticFeedbackType = HapticFeedbackType(2002) + + /** + * Error haptic feedback type. + */ val Error: HapticFeedbackType = HapticFeedbackType(2003) + /** + * Impact light haptic feedback type. + */ val ImpactLight: HapticFeedbackType = HapticFeedbackType(3001) + + /** + * Impact medium haptic feedback type. + */ val ImpactMedium: HapticFeedbackType = HapticFeedbackType(3002) + + /** + * Impact heavy haptic feedback type. + */ val ImpactHeavy: HapticFeedbackType = HapticFeedbackType(3003) + + /** + * Impact rigid haptic feedback type. + */ val ImpactRigid: HapticFeedbackType = HapticFeedbackType(3004) + + /** + * Impact soft haptic feedback type. + */ val ImpactSoft: HapticFeedbackType = HapticFeedbackType(3005) } diff --git a/hig-core/src/commonMain/kotlin/zone/ien/hig/CupertinoTween.kt b/hig-core/src/commonMain/kotlin/zone/ien/hig/CupertinoTween.kt index 78007d03..16d11c6d 100644 --- a/hig-core/src/commonMain/kotlin/zone/ien/hig/CupertinoTween.kt +++ b/hig-core/src/commonMain/kotlin/zone/ien/hig/CupertinoTween.kt @@ -30,7 +30,12 @@ import androidx.compose.animation.core.tween * * Default values are used for iOS view transitions such as * UINavigationController, UIAlertController - * */ + * + * @param durationMillis the duration of the animation in milliseconds + * @param delayMillis the delay before the animation starts in milliseconds + * @param easing the easing function to use for the animation + * @return a TweenSpec for the Cupertino transition + */ fun cupertinoTween( durationMillis: Int = CupertinoTransitionDuration, delayMillis: Int = 0, @@ -42,5 +47,12 @@ fun cupertinoTween( delayMillis = delayMillis, ) +/** + * The easing function used for Cupertino transitions. + */ val CupertinoEasing = CubicBezierEasing(0.2833f, 0.99f, 0.31833f, 0.99f) + +/** + * The default transition duration for Cupertino transitions. + */ private val CupertinoTransitionDuration = 400 diff --git a/hig-core/src/commonMain/kotlin/zone/ien/hig/SystemBarAppearance.kt b/hig-core/src/commonMain/kotlin/zone/ien/hig/SystemBarAppearance.kt index ecc653a9..4a7ba187 100644 --- a/hig-core/src/commonMain/kotlin/zone/ien/hig/SystemBarAppearance.kt +++ b/hig-core/src/commonMain/kotlin/zone/ien/hig/SystemBarAppearance.kt @@ -20,6 +20,12 @@ package zone.ien.hig import androidx.compose.runtime.Composable +/** + * Applies the system bar appearance to the current composable. + * + * @param dark whether the status bar should be dark (true) or light (false) + */ @Composable @InternalCupertinoApi expect fun SystemBarAppearance(dark: Boolean) + diff --git a/hig-core/src/commonMain/kotlin/zone/ien/hig/section/SectionScope.kt b/hig-core/src/commonMain/kotlin/zone/ien/hig/section/SectionScope.kt index 3c3f621c..6b92a8b2 100644 --- a/hig-core/src/commonMain/kotlin/zone/ien/hig/section/SectionScope.kt +++ b/hig-core/src/commonMain/kotlin/zone/ien/hig/section/SectionScope.kt @@ -1,3 +1,6 @@ package zone.ien.hig.section +/** + * A scope for section components in the Cupertino design system. + */ interface SectionScope \ No newline at end of file diff --git a/hig-core/src/commonMain/kotlin/zone/ien/hig/theme/ColorScheme.kt b/hig-core/src/commonMain/kotlin/zone/ien/hig/theme/ColorScheme.kt index 77765cd5..94e86bbf 100644 --- a/hig-core/src/commonMain/kotlin/zone/ien/hig/theme/ColorScheme.kt +++ b/hig-core/src/commonMain/kotlin/zone/ien/hig/theme/ColorScheme.kt @@ -29,6 +29,32 @@ import zone.ien.hig.Accessibility import zone.ien.hig.InternalCupertinoApi import zone.ien.hig.isHighContrastEnabled +/** + * A color scheme for the Cupertino design system. + * + * @param isDark whether the theme is dark or light + * @param accent the accent color used for interactive elements + * @param label the primary label color + * @param secondaryLabel the secondary label color + * @param tertiaryLabel the tertiary label color + * @param quaternaryLabel the quaternary label color + * @param link the link color + * @param placeholderText the placeholder text color + * @param separator the separator color + * @param opaqueSeparator the opaque separator color + * @param systemBackground the system background color + * @param secondarySystemBackground the secondary system background color + * @param tertiarySystemBackground the tertiary system background color + * @param systemGroupedBackground the system grouped background color + * @param secondarySystemGroupedBackground the secondary system grouped background color + * @param tertiarySystemGroupedBackground the tertiary system grouped background color + * @param systemFill the system fill color + * @param secondarySystemFill the secondary system fill color + * @param tertiarySystemFill the tertiary system fill color + * @param quaternarySystemFill the quaternary system fill color + * @param lightText the light text color + * @param darkText the dark text color + */ @Immutable class ColorScheme internal constructor( val isDark: Boolean, @@ -54,6 +80,31 @@ class ColorScheme internal constructor( val lightText: Color, val darkText: Color ) { + /** + * Creates a copy of this ColorScheme with the specified values replaced. + * + * @param accent the new accent color + * @param label the new label color + * @param secondaryLabel the new secondary label color + * @param tertiaryLabel the new tertiary label color + * @param quaternaryLabel the new quaternary label color + * @param link the new link color + * @param placeholderText the new placeholder text color + * @param separator the new separator color + * @param opaqueSeparator the new opaque separator color + * @param systemBackground the new system background color + * @param secondarySystemBackground the new secondary system background color + * @param tertiarySystemBackground the new tertiary system background color + * @param systemGroupedBackground the new system grouped background color + * @param secondarySystemGroupedBackground the new secondary system grouped background color + * @param tertiarySystemGroupedBackground the new tertiary system grouped background color + * @param systemFill the new system fill color + * @param secondarySystemFill the new secondary system fill color + * @param tertiarySystemFill the new tertiary system fill color + * @param quaternarySystemFill the new quaternary system fill color + * @param lightText the new light text color + * @param darkText the new dark text color + */ fun copy( accent: Color = this.accent, label: Color = this.label, diff --git a/hig-core/src/commonMain/kotlin/zone/ien/hig/theme/Shapes.kt b/hig-core/src/commonMain/kotlin/zone/ien/hig/theme/Shapes.kt index 8ecbb0fb..6fc7dcdd 100644 --- a/hig-core/src/commonMain/kotlin/zone/ien/hig/theme/Shapes.kt +++ b/hig-core/src/commonMain/kotlin/zone/ien/hig/theme/Shapes.kt @@ -30,6 +30,15 @@ import com.kyant.shapes.RoundedRectangle import com.kyant.shapes.UnevenRoundedRectangle import zone.ien.hig.InternalCupertinoApi +/** + * Shapes for the Cupertino design system. + * + * @param extraSmall the extra small corner shape + * @param small the small corner shape + * @param medium the medium corner shape + * @param large the large corner shape + * @param extraLarge the extra large corner shape + */ @Stable class Shapes( val extraSmall: RoundedRectangle = ShapeDefaults.ExtraSmall, @@ -38,6 +47,15 @@ class Shapes( val large: RoundedRectangle = ShapeDefaults.Large, val extraLarge: RoundedRectangle = ShapeDefaults.ExtraLarge, ) { + /** + * Creates a copy of this Shapes with the specified values replaced. + * + * @param extraSmall the new extra small corner shape + * @param small the new small corner shape + * @param medium the new medium corner shape + * @param large the new large corner shape + * @param extraLarge the new extra large corner shape + */ fun copy( extraSmall: RoundedRectangle = this.extraSmall, small: RoundedRectangle = this.small, @@ -56,6 +74,9 @@ class Shapes( @InternalCupertinoApi val LocalShapes = staticCompositionLocalOf { Shapes() } +/** + * Default shapes for the Cupertino design system. + */ @Immutable object ShapeDefaults { /** Extra small sized corner shape */ diff --git a/hig-core/src/commonMain/kotlin/zone/ien/hig/theme/Typography.kt b/hig-core/src/commonMain/kotlin/zone/ien/hig/theme/Typography.kt index af53d0ce..89046cf5 100644 --- a/hig-core/src/commonMain/kotlin/zone/ien/hig/theme/Typography.kt +++ b/hig-core/src/commonMain/kotlin/zone/ien/hig/theme/Typography.kt @@ -27,6 +27,21 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp import zone.ien.hig.InternalCupertinoApi +/** + * Typography for the Cupertino design system. + * + * @param largeTitle the large title text style + * @param title1 the title 1 text style + * @param title2 the title 2 text style + * @param title3 the title 3 text style + * @param headline the headline text style + * @param body the body text style + * @param callout the callout text style + * @param subhead the subhead text style + * @param footnote the footnote text style + * @param caption1 the caption 1 text style + * @param caption2 the caption 2 text style + */ @Immutable class Typography( val largeTitle: TextStyle = @@ -86,6 +101,21 @@ class Typography( lineHeight = 13.sp, ), ) { + /** + * Creates a copy of this Typography with the specified values replaced. + * + * @param largeTitle the new large title text style + * @param title1 the new title 1 text style + * @param title2 the new title 2 text style + * @param title3 the new title 3 text style + * @param headline the new headline text style + * @param body the new body text style + * @param callout the new callout text style + * @param subhead the new subhead text style + * @param footnote the new footnote text style + * @param caption1 the new caption 1 text style + * @param caption2 the new caption 2 text style + */ fun copy( largeTitle: TextStyle = this.largeTitle, title1: TextStyle = this.title1, From 1b34e0b643ba5d0a54bd9b867d89949da2c63f3e Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Sat, 9 May 2026 00:44:03 +0900 Subject: [PATCH 044/109] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(core):=20?= =?UTF-8?q?Cupertino=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=ED=99=94=20=EB=B0=8F=20=EC=BD=94=EB=93=9C=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CupertinoDatePickerNative, CupertinoDialogsNative, CupertinoDropdownMenuNative 등 컴포넌트 문서화 개선 - iOS 및 non-iOS 플랫폼별 구현 코드 정리 및 주석 추가 - 코드 복잡도 감소 및 가독성 향상을 위한 리팩토링 수행 --- .../zone/ien/hig/CupertinoDatePickerNative.kt | 9 ++ .../zone/ien/hig/CupertinoDialogsNative.kt | 48 ++++++++-- .../ien/hig/CupertinoDropdownMenuNative.kt | 87 ++++++++++++++++++- .../zone/ien/hig/CupertinoTimePickerNative.kt | 9 ++ .../ien/hig/CupertinoDatePickerNative.ios.kt | 26 ++++++ .../hig/CupertinoDatePickerNative.nonIos.kt | 8 ++ 6 files changed, 179 insertions(+), 8 deletions(-) diff --git a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDatePickerNative.kt b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDatePickerNative.kt index abf49257..8e30c26b 100644 --- a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDatePickerNative.kt +++ b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDatePickerNative.kt @@ -26,6 +26,15 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.takeOrElse import zone.ien.hig.theme.CupertinoTheme +/** + * A date picker that is implemented with native platform widgets on iOS and Compose widgets on + * non-iOS platforms. + * + * @param state the state of the date picker + * @param modifier the [Modifier] to be applied to this date picker + * @param style the date picker style, either wheel or pager + * @param containerColor the color to apply to the background of the date picker + */ @Composable @ExperimentalCupertinoApi expect fun CupertinoDatePickerNative( diff --git a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDialogsNative.kt b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDialogsNative.kt index 159f27c3..93692d7d 100644 --- a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDialogsNative.kt +++ b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDialogsNative.kt @@ -42,7 +42,7 @@ import zone.ien.hig.theme.systemGray7 * @param buttonsOrientation not used. iOS automatically picks most suitable layout * based on buttons width and count * @param buttons actions builder block - * */ + */ @Composable expect fun CupertinoAlertDialogNative( onDismissRequest: () -> Unit, @@ -56,8 +56,9 @@ expect fun CupertinoAlertDialogNative( ) /** - * Native analog for the compose [CupertinoActionSheet]. + * Native analog for to compose [CupertinoActionSheet]. * + * @param visible whether the action sheet is visible * @param onDismissRequest called when dialog is already dismissed. Must not be ignored * @param title alert dialog title * @param message alert dialog message @@ -66,7 +67,7 @@ expect fun CupertinoAlertDialogNative( * @param properties not used. To enable dismissOnClickOutside behavior * add an action with [AlertActionStyle.Cancel] that would receive a cancel callback. * @param buttons actions builder block - * */ + */ @Composable expect fun CupertinoActionSheetNative( visible: Boolean, @@ -79,10 +80,18 @@ expect fun CupertinoActionSheetNative( buttons: NativeAlertDialogActionsScope.() -> Unit, ) +/** + * Scope for building native alert dialog actions. + */ interface NativeAlertDialogActionsScope { /** * Alert controller button - * */ + * + * @param onClick callback when button is clicked + * @param style the style of the action button + * @param enabled whether the button is enabled + * @param title the title of the button + */ fun action( onClick: () -> Unit, style: AlertActionStyle = AlertActionStyle.Default, @@ -93,7 +102,11 @@ interface NativeAlertDialogActionsScope { /** * Alert controller button with default style - * */ + * + * @param onClick callback when button is clicked + * @param enabled whether the button is enabled + * @param title the title of the button + */ fun NativeAlertDialogActionsScope.default( onClick: () -> Unit, enabled: Boolean = true, @@ -107,7 +120,11 @@ fun NativeAlertDialogActionsScope.default( /** * Alert controller button with destructive style - * */ + * + * @param onClick callback when button is clicked + * @param enabled whether the button is enabled + * @param title the title of the button + */ fun NativeAlertDialogActionsScope.destructive( onClick: () -> Unit, enabled: Boolean = true, @@ -121,7 +138,11 @@ fun NativeAlertDialogActionsScope.destructive( /** * Alert controller button with cancel style - * */ + * + * @param onClick callback when button is clicked + * @param enabled whether the button is enabled + * @param title the title of the button + */ fun NativeAlertDialogActionsScope.cancel( onClick: () -> Unit, enabled: Boolean = true, @@ -133,6 +154,14 @@ fun NativeAlertDialogActionsScope.cancel( title = title, ) +/** + * A button for a native alert dialog. + * + * @param onClick callback when the button is clicked + * @param style the style of the action button + * @param enabled whether the button is enabled + * @param title the title of the button + */ internal class CupertinoAlertDialogButtonNative( val onClick: () -> Unit, val style: AlertActionStyle, @@ -140,6 +169,11 @@ internal class CupertinoAlertDialogButtonNative( val title: String, ) +/** + * Converts native alert dialog actions to regular dialog actions. + * + * @param native the native dialog actions to convert + */ internal fun AlertDialogActionsScope.fromNative(native: NativeAlertDialogActionsScope.() -> Unit) { val buttons = mutableListOf() diff --git a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt index b449d2e7..560fc18e 100644 --- a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt +++ b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt @@ -1,3 +1,23 @@ +/* + * Copyright (c) 2023-2024. Compose Cupertino project and open source contributors. + * Copyright (c) 2025. Scott Lanoue. + * Copyright (c) 2026. IENGROUND of IENLAB. + * + * 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 zone.ien.hig import androidx.compose.foundation.ScrollState @@ -10,10 +30,18 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpOffset -import androidx.compose.ui.unit.dp import androidx.compose.ui.window.PopupProperties import com.kyant.backdrop.Backdrop +/** + * Data class representing a menu item in a dropdown menu. + * + * @param title the text title of the menu item + * @param enabled whether the menu item is enabled + * @param icon an optional icon painter for the menu item + * @param isDestructive whether this is a destructive menu action + * @param onClick callback when the menu item is clicked + */ data class CupertinoMenuItemData( val title: String, val enabled: Boolean = true, @@ -22,6 +50,63 @@ data class CupertinoMenuItemData( val onClick: () -> Unit, ) +/** + * Data class representing a section in a dropdown menu. + * + * @param title the title of the section + * @param icon an optional icon for the section + * @param options display options for the section + * @param items the list of menu items in this section + */ +data class CupertinoMenuSectionData( + val title: String, + val icon: Painter? = null, + val options: HigMenuOptions = HigMenuOptions.DisplayInline, + val items: List, +) + +/** + * Enum representing display options for dropdown menu sections. + */ +enum class HigMenuOptions { + DisplayInline, SingleSelection, DisplayAsPalette, Destructive +} + +/** + * A dropdown menu that is implemented with native platform widgets on iOS and Compose widgets on + * non-iOS platforms. + * + * @param expanded whether the menu is currently expanded + * @param onDismissRequest callback when the menu should be dismissed + * @param modifier the [Modifier] to be applied to this menu + * @param offset the offset for positioning the menu + * @param paddingValues the padding values for the menu + * @param containerColor the color of the menu container + * @param width the width of the menu + * @param scrollState the scroll state for the menu + * @param properties popup properties for the menu + * @param backdrop the backdrop for the menu + * @param items list of menu items + * @param sections list of menu sections + */ +@OptIn(ExperimentalComposeUiApi::class) +@ExperimentalCupertinoApi +@Composable +expect fun CupertinoDropdownMenuNative( + expanded: Boolean, + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, + offset: DpOffset = DpOffset(0.dp, 0.dp), + paddingValues: PaddingValues = CupertinoDropdownMenuDefaults.PaddingValues, + containerColor: Color = CupertinoDropdownMenuDefaults.ContainerColor, + width: Dp = CupertinoDropdownMenuDefaults.DefaultWidth, + scrollState: ScrollState = rememberScrollState(), + properties: PopupProperties = PopupProperties(focusable = true, clippingEnabled = false), + backdrop: Backdrop, + items: List = listOf(), + sections: List = listOf(), +) + data class CupertinoMenuSectionData( val title: String, val icon: Painter? = null, diff --git a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoTimePickerNative.kt b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoTimePickerNative.kt index 09aa127a..0afb87d2 100644 --- a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoTimePickerNative.kt +++ b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoTimePickerNative.kt @@ -27,6 +27,15 @@ import androidx.compose.ui.graphics.takeOrElse import androidx.compose.ui.unit.Dp import zone.ien.hig.theme.CupertinoTheme +/** + * A time picker that is implemented with native platform widgets on iOS and Compose widgets on + * non-iOS platforms. + * + * @param state the state of the time picker + * @param modifier the [Modifier] to be applied to this time picker + * @param height the height of the time picker + * @param containerColor the color to apply to the background of the time picker + */ @Composable @ExperimentalCupertinoApi expect fun CupertinoTimePickerNative( diff --git a/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDatePickerNative.ios.kt b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDatePickerNative.ios.kt index 15168857..f3b4e4c7 100644 --- a/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDatePickerNative.ios.kt +++ b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDatePickerNative.ios.kt @@ -56,6 +56,14 @@ import platform.UIKit.UIDatePickerMode import platform.UIKit.UIDatePickerStyle import platform.UIKit.UIView +/** + * An iOS implementation of the date picker native component. + * + * @param state the date picker state + * @param modifier the [Modifier] to be applied to this date picker + * @param style the date picker style, either wheel or pager + * @param containerColor the color to apply to the background of the date picker + */ @OptIn(InternalCupertinoApi::class) @Composable @ExperimentalCupertinoApi @@ -81,6 +89,16 @@ actual fun CupertinoDatePickerNative( ) } +/** + * Composable that creates a native iOS UIDatePicker for use inside a [UIKitView]. + * + * @param millis the initial date in milliseconds since epoch + * @param mode the UIDatePickerMode + * @param onChange callback when the date changed + * @param modifier the [Modifier] to be applied to this date picker + * @param style the date picker style, either wheel or pager + * @param containerColor the color to apply to the background of the date picker + */ @OptIn(ExperimentalForeignApi::class, ExperimentalComposeUiApi::class) @Composable internal fun CupertinoDatePickerNativeImpl( @@ -140,6 +158,14 @@ internal fun CupertinoDatePickerNativeImpl( ) } +/** + * A UIDatePicker subclass that handles date selection and UI configuration. + * + * @param millis the initial date in milliseconds since epoch + * @param mode the UIDatePickerMode + * @param style the date picker style, either wheel or pager + * @param onChange callback when the date changed + */ @OptIn(ExperimentalForeignApi::class) private class DatePicker( millis: Long, diff --git a/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDatePickerNative.nonIos.kt b/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDatePickerNative.nonIos.kt index 86874ac6..9e636fb6 100644 --- a/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDatePickerNative.nonIos.kt +++ b/hig-native/src/nonIosMain/kotlin/zone/ien/hig/CupertinoDatePickerNative.nonIos.kt @@ -24,6 +24,14 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +/** + * A date picker that is implemented with Compose widgets on non-iOS platforms. + * + * @param state the state of the date picker + * @param modifier the [Modifier] to be applied to this date picker + * @param style the date picker style, either wheel or pager + * @param containerColor the color to apply to the background of the date picker + */ @Composable @ExperimentalCupertinoApi actual fun CupertinoDatePickerNative( From a605d347e8e7f8ff1e115ad247e2f760268ba791 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Sat, 9 May 2026 06:07:30 +0900 Subject: [PATCH 045/109] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(adaptive)?= =?UTF-8?q?:=20=EC=A0=81=EC=9D=91=ED=98=95=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EB=AC=B8=EC=84=9C=ED=99=94=20=EB=B0=8F=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AdaptiveNavigationBar, AdaptiveTopAppBar, AdaptiveTheme 등 주요 컴포넌트에 대한 문서화 추가 - 각 컴포넌트의 플랫폼별 적응형 동작을 명확히 설명하는 주석 추가 - 코드 구조 개선 및 일관성 있는 문서화 형식 적용 --- .../ien/hig/adaptive/AdaptiveNavigationBar.kt | 78 +++++++++++++++++ .../zone/ien/hig/adaptive/AdaptiveScaffold.kt | 41 +++++++++ .../zone/ien/hig/adaptive/AdaptiveSlider.kt | 84 ++++++++++++------- .../zone/ien/hig/adaptive/AdaptiveSwitch.kt | 56 +++++++++---- .../zone/ien/hig/adaptive/AdaptiveTheme.kt | 58 +++++++++++-- .../ien/hig/adaptive/AdaptiveTopAppBar.kt | 51 ++++++++++- .../zone/ien/hig/adaptive/AdaptiveWidget.kt | 22 ++++- .../ien/hig/adaptive/icons/AdaptiveIcons.kt | 20 ++++- .../zone/ien/hig/adaptive/DefaultTheme.ios.kt | 6 ++ .../ien/hig/adaptive/DefaultTheme.nonIos.kt | 6 ++ 10 files changed, 358 insertions(+), 64 deletions(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveNavigationBar.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveNavigationBar.kt index bb56396b..eb380c42 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveNavigationBar.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveNavigationBar.kt @@ -16,6 +16,20 @@ * limitations under the License. */ +/** + * An adaptive navigation bar that adapts between Cupertino and Material design based on the platform. + * + * This composable provides a navigation bar that automatically switches between Cupertino (iOS) and Material (Android) + * design patterns based on the target platform. It supports tab navigation with appropriate styling for each platform. + * + * @param modifier optional [Modifier] for customizing the appearance and behavior + * @param selectedTabIndex lambda that returns the currently selected tab index + * @param onTabSelected callback to be invoked when a tab is selected + * @param tabsCount the total number of tabs + * @param adaptation lambda for customizing the adaptation behavior + * @param content composable content of the navigation bar items + */ + package zone.ien.hig.adaptive @@ -170,6 +184,33 @@ fun AdaptiveNavigationBarNative( } } +/** + * An adaptive navigation bar that adapts between Cupertino and Material design based on the platform. + * + * This composable provides a navigation bar that automatically switches between Cupertino (iOS) and Material (Android) + * design patterns based on the target platform. It supports native navigation bar items with appropriate styling for each platform. + * + * @param modifier optional [Modifier] for customizing the appearance and behavior + * @param selectedTabIndex lambda that returns the currently selected tab index + * @param onTabSelected callback to be invoked when a tab is selected + * @param adaptation lambda for customizing the adaptation behavior + * @param items list of navigation bar items for the Material implementation + */ +/** + * An adaptive navigation bar item that adapts between Cupertino and Material design based on the platform. + * + * This composable provides a navigation bar item that automatically switches between Cupertino (iOS) and Material (Android) + * design patterns based on the target platform. It's designed to be used within an [AdaptiveNavigationBar]. + * + * @param index the index of this item in the navigation bar + * @param onClick callback to be invoked when the item is clicked + * @param icon composable that draws the icon for the item + * @param modifier optional [Modifier] for customizing the appearance and behavior + * @param enabled whether the item is enabled + * @param label composable that draws the label for the item + * @param interactionSource the interaction source for the item + * @param adaptation lambda for customizing the adaptation behavior + */ @OptIn(ExperimentalCupertinoApi::class, ExperimentalAdaptiveApi::class) @Composable fun RowScope.AdaptiveNavigationBarItem( @@ -189,6 +230,43 @@ fun RowScope.AdaptiveNavigationBarItem( onClick() } + AdaptiveWidget( + adaptation = remember { + NavigationBarItemAdaptation() + }, + adaptationScope = adaptation, + cupertino = { + CupertinoNavigationBarItem( + onClick = resolvedOnClick, + icon = icon, + modifier = modifier, + enabled = enabled, + label = label, + interactionSource = interactionSource, + ) + }, + material = { + NavigationBarItem( + selected = selected, + onClick = resolvedOnClick, + icon = icon, + modifier = modifier, + enabled = enabled, + label = label, + alwaysShowLabel = it.alwaysShowLabel, + colors = it.colors, + interactionSource = interactionSource + ) + } + ) +} + } + ) + } + ) + } +} + AdaptiveWidget( adaptation = remember { NavigationBarItemAdaptation() diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveScaffold.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveScaffold.kt index 3bb44f62..ed32ea20 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveScaffold.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveScaffold.kt @@ -40,6 +40,23 @@ import zone.ien.hig.ExperimentalCupertinoApi import zone.ien.hig.FabPosition +/** + * An adaptive scaffold that adapts between Cupertino and Material design based on the platform. + * + * This composable provides a scaffold that automatically switches between Cupertino (iOS) and Material (Android) + * design patterns based on the target platform. The content of the scaffold adapts to the appropriate design + * guidelines and styles. + * + * @param modifier optional [Modifier] for customizing the appearance and behavior + * @param topBar composable for the top app bar + * @param bottomBar composable for the bottom app bar + * @param snackbarHost composable for the snackbar host + * @param floatingActionButton composable for the floating action button + * @param floatingActionButtonPosition determines the position of the floating action button + * @param contentWindowInsets the window insets to be used for the content + * @param adaptation lambda for customizing the adaptation behavior + * @param content composable content of the scaffold + */ @OptIn(ExperimentalCupertinoApi::class) @ExperimentalAdaptiveApi @Composable @@ -92,7 +109,27 @@ fun AdaptiveScaffold( } ) } + ) +} +/** + * An adaptive scaffold that adapts between Cupertino and Material design based on the platform. + * + * This composable provides a scaffold that automatically switches between Cupertino (iOS) and Material (Android) + * design patterns based on the target platform. The content of the scaffold adapts to the appropriate design + * guidelines and styles. + * + * @param modifier optional [Modifier] for customizing the appearance and behavior + * @param topBar composable for the top app bar + * @param bottomBar composable for the bottom app bar + * @param snackbarHost composable for the snackbar host + * @param floatingActionButton composable for the floating action button + * @param floatingActionButtonPosition determines the position of the floating action button + * @param containerColor color for the container + * @param contentColor color for the content + * @param contentWindowInsets the window insets to be used for the content + * @param content composable content of the scaffold + */ @OptIn(ExperimentalCupertinoApi::class) @ExperimentalAdaptiveApi @Composable @@ -160,6 +197,10 @@ class ScaffoldAdaptation internal constructor( var contentColor by mutableStateOf(contentColor) var containerColor by mutableStateOf(containerColor) } + +/** + * Implementation of [Adaptation] for [ScaffoldAdaptation]. + */ @OptIn(ExperimentalAdaptiveApi::class) @Stable private class ScaffoldAdaptationImpl : diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSlider.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSlider.kt index fb52eca5..ad001e03 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSlider.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSlider.kt @@ -16,36 +16,12 @@ * limitations under the License. */ - - -package zone.ien.hig.adaptive - -import androidx.compose.foundation.interaction.Interaction -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.material3.RangeSlider -import androidx.compose.material3.Slider -import androidx.compose.material3.SliderColors -import androidx.compose.material3.SliderDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import com.kyant.backdrop.Backdrop -import com.kyant.backdrop.backdrops.rememberLayerBackdrop -import zone.ien.hig.utils.rememberDefaultBackdrop -import zone.ien.hig.CupertinoSlider -import zone.ien.hig.CupertinoLiquidSliderDefaults -import zone.ien.hig.CupertinoRangeSlider -import zone.ien.hig.CupertinoSliderColors - /** - * Sliders allow users to make selections from a range of values. + * An adaptive slider that adapts between Cupertino and Material design based on the platform. * - * Sliders reflect a range of values along a bar, from which users may select a single value. - * They are ideal for adjusting settings such as volume, brightness, or applying image filters. + * Sliders allow users to make selections from a range of values. + * This composable automatically switches between Cupertino (iOS) and Material (Android) design patterns + * based on the current theme. It supports both single value and range sliders. * * @param value current value of the slider. If outside of [valueRange] provided, value will be * coerced to this range. @@ -67,6 +43,29 @@ import zone.ien.hig.CupertinoSliderColors * [Interaction]s and customize the appearance / behavior of this slider in different states. * @param adaptation adaptation scope */ +package zone.ien.hig.adaptive + +import androidx.compose.foundation.interaction.Interaction +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.material3.RangeSlider +import androidx.compose.material3.Slider +import androidx.compose.material3.SliderColors +import androidx.compose.material3.SliderDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import com.kyant.backdrop.Backdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop +import zone.ien.hig.utils.rememberDefaultBackdrop +import zone.ien.hig.CupertinoSlider +import zone.ien.hig.CupertinoLiquidSliderDefaults +import zone.ien.hig.CupertinoRangeSlider +import zone.ien.hig.CupertinoSliderColors + @ExperimentalAdaptiveApi @Composable fun AdaptiveSlider( @@ -119,10 +118,11 @@ fun AdaptiveSlider( } /** - * Sliders allow users to make selections from a range of values. + * An adaptive range slider that adapts between Cupertino and Material design based on the platform. * - * Sliders reflect a range of values along a bar, from which users may select a single value. - * They are ideal for adjusting settings such as volume, brightness, or applying image filters. + * Sliders allow users to make selections from a range of values. + * This composable automatically switches between Cupertino (iOS) and Material (Android) design patterns + * based on the current theme. It supports both single value and range sliders. * * @param value current values of the RangeSlider. If either value is outside of [valueRange] * provided, it will be coerced to this range. @@ -187,6 +187,16 @@ fun AdaptiveRangeSlider( ) } +/** + * Cupertino slider adaptation. + * + * Container class for Cupertino slider adaptation properties. + * + * @param colors the colors to be used for the slider + * @param showStepIndicator whether to show step indicators + * @param backdrop backdrop to use for the slider + * @param visibilityThreshold threshold for visibility + */ @Stable class CupertinoSliderAdaptation internal constructor( colors: CupertinoSliderColors, @@ -200,6 +210,13 @@ class CupertinoSliderAdaptation internal constructor( var visibilityThreshold: Float by mutableStateOf(visibilityThreshold) } +/** + * Material slider adaptation. + * + * Container class for Material slider adaptation properties. + * + * @param colors the colors to be used for the slider + */ @Stable class MaterialSliderAdaptation internal constructor( colors: SliderColors, @@ -207,6 +224,11 @@ class MaterialSliderAdaptation internal constructor( var colors: SliderColors by mutableStateOf(colors) } +/** + * Slider adaptation implementation. + * + * Implementation of [Adaptation] for slider adaptation. + */ @OptIn(ExperimentalAdaptiveApi::class) @Stable private class SliderAdaptation( diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt index f956745c..2ed6b61a 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt @@ -16,8 +16,24 @@ * limitations under the License. */ - - +/** + * An adaptive switch that adapts between Cupertino and Material design based on the platform. + * + * Switches toggle the state of a single item on or off. + * This composable automatically switches between Cupertino (iOS) and Material (Android) design patterns + * based on the current theme. + * + * @param checked whether or not this switch is checked + * @param onCheckedChange called when this switch is clicked. If `null`, then this switch will not + * be interactable, unless something else handles its input events and updates its state. + * @param modifier the [Modifier] to be applied to this switch + * @param thumbContent content that will be drawn inside the thumb + * @param enabled controls the enabled state of this switch. When `false`, this component will not + * respond to user input, and it will appear visually disabled and disabled to accessibility + * services. + * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s + * @param adaptation configuration block for theme-dependent properties for this switch + */ package zone.ien.hig.adaptive import androidx.compose.foundation.interaction.Interaction @@ -39,22 +55,6 @@ import zone.ien.hig.CupertinoSwitch import zone.ien.hig.CupertinoSwitchColors import zone.ien.hig.CupertinoSwitchDefaults -/** - * Adaptive Switch depending on current [Theme]. - * - * Switches toggle the state of a single item on or off. - * - * @param checked whether or not this switch is checked - * @param onCheckedChange called when this switch is clicked. If `null`, then this switch will not - * be interactable, unless something else handles its input events and updates its state. - * @param modifier the [Modifier] to be applied to this switch - * @param thumbContent content that will be drawn inside the thumb - * @param enabled controls the enabled state of this switch. When `false`, this component will not - * respond to user input, and it will appear visually disabled and disabled to accessibility - * services. - * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s - * @param adaptation configuration block for theme-dependent properties for this switch - */ @ExperimentalAdaptiveApi @Composable fun AdaptiveSwitch( @@ -98,6 +98,14 @@ fun AdaptiveSwitch( ) } +/** + * Cupertino switch adaptation. + * + * Container class for Cupertino switch adaptation properties. + * + * @param colors the colors to be used for the switch + * @param backdrop backdrop to use for the switch + */ @Stable class CupertinoSwitchAdaptation internal constructor( colors: CupertinoSwitchColors, @@ -107,6 +115,13 @@ class CupertinoSwitchAdaptation internal constructor( var backdrop by mutableStateOf(backdrop) } +/** + * Material switch adaptation. + * + * Container class for Material switch adaptation properties. + * + * @param colors the colors to be used for the switch + */ @Stable class MaterialSwitchAdaptation internal constructor( colors: SwitchColors, @@ -114,6 +129,11 @@ class MaterialSwitchAdaptation internal constructor( var colors by mutableStateOf(colors) } +/** + * Switch adaptation implementation. + * + * Implementation of [Adaptation] for switch adaptation. + */ @OptIn(ExperimentalAdaptiveApi::class) @Stable private class SwitchAdaptation: Adaptation() { diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt index 09ed2025..6740635a 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt @@ -16,8 +16,15 @@ * limitations under the License. */ - - +/** + * Adaptive theme that allows seamless use of both Material and Cupertino widgets. + * + * This theme provides a way to seamlessly use Material and Cupertino widgets together in the same application + * by adapting based on the target theme. It also makes [Text] and [CupertinoText], as well as [Icon] and [CupertinoIcon] + * behave identically in both design systems. + * + * The current theme target can be accessed inside the [content] using [currentTheme] property. + */ package zone.ien.hig.adaptive import androidx.compose.material3.Icon @@ -46,6 +53,9 @@ import zone.ien.hig.CupertinoText import androidx.compose.material3.LocalContentColor as MaterialLocalContentColor import androidx.compose.material3.LocalTextStyle as MaterialLocalTextStyle +/** + * The supported themes for adaptive widgets. + */ enum class Theme { Cupertino, Material3 } @@ -57,11 +67,11 @@ enum class Theme { * * Current theme target can be accessed inside the [content] using [currentTheme] property * - * @param target theme for adaptive widgets - * @param material [MaterialTheme] specification - * @param cupertino [CupertinoTheme] specification + * @param target theme for adaptive widgets. Defaults to [Theme.Cupertino] for iOS and [Theme.Material3] for other platforms + * @param material [MaterialTheme] specification. NOTE: You must use lambda parameter as a content + * @param cupertino [CupertinoTheme] specification. NOTE: You must use lambda parameter as a content * @param content themed content - * */ + */ @ExperimentalAdaptiveApi @Composable fun AdaptiveTheme( @@ -123,7 +133,7 @@ fun AdaptiveTheme( * @param material [MaterialTheme] specification. NOTE: You must use lambda parameter as a content * @param cupertino [CupertinoTheme] specification. NOTE: You must use lambda parameter as a content * @param content themed content - * */ + */ @ExperimentalAdaptiveApi @Deprecated( message = "Use variant with theme specs instead of lambdas", @@ -165,6 +175,15 @@ fun AdaptiveTheme( } } +/** + * Material theme specification. + * + * This class holds the specification for Material themes including color scheme, shapes, and typography. + * + * @param colorScheme the color scheme to use for Material design + * @param shapes the shapes to use for Material design + * @param typography the typography to use for Material design + */ @Immutable @ExperimentalAdaptiveApi class MaterialThemeSpec( @@ -187,6 +206,14 @@ class MaterialThemeSpec( } companion object { + /** + * Creates a default Material theme specification based on the current MaterialTheme. + * + * @param colorScheme the color scheme to use for Material design + * @param shapes the shapes to use for Material design + * @param typography the typography to use for Material design + * @return a MaterialThemeSpec with the default values + */ @Composable fun Default( colorScheme: MaterialColorScheme = MaterialTheme.colorScheme, @@ -196,6 +223,15 @@ class MaterialThemeSpec( } } +/** + * Cupertino theme specification. + * + * This class holds the specification for Cupertino themes including color scheme, shapes, and typography. + * + * @param colorScheme the color scheme to use for Cupertino design + * @param shapes the shapes to use for Cupertino design + * @param typography the typography to use for Cupertino design + */ @Immutable @ExperimentalAdaptiveApi class CupertinoThemeSpec( @@ -217,6 +253,14 @@ class CupertinoThemeSpec( return "CupertinoThemeSpec(colorScheme=$colorScheme, shapes=$shapes, typography=$typography)" } companion object { + /** + * Creates a default Cupertino theme specification based on the current CupertinoTheme. + * + * @param colorScheme the color scheme to use for Cupertino design + * @param shapes the shapes to use for Cupertino design + * @param typography the typography to use for Cupertino design + * @return a CupertinoThemeSpec with the default values + */ @Composable fun Default( colorScheme: CupertinoColorScheme = CupertinoTheme.colorScheme, diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTopAppBar.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTopAppBar.kt index 883c099a..adf8d21b 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTopAppBar.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTopAppBar.kt @@ -16,8 +16,19 @@ * limitations under the License. */ - - +/** + * An adaptive top app bar that adapts between Cupertino and Material design based on the platform. + * + * This composable provides a top app bar that automatically switches between Cupertino (iOS) and Material (Android) + * design patterns based on the target platform. The content adapts to appropriate design guidelines and styles. + * + * @param title composable for the title + * @param modifier optional [Modifier] for customizing the appearance and behavior + * @param navigationIcon composable for the navigation icon + * @param actions composable for the actions + * @param windowInsets the window insets to be used for the content + * @param adaptation lambda for customizing the adaptation behavior + */ package zone.ien.hig.adaptive import androidx.compose.foundation.layout.RowScope @@ -86,6 +97,20 @@ fun AdaptiveTopAppBar( ) } +/** + * A single row top app bar. + * + * This private composable renders either a CenterAlignedTopAppBar or a TopAppBar based on the isCenterAligned parameter. + * + * @param title composable for the title + * @param isCenterAligned whether the title should be centered + * @param colors the colors to be used for the top app bar + * @param modifier optional [Modifier] for customizing the appearance and behavior + * @param navigationIcon composable for the navigation icon + * @param actions composable for the actions + * @param windowInsets the window insets to be used for the content + * @param scrollBehavior the scroll behavior for the top app bar + */ @OptIn(ExperimentalMaterial3Api::class) @Composable private fun SingleRowTopAppBar( @@ -121,6 +146,15 @@ private fun SingleRowTopAppBar( } } +/** + * Material top app bar adaptation. + * + * Container class for Material top app bar adaptation properties. + * + * @param colors the colors to be used for the top app bar + * @param isCenterAligned whether the title should be centered + * @param scrollBehavior the scroll behavior for the top app bar + */ @Stable @OptIn(ExperimentalMaterial3Api::class) class MaterialTopAppBarAdaptation internal constructor( @@ -133,6 +167,14 @@ class MaterialTopAppBarAdaptation internal constructor( var scrollBehavior: TopAppBarScrollBehavior? by mutableStateOf(scrollBehavior) } +/** + * Cupertino top app bar adaptation. + * + * Container class for Cupertino top app bar adaptation properties. + * + * @param colors the colors to be used for the top app bar + * @param backdrop backdrop to use for the bar + */ @Stable class CupertinoTopAppBarAdaptation internal constructor( colors: CupertinoTopAppBarColors, @@ -142,6 +184,11 @@ class CupertinoTopAppBarAdaptation internal constructor( var backdrop: LayerBackdrop by mutableStateOf(backdrop) } +/** + * Top app bar adaptation. + * + * Implementation of [Adaptation] for top app bar adaptation. + */ @OptIn(ExperimentalAdaptiveApi::class) @Stable private class TopAppBarAdaptation: Adaptation() { diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveWidget.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveWidget.kt index 0a32a3d0..60172894 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveWidget.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveWidget.kt @@ -16,8 +16,15 @@ * limitations under the License. */ - - +/** + * A composable that adapts between Cupertino and Material design based on the current theme. + * + * This function provides a way to conditionally render either Cupertino or Material design composables + * based on the current theme, allowing for a consistent UX across different platforms. + * + * @param material composable for the material design implementation + * @param cupertino composable for the cupertino design implementation + */ package zone.ien.hig.adaptive import androidx.compose.runtime.Composable @@ -34,6 +41,17 @@ fun AdaptiveWidget( } } +/** + * A composable that adapts between Cupertino and Material design based on the current theme with adaptation support. + * + * This function provides a way to conditionally render either Cupertino or Material design composables + * based on the current theme, with support for custom adaptation of design properties. + * + * @param adaptation the adaptation object to manage theme-specific values + * @param material composable for the material design implementation with adaptation + * @param cupertino composable for the cupertino design implementation with adaptation + * @param adaptationScope lambda that allows customization of the adaptation behavior + */ @Composable @ExperimentalAdaptiveApi fun AdaptiveWidget( diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/icons/AdaptiveIcons.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/icons/AdaptiveIcons.kt index 8aee685d..25f7437e 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/icons/AdaptiveIcons.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/icons/AdaptiveIcons.kt @@ -16,8 +16,12 @@ * limitations under the License. */ - - +/** + * Provides adaptive icons for both Cupertino and Material design. + * + * This object contains a collection of adaptive icons that switch between Material and Cupertino design based on + * the current theme. Icons are defined for both filled and outlined variants. + */ package zone.ien.hig.adaptive.icons import androidx.compose.material.icons.Icons @@ -85,7 +89,11 @@ object AdaptiveIcons { * Adaptive vector icon based on current theme. * * To retrieve system images on iOS use [painter] - * */ + * + * @param material composable that returns the Material design icon + * @param cupertino composable that returns the Cupertino icon + * @return the appropriate icon based on the current theme + */ @Composable fun vector( material: @Composable () -> ImageVector, @@ -101,7 +109,11 @@ object AdaptiveIcons { * * - For [Theme.Material3] and [Theme.Cupertino] on non-iOS it return painter from [material] vector. * - For [Theme.Cupertino] on iOS it returns SF Symbol with system name [cupertino] - * */ + * + * @param material composable that returns the Material design icon + * @param cupertino composable that returns the Cupertino system name + * @return the appropriate painter based on the current theme + */ @Composable fun painter( material: @Composable () -> ImageVector, diff --git a/hig-adaptive/src/iosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.ios.kt b/hig-adaptive/src/iosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.ios.kt index 08a297d3..e3acaaf9 100644 --- a/hig-adaptive/src/iosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.ios.kt +++ b/hig-adaptive/src/iosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.ios.kt @@ -16,6 +16,12 @@ * limitations under the License. */ +/** + * Platform-specific default theme for iOS platforms. + * + * This file defines the default theme for iOS platforms to use Cupertino design. + * It ensures consistent theming across iOS applications using this library. + */ package zone.ien.hig.adaptive internal actual val DefaultTheme: Theme diff --git a/hig-adaptive/src/nonIosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.nonIos.kt b/hig-adaptive/src/nonIosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.nonIos.kt index aedd1e03..a1b47552 100644 --- a/hig-adaptive/src/nonIosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.nonIos.kt +++ b/hig-adaptive/src/nonIosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.nonIos.kt @@ -16,6 +16,12 @@ * limitations under the License. */ +/** + * Platform-specific default theme for non-iOS platforms. + * + * This file defines the default theme for non-iOS platforms (such as Android) to use Material 3 design. + * It ensures consistent theming across Android applications using this library. + */ package zone.ien.hig.adaptive internal actual val DefaultTheme: Theme From 894fc53b7550a9778eaaaf2664be60ebdfe5cce2 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Sat, 9 May 2026 07:37:24 +0900 Subject: [PATCH 046/109] docs: add missing documentation comments to CupertinoTextField function Added comprehensive Javadoc-style documentation comments to the first CupertinoTextField function (String-based) that was missing parameter documentation. This ensures all overloaded functions in the file have consistent documentation and fixes the mismatch between code and comments. --- .../kotlin/zone/ien/hig/CupertinoTextField.kt | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoTextField.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoTextField.kt index 8a7af482..98d4ecd9 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoTextField.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoTextField.kt @@ -76,6 +76,32 @@ import zone.ien.hig.theme.systemRed internal expect fun KeyboardOptions.enableNativeInput(): KeyboardOptions +/** + * A Cupertino-style text field that allows users to enter and edit text. + * + * @param value the text field's input value + * @param onValueChange called when the text field's input value changes + * @param modifier the [Modifier] to be applied to this text field + * @param enabled controls the enabled state of this text field. When `false`, this component will not + * respond to user input and will be displayed as disabled + * @param readOnly controls the read-only state of this text field. When `true`, this component will not + * respond to user input and will be displayed as read-only + * @param textStyle the [TextStyle] to be applied to the text content + * @param placeholder the placeholder content to be displayed when the text field is empty + * @param leadingIcon the icon displayed at the start of the text field + * @param trailingIcon the icon displayed at the end of the text field + * @param isError controls the error state of this text field. When `true`, this component will be displayed + * with error styling + * @param visualTransformation the [VisualTransformation] to be applied to the text content + * @param keyboardOptions the keyboard options configuration for this text field + * @param keyboardActions the keyboard actions configuration for this text field + * @param singleLine when `true`, this text field will be constrained to a single line + * @param maxLines the maximum number of lines this text field can grow to + * @param minLines the minimum number of lines this text field will show + * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s + * @param contentAlignment the vertical alignment of the text field content + * @param colors the [CupertinoTextFieldColors] to be used to configure the text field's appearance + */ @Composable fun CupertinoTextField( value: String, From 99c882349ae973ca380bcf58665b9d41a06155c6 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 11:07:33 +0900 Subject: [PATCH 047/109] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(core):=20?= =?UTF-8?q?=EC=A0=81=EC=9D=91=ED=98=95=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20=EB=B0=8F=20?= =?UTF-8?q?=EB=AC=B8=EC=84=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AdaptiveButton, AdaptiveCheckbox, AdaptiveDivider 등 주요 컴포넌트의 코드 구조 개선 및 주석 추가 - Material Design과 Cupertino 테마에 따른 적응형 UI 구현 로직 강화 - 패키지 구조 및 import 문 정리로 코드 일관성 향상 --- .../zone/ien/hig/adaptive/AdaptiveButton.kt | 84 ++++++++- .../zone/ien/hig/adaptive/AdaptiveCheckbox.kt | 129 +++++++++++++ .../ien/hig/adaptive/AdaptiveComponent.kt | 25 +++ .../zone/ien/hig/adaptive/AdaptiveDivider.kt | 120 +++++++++++- .../zone/ien/hig/adaptive/AdaptiveScaffold.kt | 52 ++++++ .../zone/ien/hig/adaptive/AdaptiveSwitch.kt | 107 +++++++++-- .../zone/ien/hig/adaptive/AdaptiveTheme.kt | 105 ++++++++--- .../ien/hig/adaptive/AdaptiveTopAppBar.kt | 174 +++++++++++++++++- .../zone/ien/hig/adaptive/AdaptiveWidget.kt | 14 ++ .../zone/ien/hig/adaptive/DefaultTheme.ios.kt | 6 + .../ien/hig/adaptive/icons/SystemImage.kt | 9 + .../zone/ien/hig/adaptive/SystemTheme.kt | 9 + .../ien/hig/adaptive/DefaultTheme.nonIos.kt | 6 + .../ien/hig/adaptive/icons/SystemImage.kt | 9 + 14 files changed, 795 insertions(+), 54 deletions(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt index abbb369f..cd3605a9 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt @@ -50,8 +50,21 @@ import zone.ien.hig.ExperimentalCupertinoApi import zone.ien.hig.theme.CupertinoTheme /** - * Adaptive button that takes [Button] or borderedProminent [CupertinoButton] appearance - * */ + * 적응형 버튼으로, Material [Button] 또는 테두리가 있는 [CupertinoButton] 모양을 사용합니다. + * + * Material Design에서는 [Button] 컴포넌트를 사용하고, Cupertino 테마에서는 [CupertinoLiquidButton] 컴포넌트를 사용하여 + * 운영체제별로 적절한 UI를 제공합니다. + * + * @param onClick 버튼이 클릭되었을 때 호출되는 함수 + * @param modifier 버튼에 적용할 Modifier + * @param enabled 버튼이 활성화되어 있는지 여부 + * @param interactionSource 상호작용 소스 + * @param adaptation [CupertinoButtonAdaptation]와 [MaterialButtonAdaptation]에 대한 사용자 정의 설정 함수 + * @param content 버튼 내부에 표시될 내용 + * @see AdaptiveWidget + * @see CupertinoButton + * @see Button + */ @OptIn(ExperimentalCupertinoApi::class) @ExperimentalAdaptiveApi @Composable @@ -101,8 +114,21 @@ fun AdaptiveButton( } /** - * Adaptive button that takes [TextButton] or borderless [CupertinoButton] appearance - * */ + * 적응형 버튼으로, Material [TextButton] 또는 테두리가 없는 [CupertinoButton] 모양을 사용합니다. + * + * Material Design에서는 [TextButton] 컴포넌트를 사용하고, Cupertino 테마에서는 [CupertinoLiquidButton] 컴포넌트를 사용하여 + * 운영체제별로 적절한 UI를 제공합니다. + * + * @param onClick 버튼이 클릭되었을 때 호출되는 함수 + * @param modifier 버튼에 적용할 Modifier + * @param enabled 버튼이 활성화되어 있는지 여부 + * @param interactionSource 상호작용 소스 + * @param adaptation [CupertinoButtonAdaptation]와 [MaterialButtonAdaptation]에 대한 사용자 정의 설정 함수 + * @param content 버튼 내부에 표시될 내용 + * @see AdaptiveWidget + * @see CupertinoButton + * @see TextButton + */ @OptIn(ExperimentalCupertinoApi::class) @ExperimentalAdaptiveApi @Composable @@ -152,8 +178,21 @@ fun AdaptiveTextButton( } /** - * Adaptive button that takes [FilledTonalButton] or bordered [CupertinoButton] appearance - * */ + * 적응형 버튼으로, Material [FilledTonalButton] 또는 테두리가 있는 [CupertinoButton] 모양을 사용합니다. + * + * Material Design에서는 [FilledTonalButton] 컴포넌트를 사용하고, Cupertino 테마에서는 [CupertinoLiquidButton] 컴포넌트를 사용하여 + * 운영체제별로 적절한 UI를 제공합니다. + * + * @param onClick 버튼이 클릭되었을 때 호출되는 함수 + * @param modifier 버튼에 적용할 Modifier + * @param enabled 버튼이 활성화되어 있는지 여부 + * @param interactionSource 상호작용 소스 + * @param adaptation [CupertinoButtonAdaptation]와 [MaterialButtonAdaptation]에 대한 사용자 정의 설정 함수 + * @param content 버튼 내부에 표시될 내용 + * @see AdaptiveWidget + * @see CupertinoButton + * @see FilledTonalButton + */ @OptIn(ExperimentalCupertinoApi::class) @ExperimentalAdaptiveApi @Composable @@ -204,6 +243,18 @@ fun AdaptiveTonalButton( @Stable +/** + * [CupertinoButton]에 대한 적응형 어댑테이션 클래스로, Cupertino 버튼 스타일의 다양한 속성을 관리합니다. + * + * @param colors [CupertinoLiquidButtonColors] - 버튼의 색상 설정 + * @param backdrop [LayerBackdrop] - 버튼의 레이어 배경 설정 + * @param isBackgroundAdaptive 배경이 적응형으로 처리되는지 여부 + * @see CupertinoLiquidButtonColors + * @see LayerBackdrop + * @see CupertinoButtonSize + * @see Shape + * @see PaddingValues + */ class CupertinoButtonAdaptation internal constructor( colors: CupertinoLiquidButtonColors, backdrop: LayerBackdrop, @@ -218,6 +269,20 @@ class CupertinoButtonAdaptation internal constructor( } @Stable +/** + * [MaterialButton]에 대한 적응형 어댑테이션 클래스로, Material 버튼 스타일의 다양한 속성을 관리합니다. + * + * @param colors [ButtonColors] - 버튼의 색상 설정 + * @param elevation [ButtonElevation] - 버튼의 그림자 효과 + * @param shape [Shape] - 버튼의 모양 + * @param contentPadding [PaddingValues] - 버튼 내용의 패딩 + * @param border [BorderStroke] - 버튼 테두리 스타일 + * @see ButtonColors + * @see ButtonElevation + * @see Shape + * @see PaddingValues + * @see BorderStroke + */ class MaterialButtonAdaptation internal constructor( colors: ButtonColors, elevation: ButtonElevation?, @@ -237,6 +302,13 @@ private enum class ButtonType { } @ExperimentalAdaptiveApi +/** + * 버튼 유형에 따라 적응형 어댑테이션을 제공하는 클래스입니다. + * + * @param type [ButtonType] - 버튼의 유형 (Filled, Text, Tonal) + * @see CupertinoButtonAdaptation + * @see MaterialButtonAdaptation + */ private class ButtonAdaptation( private val type: ButtonType, ): Adaptation() { diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt index 701c344d..8ff93464 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt @@ -36,6 +36,22 @@ import zone.ien.hig.CupertinoCheckboxColors import zone.ien.hig.CupertinoCheckboxDefaults import zone.ien.hig.CupertinoTriStateCheckBox +/** + * 적응형 체크박스 컴포넌트로, Material Design과 Cupertino 테마에 따라 다른 체크박스 스타일을 제공합니다. + * + * [AdaptiveCheckbox]는 Material Design의 [Checkbox] 컴포넌트와 Cupertino의 [CupertinoCheckBox] 컴포넌트를 사용하여 + * 운영체제별로 적절한 체크박스를 제공합니다. + * + * @param checked 체크박스의 현재 상태 (-checked) + * @param onCheckedChange 체크박스 상태 변경 시 호출되는 함수 + * @param modifier 요소에 적용할 Modifier + * @param enabled 체크박스가 활성화되어 있는지 여부 + * @param interactionSource 상호작용 소스 + * @param adaptation [CupertinoCheckBoxAdaptation]와 [MaterialCheckBoxAdaptation]에 대한 사용자 정의 설정 함수 + * @see AdaptiveWidget + * @see CupertinoCheckBox + * @see Checkbox + */ @ExperimentalAdaptiveApi @Composable fun AdaptiveCheckbox( @@ -72,6 +88,119 @@ fun AdaptiveCheckbox( ) } +/** + * 적응형 삼중 상태 체크박스 컴포넌트로, Material Design과 Cupertino 테마에 따라 다른 체크박스 스타일을 제공합니다. + * + * [AdaptiveTriStateCheckbox]는 Material Design의 [TriStateCheckbox] 컴포넌트와 Cupertino의 [CupertinoTriStateCheckBox] 컴포넌트를 사용하여 + * 운영체제별로 적절한 삼중 상태 체크박스를 제공합니다. + * + * @param state 체크박스의 상태 (Unchecked, Checked, Indeterminate) + * @param onClick 체크박스 클릭 시 호출되는 함수 + * @param modifier 요소에 적용할 Modifier + * @param enabled 체크박스가 활성화되어 있는지 여부 + * @param interactionSource 상호작용 소스 + * @param adaptation [CupertinoCheckBoxAdaptation]와 [MaterialCheckBoxAdaptation]에 대한 사용자 정의 설정 함수 + * @see AdaptiveWidget + * @see CupertinoTriStateCheckBox + * @see TriStateCheckbox + */ +@ExperimentalAdaptiveApi +@Composable +fun AdaptiveTriStateCheckbox( + state: ToggleableState, + onClick: (() -> Unit)?, + modifier: Modifier = Modifier, + enabled: Boolean = true, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + adaptation: AdaptationScope.() -> Unit = {} +) { + AdaptiveWidget( + adaptation = remember { CheckBoxAdaptation() }, + adaptationScope = adaptation, + material = { + TriStateCheckbox( + state = state, + onClick = onClick, + modifier = modifier, + enabled = enabled, + interactionSource = interactionSource, + colors = it.colors + ) + }, + cupertino = { + CupertinoTriStateCheckBox( + state = state, + onClick = onClick, + modifier = modifier, + enabled = enabled, + interactionSource = interactionSource, + colors = it.colors + ) + } + ) +} + +@Stable +/** + * Material Design 체크박스에 대한 적응형 어댑테이션 클래스로, 체크박스 색상 속성을 관리합니다. + * + * @param colors [CheckboxColors] - 체크박스의 색상 설정 + * @see CheckboxColors + * @see CheckboxDefaults + */ +class MaterialCheckBoxAdaptation( + colors: CheckboxColors +) { + var colors: CheckboxColors by mutableStateOf(colors) +} + +@Stable +/** + * Cupertino 체크박스에 대한 적응형 어댑테이션 클래스로, 체크박스 색상 속성을 관리합니다. + * + * @param colors [CupertinoCheckboxColors] - 체크박스의 색상 설정 + * @see CupertinoCheckboxColors + * @see CupertinoCheckboxDefaults + */ +class CupertinoCheckBoxAdaptation( + colors: CupertinoCheckboxColors +){ + var colors: CupertinoCheckboxColors by mutableStateOf(colors) +} + +@OptIn(ExperimentalAdaptiveApi::class) +@Stable +/** + * 체크박스 컴포넌트에 대한 적응형 어댑테이션을 구현하는 클래스입니다. + * + * @see Adaptation + * @see CupertinoCheckBoxAdaptation + * @see MaterialCheckBoxAdaptation + */ +private class CheckBoxAdaptation: Adaptation(){ + + @Composable + override fun rememberCupertinoAdaptation(): CupertinoCheckBoxAdaptation { + val colors = CupertinoCheckboxDefaults.colors() + + return remember(colors) { + CupertinoCheckBoxAdaptation(colors) + } + } + + @Composable + override fun rememberMaterialAdaptation(): MaterialCheckBoxAdaptation { + val colors = CheckboxDefaults.colors() + + return remember(colors) { + MaterialCheckBoxAdaptation(colors) + } + } + +} + ) +} + @ExperimentalAdaptiveApi @Composable fun AdaptiveTriStateCheckbox( diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveComponent.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveComponent.kt index d1ae7c1f..6e72e3c8 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveComponent.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveComponent.kt @@ -22,6 +22,17 @@ package zone.ien.hig.adaptive import androidx.compose.runtime.Composable +/** + * [adaptiveComponent]는 현재 테마에 따라 적절한 컴포넌트를 렌더링하는 함수입니다. + * + * 이 함수는 Material Design과 Cupertino 테마를 지원하며, 현재 테마에 따라 적절한 컴포넌트를 렌더링합니다. + * + * @param material Material Design 테마에 사용될 컴포저블 블록입니다. + * @param cupertino Cupertino 테마에 사용될 컴포저블 블록입니다. + * @return 현재 테마에 따라 선택된 컴포넌트를 반환합니다. + * @see LocalTheme + * @see Theme + */ @Composable @ExperimentalAdaptiveApi fun adaptiveComponent( @@ -34,6 +45,20 @@ fun adaptiveComponent( } } +/** + * [adaptiveComponent]는 적응형 컴포넌트를 지원하는 함수로, 특정 어댑테이션 설정을 기반으로 + * Material Design과 Cupertino 테마에 따라 적절한 컴포넌트를 반환합니다. + * + * @param adaptation 적용할 어댑테이션 설정입니다. + * @param material Material Design 테마에 사용될 컴포저블 블록입니다. + * @param cupertino Cupertino 테마에 사용될 컴포저블 블록입니다. + * @param adaptationScope 어댑테이션 설정을 위한 확장 함수입니다. + * @return 현재 테마에 따라 선택된 컴포넌트를 반환합니다. + * @see Adaptation + * @see AdaptationScope + * @see LocalTheme + * @see Theme + */ @Composable @ExperimentalAdaptiveApi fun adaptiveComponent( diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveDivider.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveDivider.kt index 93fb54e1..0d76a85c 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveDivider.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveDivider.kt @@ -23,10 +23,10 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue +import androidx.compose.runtime.by import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.runtime.var import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp @@ -34,6 +34,17 @@ import zone.ien.hig.CupertinoDividerDefaults import zone.ien.hig.CupertinoHorizontalDivider import zone.ien.hig.CupertinoVerticalDivider +/** + * 적응형 구분선 컴포넌트로, Material Design과 Cupertino 테마에 따라 다른 구분선 스타일을 제공합니다. + * + * 이 함수는 [AdaptiveHorizontalDivider] 함수로 대체되었습니다. + * + * @param modifier 요소에 적용할 Modifier + * @param adaptation [DividerAdaptation]에 대한 사용자 정의 설정 함수 + * @see AdaptiveHorizontalDivider + * @see HorizontalDivider + * @see CupertinoHorizontalDivider + */ @Deprecated( replaceWith = ReplaceWith( "AdaptiveHorizontalDivider(modifier,adaptation)", @@ -48,6 +59,18 @@ fun AdaptiveDivider( ) = AdaptiveHorizontalDivider(modifier, adaptation) +/** + * 적응형 수평 구분선 컴포넌트로, Material Design과 Cupertino 테마에 따라 다른 구분선 스타일을 제공합니다. + * + * [AdaptiveHorizontalDivider]는 Material Design의 [HorizontalDivider] 컴포넌트와 Cupertino의 [CupertinoHorizontalDivider] 컴포넌트를 사용하여 + * 운영체제별로 적절한 수평 구분선을 제공합니다. + * + * @param modifier 요소에 적용할 Modifier + * @param adaptation [DividerAdaptation]에 대한 사용자 정의 설정 함수 + * @see AdaptiveWidget + * @see HorizontalDivider + * @see CupertinoHorizontalDivider + */ @Composable @ExperimentalAdaptiveApi fun AdaptiveHorizontalDivider( @@ -76,6 +99,99 @@ fun AdaptiveHorizontalDivider( ) } +/** + * 적응형 수직 구분선 컴포넌트로, Material Design과 Cupertino 테마에 따라 다른 구분선 스타일을 제공합니다. + * + * [AdaptiveVerticalDivider]는 Material Design의 [VerticalDivider] 컴포넌트와 Cupertino의 [CupertinoVerticalDivider] 컴포넌트를 사용하여 + * 운영체제별로 적절한 수직 구분선을 제공합니다. + * + * @param modifier 요소에 적용할 Modifier + * @param adaptation [DividerAdaptation]에 대한 사용자 정의 설정 함수 + * @see AdaptiveWidget + * @see VerticalDivider + * @see CupertinoVerticalDivider + */ +@Composable +@ExperimentalAdaptiveApi +fun AdaptiveVerticalDivider( + modifier: Modifier = Modifier, + adaptation: AdaptationScope.() -> Unit = {} +) { + AdaptiveWidget( + adaptation = remember { + DividerAdaptationScope() + }, + adaptationScope = adaptation, + material = { + VerticalDivider( + modifier = modifier, + thickness = it.thickness, + color = it.color + ) + }, + cupertino = { + CupertinoVerticalDivider( + modifier = modifier, + thickness = it.thickness, + color = it.color + ) + } + ) +} + +@ExperimentalAdaptiveApi +/** + * 구분선 컴포넌트에 대한 적응형 어댑테이션을 구현하는 클래스입니다. + * + * @see Adaptation + * @see DividerAdaptation + */ +private class DividerAdaptationScope: Adaptation() { + + @Composable + override fun rememberCupertinoAdaptation(): DividerAdaptation { + + val color = CupertinoDividerDefaults.color + return remember(color) { + DividerAdaptation( + color = color, + thickness = CupertinoDividerDefaults.Thickness + ) + } + } + + @Composable + override fun rememberMaterialAdaptation(): DividerAdaptation { + val color = DividerDefaults.color + + return remember(color) { + DividerAdaptation( + color = color, + thickness = DividerDefaults.Thickness + ) + } + } +} + +@Stable +/** + * 구분선 컴포넌트에 대한 적응형 어댑테이션 클래스로, 구분선의 색상과 두께 속성을 관리합니다. + * + * @param color 구분선의 색상 + * @param thickness 구분선의 두께 + * @see Color + * @see Dp + */ +class DividerAdaptation internal constructor( + color: Color, + thickness: Dp +) { + var color: Color by mutableStateOf(color) + var thickness: Dp by mutableStateOf(thickness) +} + ) +} + @Composable @ExperimentalAdaptiveApi fun AdaptiveVerticalDivider( diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveScaffold.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveScaffold.kt index 3bb44f62..b262cdc1 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveScaffold.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveScaffold.kt @@ -40,6 +40,25 @@ import zone.ien.hig.ExperimentalCupertinoApi import zone.ien.hig.FabPosition +/** + * 적응형 [Scaffold] 컴포넌트로, Material Design과 Cupertino 테마에 따라 다른 레이아웃을 제공합니다. + * + * [AdaptiveScaffold]는 Material Design의 [Scaffold] 컴포넌트와 Cupertino의 [CupertinoScaffold] 컴포넌트를 사용하여 + * 운영체제별로 적절한 레이아웃을 제공합니다. + * + * @param modifier 요소에 적용할 Modifier + * @param topBar 상단 탑바 컴포넌트 + * @param bottomBar 하단 바 컴포넌트 + * @param snackbarHost 스낵바 호스트 컴포넌트 + * @param floatingActionButton 플로팅 액션 버튼 컴포넌트 + * @param floatingActionButtonPosition 플로팅 액션 버튼 위치 + * @param contentWindowInsets 내용 패딩 설정 + * @param adaptation [ScaffoldAdaptation]에 대한 사용자 정의 설정 함수 + * @param content 컨텐츠 영역 컴포넌트 + * @see AdaptiveWidget + * @see CupertinoScaffold + * @see Scaffold + */ @OptIn(ExperimentalCupertinoApi::class) @ExperimentalAdaptiveApi @Composable @@ -93,6 +112,26 @@ fun AdaptiveScaffold( ) } +/** + * 적응형 [Scaffold] 컴포넌트로, Material Design과 Cupertino 테마에 따라 다른 레이아웃을 제공합니다. + * + * [AdaptiveScaffold]는 Material Design의 [Scaffold] 컴포넌트와 Cupertino의 [CupertinoScaffold] 컴포넌트를 사용하여 + * 운영체제별로 적절한 레이아웃을 제공합니다. + * + * @param modifier 요소에 적용할 Modifier + * @param topBar 상단 탑바 컴포넌트 + * @param bottomBar 하단 바 컴포넌트 + * @param snackbarHost 스낵바 호스트 컴포넌트 + * @param floatingActionButton 플로팅 액션 버튼 컴포넌트 + * @param floatingActionButtonPosition 플로팅 액션 버튼 위치 + * @param containerColor 스크린 컨테이너의 배경 색상 + * @param contentColor 스크린 내용의 색상 + * @param contentWindowInsets 내용 패딩 설정 + * @param content 컨텐츠 영역 컴포넌트 + * @see AdaptiveWidget + * @see CupertinoScaffold + * @see Scaffold + */ @OptIn(ExperimentalCupertinoApi::class) @ExperimentalAdaptiveApi @Composable @@ -153,6 +192,13 @@ fun AdaptiveScaffold( } @Stable +/** + * [Scaffold] 컴포넌트에 대한 적응형 어댑테이션 클래스로, Scaffold 스타일의 다양한 속성을 관리합니다. + * + * @param contentColor 스크린 내용의 색상 + * @param containerColor 스크린 컨테이너의 배경 색상 + * @see Color + */ class ScaffoldAdaptation internal constructor( contentColor: Color, containerColor: Color @@ -162,6 +208,12 @@ class ScaffoldAdaptation internal constructor( } @OptIn(ExperimentalAdaptiveApi::class) @Stable +/** + * [Scaffold] 컴포넌트에 대한 적응형 어댑테이션을 구현하는 클래스입니다. + * + * @see Adaptation + * @see ScaffoldAdaptation + */ private class ScaffoldAdaptationImpl : Adaptation() { diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt index f956745c..8a800f28 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt @@ -17,7 +17,6 @@ */ - package zone.ien.hig.adaptive import androidx.compose.foundation.interaction.Interaction @@ -27,10 +26,10 @@ import androidx.compose.material3.SwitchColors import androidx.compose.material3.SwitchDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue +import androidx.compose.runtime.by import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.runtime.var import androidx.compose.ui.Modifier import com.kyant.backdrop.Backdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop @@ -40,20 +39,20 @@ import zone.ien.hig.CupertinoSwitchColors import zone.ien.hig.CupertinoSwitchDefaults /** - * Adaptive Switch depending on current [Theme]. + * 현재 [Theme]에 따라 적응형 스위치입니다. * - * Switches toggle the state of a single item on or off. + * 스위치는 단일 항목을 켜거나 끕니다. * - * @param checked whether or not this switch is checked - * @param onCheckedChange called when this switch is clicked. If `null`, then this switch will not - * be interactable, unless something else handles its input events and updates its state. - * @param modifier the [Modifier] to be applied to this switch - * @param thumbContent content that will be drawn inside the thumb - * @param enabled controls the enabled state of this switch. When `false`, this component will not - * respond to user input, and it will appear visually disabled and disabled to accessibility - * services. - * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s - * @param adaptation configuration block for theme-dependent properties for this switch + * @param checked 스위치가 체크되었는지 여부 + * @param onCheckedChange 스위치가 클릭되었을 때 호출되는 함수. null인 경우, 이 스위치는 상호작용이 불가능하며, 다른 요소가 입력 이벤트를 처리하고 상태를 업데이트해야 합니다. + * @param modifier 스위치에 적용할 [Modifier] + * @param thumbContent 스위치의 엄지/thumb 안에 표시할 내용 + * @param enabled 스위치의 활성화 상태를 제어합니다. false인 경우, 이 컴포넌트는 사용자 입력에 응답하지 않으며, 시각적으로 비활성화되고 접근성 서비스에서는 비활성화된 상태로 표시됩니다. + * @param interactionSource [Interaction] 스트림을 나타내는 [MutableInteractionSource] + * @param adaptation 테마에 따라 다른 속성을 설정하는 구성 블록 + * @see AdaptiveWidget + * @see CupertinoSwitch + * @see Switch */ @ExperimentalAdaptiveApi @Composable @@ -99,6 +98,76 @@ fun AdaptiveSwitch( } @Stable +/** + * Cupertino 스위치에 대한 적응형 어댑테이션 클래스로, 스위치의 다양한 속성을 관리합니다. + * + * @param colors [CupertinoSwitchColors] - 스위치의 색상 설정 + * @param backdrop [Backdrop] - 스위치의 배경 + * @see CupertinoSwitchColors + * @see Backdrop + */ +class CupertinoSwitchAdaptation internal constructor( + colors: CupertinoSwitchColors, + backdrop: Backdrop +) { + var colors by mutableStateOf(colors) + var backdrop by mutableStateOf(backdrop) +} + +@Stable +/** + * Material 스위치에 대한 적응형 어댑테이션 클래스로, 스위치의 다양한 속성을 관리합니다. + * + * @param colors [SwitchColors] - 스위치의 색상 설정 + * @see SwitchColors + * @see SwitchDefaults + */ +class MaterialSwitchAdaptation internal constructor( + colors: SwitchColors, +) { + var colors by mutableStateOf(colors) +} + +@OptIn(ExperimentalAdaptiveApi::class) +@Stable +/** + * 스위치 유형에 따라 적응형 어댑테이션을 제공하는 클래스입니다. + * + * @see Adaptation + * @see CupertinoSwitchAdaptation + * @see MaterialSwitchAdaptation + */ +private class SwitchAdaptation: Adaptation() { + @Composable + override fun rememberCupertinoAdaptation(): CupertinoSwitchAdaptation { + val colors = CupertinoSwitchDefaults.colors() + val backdrop = rememberLayerBackdrop() + + return remember(colors, backdrop) { + CupertinoSwitchAdaptation( + colors = colors, + backdrop = backdrop + ) + } + } + + @Composable + override fun rememberMaterialAdaptation(): MaterialSwitchAdaptation { + val colors = SwitchDefaults.colors() + + return remember(colors) { + MaterialSwitchAdaptation(colors) + } + } +} + +@Stable +/** + * Cupertino 스위치에 대한 적응형 어댑테이션 클래스로, 스위치의 다양한 속성을 관리합니다. + * + * @param colors [CupertinoSwitchColors] - 스위치의 색상 설정 + * @param backdrop [Backdrop] - 스위치의 배경 + */ class CupertinoSwitchAdaptation internal constructor( colors: CupertinoSwitchColors, backdrop: Backdrop @@ -108,6 +177,11 @@ class CupertinoSwitchAdaptation internal constructor( } @Stable +/** + * Material 스위치에 대한 적응형 어댑테이션 클래스로, 스위치의 다양한 속성을 관리합니다. + * + * @param colors [SwitchColors] - 스위치의 색상 설정 + */ class MaterialSwitchAdaptation internal constructor( colors: SwitchColors, ) { @@ -116,6 +190,9 @@ class MaterialSwitchAdaptation internal constructor( @OptIn(ExperimentalAdaptiveApi::class) @Stable +/** + * 스위치 유형에 따라 적응형 어댑테이션을 제공하는 클래스입니다. + */ private class SwitchAdaptation: Adaptation() { @Composable override fun rememberCupertinoAdaptation(): CupertinoSwitchAdaptation { diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt index 09ed2025..064c45a0 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt @@ -46,22 +46,28 @@ import zone.ien.hig.CupertinoText import androidx.compose.material3.LocalContentColor as MaterialLocalContentColor import androidx.compose.material3.LocalTextStyle as MaterialLocalTextStyle +/** + * 테마의 유형을 정의하는 열거형입니다. + * + * @property Cupertino Cupertino 스타일 테마 + * @property Material3 Material Design 3 스타일 테마 + */ enum class Theme { Cupertino, Material3 } /** - * Adaptive theme depending on [target]. It allows to seamlessly use Material and Cupertino widgets. - * - * This theme also make [Text] <-> [CupertinoText] and [Icon] <-> [CupertinoIcon] behave identically - * - * Current theme target can be accessed inside the [content] using [currentTheme] property - * - * @param target theme for adaptive widgets - * @param material [MaterialTheme] specification - * @param cupertino [CupertinoTheme] specification - * @param content themed content - * */ + * 적응형 테마로, 현재 테마에 따라 Material 또는 Cupertino 위젯을 사용합니다. + * + * 이 테마는 [Text] ↔ [CupertinoText] 및 [Icon] ↔ [CupertinoIcon] 컴포넌트를 동일하게 작동하게 만듭니다. + * + * 현재 테마는 [content] 내에서 [currentTheme] 속성을 사용하여 접근할 수 있습니다. + * + * @param target [AdaptiveWidget]에 대한 테마 + * @param material [MaterialTheme] 사양 + * @param cupertino [CupertinoTheme] 사양 + * @param content 테마가 적용된 내용 + */ @ExperimentalAdaptiveApi @Composable fun AdaptiveTheme( @@ -110,23 +116,20 @@ fun AdaptiveTheme( } /** - * Adaptive theme depending on [target]. It allows to seamlessly use Material and Cupertino widgets. - * - * This theme also allows to use [Text] together with [CupertinoText] and - * [Icon] together with [CupertinoIcon] both in Material and Cupertino widgets. - * This components will behave identically - * - * Current theme target can be accessed inside the [content] using [currentTheme] property. - * - * @param target theme for adaptive widgets. Defaults to [Theme.Cupertino] for iOS - * and [Theme.Material3] for other platforms - * @param material [MaterialTheme] specification. NOTE: You must use lambda parameter as a content - * @param cupertino [CupertinoTheme] specification. NOTE: You must use lambda parameter as a content - * @param content themed content - * */ + * 적응형 테마로, 현재 테마에 따라 Material 또는 Cupertino 위젯을 사용합니다. + * + * 이 테마는 [Text] ↔ [CupertinoText] 및 [Icon] ↔ [CupertinoIcon] 컴포넌트를 동일하게 작동하게 만듭니다. + * + * 현재 테마는 [content] 내에서 [currentTheme] 속성을 사용하여 접근할 수 있습니다. + * + * @param target [AdaptiveWidget]에 대한 테마. iOS의 경우 [Theme.Cupertino] 기본값을 사용하고, 다른 플랫폼의 경우 [Theme.Material3] 기본값을 사용합니다. + * @param material [MaterialTheme] 사양. 참고로 람다 파라미터를 내용으로 사용해야 합니다. + * @param cupertino [CupertinoTheme] 사양. 참고로 람다 파라미터를 내용으로 사용해야 합니다. + * @param content 테마가 적용된 내용 + */ @ExperimentalAdaptiveApi @Deprecated( - message = "Use variant with theme specs instead of lambdas", + message = "테마 사양을 파라미터로 받는 버전을 사용하세요", replaceWith = ReplaceWith( "AdaptiveTheme(target, MaterialThemeSpec.Default(), CupertinoThemeSpec.Default(), content)", "import zone.ien.hig.adaptive.MaterialThemeSpec", @@ -167,11 +170,26 @@ fun AdaptiveTheme( @Immutable @ExperimentalAdaptiveApi +/** + * Material 테마 사양 클래스로, Material Design 3 테마의 컬러, 모양, 타이포그래피를 정의합니다. + * + * @param colorScheme [MaterialColorScheme] - 테마의 컬러 스키마 + * @param shapes [MaterialShapes] - 테마의 모양 + * @param typography [MaterialTypography] - 테마의 타이포그래피 + */ class MaterialThemeSpec( val colorScheme: MaterialColorScheme = materialLightColorScheme(), val shapes: MaterialShapes = MaterialShapes(), val typography: MaterialTypography = MaterialTypography(), ) { + /** + * MaterialThemeSpec 인스턴스를 복사하여 새로운 인스턴스를 생성합니다. + * + * @param colorScheme 새로운 컬러 스키마 + * @param shapes 새로운 모양 + * @param typography 새로운 타이포그래피 + * @return 복사된 MaterialThemeSpec 인스턴스 + */ fun copy( colorScheme: MaterialColorScheme = this.colorScheme, shapes: MaterialShapes = this.shapes, @@ -187,6 +205,14 @@ class MaterialThemeSpec( } companion object { + /** + * MaterialThemeSpec의 기본 인스턴스를 생성합니다. + * + * @param colorScheme 컬러 스키마. 기본값은 MaterialTheme.colorScheme입니다. + * @param shapes 모양. 기본값은 MaterialTheme.shapes입니다. + * @param typography 타이포그래피. 기본값은 MaterialTheme.typography입니다. + * @return 생성된 MaterialThemeSpec 인스턴스 + */ @Composable fun Default( colorScheme: MaterialColorScheme = MaterialTheme.colorScheme, @@ -198,11 +224,26 @@ class MaterialThemeSpec( @Immutable @ExperimentalAdaptiveApi +/** + * Cupertino 테마 사양 클래스로, Cupertino 테마의 컬러, 모양, 타이포그래피를 정의합니다. + * + * @param colorScheme [CupertinoColorScheme] - 테마의 컬러 스키마 + * @param shapes [CupertinoShapes] - 테마의 모양 + * @param typography [CupertinoTypography] - 테마의 타이포그래피 + */ class CupertinoThemeSpec( val colorScheme: CupertinoColorScheme = cupertinoLightColorScheme(), val shapes: CupertinoShapes = CupertinoShapes(), val typography: CupertinoTypography = CupertinoTypography() ) { + /** + * CupertinoThemeSpec 인스턴스를 복사하여 새로운 인스턴스를 생성합니다. + * + * @param colorScheme 새로운 컬러 스키마 + * @param shapes 새로운 모양 + * @param typography 새로운 타이포그래피 + * @return 복사된 CupertinoThemeSpec 인스턴스 + */ fun copy( colorScheme: CupertinoColorScheme = this.colorScheme, shapes: CupertinoShapes = this.shapes, @@ -217,6 +258,14 @@ class CupertinoThemeSpec( return "CupertinoThemeSpec(colorScheme=$colorScheme, shapes=$shapes, typography=$typography)" } companion object { + /** + * CupertinoThemeSpec의 기본 인스턴스를 생성합니다. + * + * @param colorScheme 컬러 스키마. 기본값은 CupertinoTheme.colorScheme입니다. + * @param shapes 모양. 기본값은 CupertinoTheme.shapes입니다. + * @param typography 타이포그래피. 기본값은 CupertinoTheme.typography입니다. + * @return 생성된 CupertinoThemeSpec 인스턴스 + */ @Composable fun Default( colorScheme: CupertinoColorScheme = CupertinoTheme.colorScheme, @@ -228,8 +277,8 @@ class CupertinoThemeSpec( /** - * Theme declared as a target in [AdaptiveTheme] - * */ + * [AdaptiveTheme]에서 선언된 테마로, 현재 테마를 확인할 수 있습니다. + */ @ExperimentalAdaptiveApi val currentTheme: Theme @Composable diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTopAppBar.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTopAppBar.kt index 883c099a..6ff1722e 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTopAppBar.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTopAppBar.kt @@ -17,7 +17,6 @@ */ - package zone.ien.hig.adaptive import androidx.compose.foundation.layout.RowScope @@ -30,10 +29,10 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue +import androidx.compose.runtime.by import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.runtime.var import androidx.compose.ui.Modifier import com.kyant.backdrop.backdrops.LayerBackdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop @@ -43,6 +42,23 @@ import zone.ien.hig.CupertinoTopAppBarColors import zone.ien.hig.CupertinoTopAppBarDefaults import zone.ien.hig.ExperimentalCupertinoApi +/** + * 적응형 상단 앱바로, 현재 테마에 따라 Material 또는 Cupertino 스타일로 표시됩니다. + * + * [AdaptiveTopAppBar]는 Material Design의 [TopAppBar] 또는 [CenterAlignedTopAppBar] 컴포넌트와 + * Cupertino의 [CupertinoTopAppBar] 컴포넌트를 사용하여 운영체제별로 적절한 상단 앱바를 제공합니다. + * + * @param title 상단 앱바에 표시할 제목 + * @param modifier 상단 앱바에 적용할 Modifier + * @param navigationIcon 네비게이션 아이콘 + * @param actions 앱바에 표시할 액션 버튼들 + * @param windowInsets 윈도우 인셋 설정 + * @param adaptation [CupertinoTopAppBarAdaptation]와 [MaterialTopAppBarAdaptation]에 대한 사용자 정의 설정 함수 + * @see AdaptiveWidget + * @see CupertinoTopAppBar + * @see TopAppBar + * @see CenterAlignedTopAppBar + */ @OptIn(ExperimentalMaterial3Api::class, ExperimentalCupertinoApi::class) @ExperimentalAdaptiveApi @Composable @@ -86,6 +102,20 @@ fun AdaptiveTopAppBar( ) } +/** + * 단일 행 상단 앱바를 표시하는 내부 함수로, 중심 정렬 여부에 따라 다른 상단 앱바를 선택적으로 사용합니다. + * + * @param title 상단 앱바에 표시할 제목 + * @param isCenterAligned 제목이 중앙 정렬되는지 여부 + * @param colors 상단 앱바의 색상 설정 + * @param modifier 상단 앱바에 적용할 Modifier + * @param navigationIcon 네비게이션 아이콘 + * @param actions 앱바에 표시할 액션 버튼들 + * @param windowInsets 윈도우 인셋 설정 + * @param scrollBehavior 스크롤 행동 설정 + * @see TopAppBar + * @see CenterAlignedTopAppBar + */ @OptIn(ExperimentalMaterial3Api::class) @Composable private fun SingleRowTopAppBar( @@ -123,6 +153,16 @@ private fun SingleRowTopAppBar( @Stable @OptIn(ExperimentalMaterial3Api::class) +/** + * Material 상단 앱바에 대한 적응형 어댑테이션 클래스로, 상단 앱바의 다양한 속성을 관리합니다. + * + * @param colors [TopAppBarColors] - 상단 앱바의 색상 설정 + * @param isCenterAligned 제목이 중앙 정렬되는지 여부 + * @param scrollBehavior 스크롤 행동 설정 + * @see TopAppBarColors + * @see TopAppBarDefaults + * @see TopAppBarScrollBehavior + */ class MaterialTopAppBarAdaptation internal constructor( colors: TopAppBarColors, isCenterAligned: Boolean = false, @@ -134,6 +174,14 @@ class MaterialTopAppBarAdaptation internal constructor( } @Stable +/** + * Cupertino 상단 앱바에 대한 적응형 어댑테이션 클래스로, 상단 앱바의 다양한 속성을 관리합니다. + * + * @param colors [CupertinoTopAppBarColors] - 상단 앱바의 색상 설정 + * @param backdrop [LayerBackdrop] - 상단 앱바의 레이어 배경 + * @see CupertinoTopAppBarColors + * @see LayerBackdrop + */ class CupertinoTopAppBarAdaptation internal constructor( colors: CupertinoTopAppBarColors, backdrop: LayerBackdrop @@ -144,6 +192,126 @@ class CupertinoTopAppBarAdaptation internal constructor( @OptIn(ExperimentalAdaptiveApi::class) @Stable +/** + * 상단 앱바 유형에 따라 적응형 어댑테이션을 제공하는 클래스입니다. + * + * @see Adaptation + * @see CupertinoTopAppBarAdaptation + * @see MaterialTopAppBarAdaptation + */ +private class TopAppBarAdaptation: Adaptation() { + @Composable + override fun rememberCupertinoAdaptation(): CupertinoTopAppBarAdaptation { + val colors = CupertinoTopAppBarDefaults.topAppBarColors() + val backdrop = rememberLayerBackdrop() + + return remember(colors, backdrop) { + CupertinoTopAppBarAdaptation( + colors = colors, + backdrop = backdrop + ) + } + } + + @OptIn(ExperimentalMaterial3Api::class) + @Composable + override fun rememberMaterialAdaptation(): MaterialTopAppBarAdaptation { + val colors = TopAppBarDefaults.topAppBarColors() + + return remember(colors) { + MaterialTopAppBarAdaptation( + colors = colors, + ) + } + } +} + +/** + * 단일 행 상단 앱바를 표시하는 내부 함수로, 중심 정렬 여부에 따라 다른 상단 앱바를 선택적으로 사용합니다. + * + * @param title 상단 앱바에 표시할 제목 + * @param isCenterAligned 제목이 중앙 정렬되는지 여부 + * @param colors 상단 앱바의 색상 설정 + * @param modifier 상단 앱바에 적용할 Modifier + * @param navigationIcon 네비게이션 아이콘 + * @param actions 앱바에 표시할 액션 버튼들 + * @param windowInsets 윈도우 인셋 설정 + * @param scrollBehavior 스크롤 행동 설정 + */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun SingleRowTopAppBar( + title: @Composable () -> Unit, + isCenterAligned: Boolean, + colors: TopAppBarColors, + modifier: Modifier = Modifier, + navigationIcon: @Composable () -> Unit = {}, + actions: @Composable (RowScope.() -> Unit) = {}, + windowInsets: WindowInsets = CupertinoTopAppBarDefaults.windowInsets, + scrollBehavior: TopAppBarScrollBehavior? = null, +) { + if (isCenterAligned) { + CenterAlignedTopAppBar( + title = title, + modifier = modifier, + navigationIcon = navigationIcon, + actions = actions, + windowInsets = windowInsets, + colors = colors, + scrollBehavior = scrollBehavior, + ) + } else { + TopAppBar( + title = title, + modifier = modifier, + navigationIcon = navigationIcon, + actions = actions, + windowInsets = windowInsets, + colors = colors, + scrollBehavior = scrollBehavior, + ) + } +} + +@Stable +@OptIn(ExperimentalMaterial3Api::class) +/** + * Material 상단 앱바에 대한 적응형 어댑테이션 클래스로, 상단 앱바의 다양한 속성을 관리합니다. + * + * @param colors [TopAppBarColors] - 상단 앱바의 색상 설정 + * @param isCenterAligned 제목이 중앙 정렬되는지 여부 + * @param scrollBehavior 스크롤 행동 설정 + */ +class MaterialTopAppBarAdaptation internal constructor( + colors: TopAppBarColors, + isCenterAligned: Boolean = false, + scrollBehavior: TopAppBarScrollBehavior? = null, +) { + var isCenterAligned: Boolean by mutableStateOf(isCenterAligned) + var colors: TopAppBarColors by mutableStateOf(colors) + var scrollBehavior: TopAppBarScrollBehavior? by mutableStateOf(scrollBehavior) +} + +@Stable +/** + * Cupertino 상단 앱바에 대한 적응형 어댑테이션 클래스로, 상단 앱바의 다양한 속성을 관리합니다. + * + * @param colors [CupertinoTopAppBarColors] - 상단 앱바의 색상 설정 + * @param backdrop [LayerBackdrop] - 상단 앱바의 레이어 배경 + */ +class CupertinoTopAppBarAdaptation internal constructor( + colors: CupertinoTopAppBarColors, + backdrop: LayerBackdrop +) { + var colors: CupertinoTopAppBarColors by mutableStateOf(colors) + var backdrop: LayerBackdrop by mutableStateOf(backdrop) +} + +@OptIn(ExperimentalAdaptiveApi::class) +@Stable +/** + * 상단 앱바 유형에 따라 적응형 어댑테이션을 제공하는 클래스입니다. + */ private class TopAppBarAdaptation: Adaptation() { @Composable override fun rememberCupertinoAdaptation(): CupertinoTopAppBarAdaptation { diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveWidget.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveWidget.kt index 0a32a3d0..ecdb3f02 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveWidget.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveWidget.kt @@ -22,6 +22,12 @@ package zone.ien.hig.adaptive import androidx.compose.runtime.Composable +/** + * 기본 적응형 위젯으로, 주어진 내용을 현재 테마에 따라 Material 또는 Cupertino 스타일로 표시합니다. + * + * @param material Material 스타일로 표시할 내용 + * @param cupertino Cupertino 스타일로 표시할 내용 + */ @Composable @ExperimentalAdaptiveApi fun AdaptiveWidget( @@ -34,6 +40,14 @@ fun AdaptiveWidget( } } +/** + * 다양한 적응형 컴포넌트를 처리하는 고급 적응형 위젯입니다. + * + * @param adaptation [Adaptation] - 어댑테이션 정의 + * @param material Material 스타일로 표시할 내용 + * @param cupertino Cupertino 스타일로 표시할 내용 + * @param adaptationScope 어댑테이션에 대한 사용자 정의 설정 함수 + */ @Composable @ExperimentalAdaptiveApi fun AdaptiveWidget( diff --git a/hig-adaptive/src/iosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.ios.kt b/hig-adaptive/src/iosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.ios.kt index 08a297d3..aaf08ba9 100644 --- a/hig-adaptive/src/iosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.ios.kt +++ b/hig-adaptive/src/iosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.ios.kt @@ -18,5 +18,11 @@ package zone.ien.hig.adaptive +/** + * iOS 플랫폼에 맞춘 기본 테마를 반환하는 actual 값 + * + * 이 값은 iOS 환경에서 사용되는 기본 테마를 제공하며, + * iOS 전용 테마인 Cupertino 테마를 반환합니다. + */ internal actual val DefaultTheme: Theme get() = Theme.Cupertino \ No newline at end of file diff --git a/hig-adaptive/src/iosMain/kotlin/zone/ien/hig/adaptive/icons/SystemImage.kt b/hig-adaptive/src/iosMain/kotlin/zone/ien/hig/adaptive/icons/SystemImage.kt index 2ad3b1ff..03537606 100644 --- a/hig-adaptive/src/iosMain/kotlin/zone/ien/hig/adaptive/icons/SystemImage.kt +++ b/hig-adaptive/src/iosMain/kotlin/zone/ien/hig/adaptive/icons/SystemImage.kt @@ -25,5 +25,14 @@ import androidx.compose.ui.graphics.painter.Painter import zone.ien.hig.icons.CupertinoIcons import zone.ien.hig.named +/** + * iOS 플랫폼에 맞춘 시스템 이미지를 반환하는 함수 + * + * 이 함수는 주어진 이름에 해당하는 시스템 이미지를 반환하며, + * iOS 환경에서는 CupertinoIcons를 사용하여 이미지를 로드합니다. + * + * @param name 시스템 이미지 이름 + * @return 해당 이름의 시스템 이미지 또는 null + */ @Composable internal actual fun systemImage(name: String): Painter? = CupertinoIcons.named(name) diff --git a/hig-adaptive/src/nonAndroidMain/kotlin/zone/ien/hig/adaptive/SystemTheme.kt b/hig-adaptive/src/nonAndroidMain/kotlin/zone/ien/hig/adaptive/SystemTheme.kt index b4575b0e..c81b445e 100644 --- a/hig-adaptive/src/nonAndroidMain/kotlin/zone/ien/hig/adaptive/SystemTheme.kt +++ b/hig-adaptive/src/nonAndroidMain/kotlin/zone/ien/hig/adaptive/SystemTheme.kt @@ -19,6 +19,15 @@ package zone.ien.hig.adaptive import androidx.compose.material3.ColorScheme import androidx.compose.runtime.Composable +/** + * 시스템 Material Color Scheme을 반환하는 함수 + * + * 이 함수는 Android 플랫폼에서 사용되는 시스템 색상 기준을 반환하며, + * 현재는 null을 반환하는 구현을 가지고 있습니다. + * + * @param dark 다크 모드 여부 + * @return 시스템 색상 스킴 또는 null + */ @Composable internal actual fun systemMaterialColorScheme(dark: Boolean): ColorScheme? { val r: ColorScheme? = null // https://github.com/JetBrains/compose-multiplatform/issues/3900 diff --git a/hig-adaptive/src/nonIosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.nonIos.kt b/hig-adaptive/src/nonIosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.nonIos.kt index aedd1e03..219c49b2 100644 --- a/hig-adaptive/src/nonIosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.nonIos.kt +++ b/hig-adaptive/src/nonIosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.nonIos.kt @@ -18,5 +18,11 @@ package zone.ien.hig.adaptive +/** + * 비 iOS 플랫폼에 맞춘 기본 테마를 반환하는 actual 값 + * + * 이 값은 iOS가 아닌 플랫폼(예: Android, Desktop 등)에서 사용되는 + * 기본 테마를 제공하며, Material3 테마를 반환합니다. + */ internal actual val DefaultTheme: Theme get() = Theme.Material3 \ No newline at end of file diff --git a/hig-adaptive/src/nonIosMain/kotlin/zone/ien/hig/adaptive/icons/SystemImage.kt b/hig-adaptive/src/nonIosMain/kotlin/zone/ien/hig/adaptive/icons/SystemImage.kt index c7ebc344..1db5aecc 100644 --- a/hig-adaptive/src/nonIosMain/kotlin/zone/ien/hig/adaptive/icons/SystemImage.kt +++ b/hig-adaptive/src/nonIosMain/kotlin/zone/ien/hig/adaptive/icons/SystemImage.kt @@ -21,5 +21,14 @@ package zone.ien.hig.adaptive.icons import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.painter.Painter +/** + * 비 iOS 플랫폼에 맞춘 시스템 이미지를 반환하는 함수 + * + * 이 함수는 iOS가 아닌 플랫폼(예: Android, Desktop 등)에서 사용되며, + * 항상 null을 반환하여 시스템 이미지 사용을 방지합니다. + * + * @param name 시스템 이미지 이름 + * @return null (시스템 이미지 지원되지 않음) + */ @Composable internal actual fun systemImage(name: String): Painter? = null From c24453880eb58696595927f6e341954cd3f09a51 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 11:31:58 +0900 Subject: [PATCH 048/109] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(core):=20?= =?UTF-8?q?Adaptive=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81=20=EB=B0=8F=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AdaptiveCheckbox, AdaptiveDivider, AdaptiveSwitch, AdaptiveTopAppBar 컴포넌트의 상태 관리 방식을 개선하여 코드 복잡도 감소 - mutableStateOf 사용 방식을 by 대신 get/set 방식으로 변경하여 일관성 향상 - 불필요한 어댑테이션 클래스 및 함수 제거로 코드 간결화 - Compose runtime 관련 import 문 정리 및 최적화 --- .../zone/ien/hig/adaptive/AdaptiveCheckbox.kt | 77 ------------ .../zone/ien/hig/adaptive/AdaptiveDivider.kt | 73 +---------- .../zone/ien/hig/adaptive/AdaptiveSwitch.kt | 62 +-------- .../ien/hig/adaptive/AdaptiveTopAppBar.kt | 119 +----------------- 4 files changed, 9 insertions(+), 322 deletions(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt index 8ff93464..4aafc1bd 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt @@ -196,81 +196,4 @@ private class CheckBoxAdaptation: Adaptation Unit)?, - modifier: Modifier = Modifier, - enabled: Boolean = true, - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - adaptation: AdaptationScope.() -> Unit = {} -) { - AdaptiveWidget( - adaptation = remember { CheckBoxAdaptation() }, - adaptationScope = adaptation, - material = { - TriStateCheckbox( - state = state, - onClick = onClick, - modifier = modifier, - enabled = enabled, - interactionSource = interactionSource, - colors = it.colors - ) - }, - cupertino = { - CupertinoTriStateCheckBox( - state = state, - onClick = onClick, - modifier = modifier, - enabled = enabled, - interactionSource = interactionSource, - colors = it.colors - ) - } - ) -} - -@Stable -class MaterialCheckBoxAdaptation( - colors: CheckboxColors -) { - var colors: CheckboxColors by mutableStateOf(colors) -} - -@Stable -class CupertinoCheckBoxAdaptation( - colors: CupertinoCheckboxColors -){ - var colors: CupertinoCheckboxColors by mutableStateOf(colors) -} - -@OptIn(ExperimentalAdaptiveApi::class) -@Stable -private class CheckBoxAdaptation: Adaptation(){ - - @Composable - override fun rememberCupertinoAdaptation(): CupertinoCheckBoxAdaptation { - val colors = CupertinoCheckboxDefaults.colors() - - return remember(colors) { - CupertinoCheckBoxAdaptation(colors) - } - } - - @Composable - override fun rememberMaterialAdaptation(): MaterialCheckBoxAdaptation { - val colors = CheckboxDefaults.colors() - - return remember(colors) { - MaterialCheckBoxAdaptation(colors) - } - } - } \ No newline at end of file diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveDivider.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveDivider.kt index 0d76a85c..9f5047b5 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveDivider.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveDivider.kt @@ -23,10 +23,10 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable -import androidx.compose.runtime.by +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.var +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp @@ -188,71 +188,4 @@ class DividerAdaptation internal constructor( ) { var color: Color by mutableStateOf(color) var thickness: Dp by mutableStateOf(thickness) -} - ) -} - -@Composable -@ExperimentalAdaptiveApi -fun AdaptiveVerticalDivider( - modifier: Modifier = Modifier, - adaptation: AdaptationScope.() -> Unit = {} -) { - AdaptiveWidget( - adaptation = remember { - DividerAdaptationScope() - }, - adaptationScope = adaptation, - material = { - VerticalDivider( - modifier = modifier, - thickness = it.thickness, - color = it.color - ) - }, - cupertino = { - CupertinoVerticalDivider( - modifier = modifier, - thickness = it.thickness, - color = it.color - ) - } - ) -} - -@ExperimentalAdaptiveApi -private class DividerAdaptationScope: Adaptation() { - - @Composable - override fun rememberCupertinoAdaptation(): DividerAdaptation { - - val color = CupertinoDividerDefaults.color - return remember(color) { - DividerAdaptation( - color = color, - thickness = CupertinoDividerDefaults.Thickness - ) - } - } - - @Composable - override fun rememberMaterialAdaptation(): DividerAdaptation { - val color = DividerDefaults.color - - return remember(color) { - DividerAdaptation( - color = color, - thickness = DividerDefaults.Thickness - ) - } - } -} - -@Stable -class DividerAdaptation internal constructor( - color: Color, - thickness: Dp -) { - var color: Color by mutableStateOf(color) - var thickness: Dp by mutableStateOf(thickness) -} +} \ No newline at end of file diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt index 8a800f28..81afafd2 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt @@ -26,10 +26,10 @@ import androidx.compose.material3.SwitchColors import androidx.compose.material3.SwitchDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable -import androidx.compose.runtime.by +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.var +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import com.kyant.backdrop.Backdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop @@ -159,60 +159,4 @@ private class SwitchAdaptation: Adaptation() { - @Composable - override fun rememberCupertinoAdaptation(): CupertinoSwitchAdaptation { - val colors = CupertinoSwitchDefaults.colors() - val backdrop = rememberLayerBackdrop() - - return remember(colors, backdrop) { - CupertinoSwitchAdaptation( - colors = colors, - backdrop = backdrop - ) - } - } - - @Composable - override fun rememberMaterialAdaptation(): MaterialSwitchAdaptation { - val colors = SwitchDefaults.colors() - - return remember(colors) { - MaterialSwitchAdaptation(colors) - } - } -} +} \ No newline at end of file diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTopAppBar.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTopAppBar.kt index 6ff1722e..601ccd83 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTopAppBar.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTopAppBar.kt @@ -29,10 +29,10 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable -import androidx.compose.runtime.by +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.var +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import com.kyant.backdrop.backdrops.LayerBackdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop @@ -224,117 +224,4 @@ private class TopAppBarAdaptation: Adaptation Unit, - isCenterAligned: Boolean, - colors: TopAppBarColors, - modifier: Modifier = Modifier, - navigationIcon: @Composable () -> Unit = {}, - actions: @Composable (RowScope.() -> Unit) = {}, - windowInsets: WindowInsets = CupertinoTopAppBarDefaults.windowInsets, - scrollBehavior: TopAppBarScrollBehavior? = null, -) { - if (isCenterAligned) { - CenterAlignedTopAppBar( - title = title, - modifier = modifier, - navigationIcon = navigationIcon, - actions = actions, - windowInsets = windowInsets, - colors = colors, - scrollBehavior = scrollBehavior, - ) - } else { - TopAppBar( - title = title, - modifier = modifier, - navigationIcon = navigationIcon, - actions = actions, - windowInsets = windowInsets, - colors = colors, - scrollBehavior = scrollBehavior, - ) - } -} - -@Stable -@OptIn(ExperimentalMaterial3Api::class) -/** - * Material 상단 앱바에 대한 적응형 어댑테이션 클래스로, 상단 앱바의 다양한 속성을 관리합니다. - * - * @param colors [TopAppBarColors] - 상단 앱바의 색상 설정 - * @param isCenterAligned 제목이 중앙 정렬되는지 여부 - * @param scrollBehavior 스크롤 행동 설정 - */ -class MaterialTopAppBarAdaptation internal constructor( - colors: TopAppBarColors, - isCenterAligned: Boolean = false, - scrollBehavior: TopAppBarScrollBehavior? = null, -) { - var isCenterAligned: Boolean by mutableStateOf(isCenterAligned) - var colors: TopAppBarColors by mutableStateOf(colors) - var scrollBehavior: TopAppBarScrollBehavior? by mutableStateOf(scrollBehavior) -} - -@Stable -/** - * Cupertino 상단 앱바에 대한 적응형 어댑테이션 클래스로, 상단 앱바의 다양한 속성을 관리합니다. - * - * @param colors [CupertinoTopAppBarColors] - 상단 앱바의 색상 설정 - * @param backdrop [LayerBackdrop] - 상단 앱바의 레이어 배경 - */ -class CupertinoTopAppBarAdaptation internal constructor( - colors: CupertinoTopAppBarColors, - backdrop: LayerBackdrop -) { - var colors: CupertinoTopAppBarColors by mutableStateOf(colors) - var backdrop: LayerBackdrop by mutableStateOf(backdrop) -} - -@OptIn(ExperimentalAdaptiveApi::class) -@Stable -/** - * 상단 앱바 유형에 따라 적응형 어댑테이션을 제공하는 클래스입니다. - */ -private class TopAppBarAdaptation: Adaptation() { - @Composable - override fun rememberCupertinoAdaptation(): CupertinoTopAppBarAdaptation { - val colors = CupertinoTopAppBarDefaults.topAppBarColors() - val backdrop = rememberLayerBackdrop() - - return remember(colors, backdrop) { - CupertinoTopAppBarAdaptation( - colors = colors, - backdrop = backdrop - ) - } - } - - @OptIn(ExperimentalMaterial3Api::class) - @Composable - override fun rememberMaterialAdaptation(): MaterialTopAppBarAdaptation { - val colors = TopAppBarDefaults.topAppBarColors() - - return remember(colors) { - MaterialTopAppBarAdaptation( - colors = colors, - ) - } - } -} +} \ No newline at end of file From c07ea3b43f3652452fd18755db0087b6c9b19ac9 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 11:34:59 +0900 Subject: [PATCH 049/109] Update hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt index cd3605a9..078fb5dd 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt @@ -50,7 +50,7 @@ import zone.ien.hig.ExperimentalCupertinoApi import zone.ien.hig.theme.CupertinoTheme /** - * 적응형 버튼으로, Material [Button] 또는 테두리가 있는 [CupertinoButton] 모양을 사용합니다. + * 적응형 버튼으로, Material [Button] 또는 테두리가 있는 [CupertinoLiquidButton] 모양을 사용합니다. * * Material Design에서는 [Button] 컴포넌트를 사용하고, Cupertino 테마에서는 [CupertinoLiquidButton] 컴포넌트를 사용하여 * 운영체제별로 적절한 UI를 제공합니다. From 4217fb131881127ca1e7151fceb6c84e65dc4af0 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 11:35:16 +0900 Subject: [PATCH 050/109] Update hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt index 078fb5dd..89095e1d 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt @@ -114,7 +114,7 @@ fun AdaptiveButton( } /** - * 적응형 버튼으로, Material [TextButton] 또는 테두리가 없는 [CupertinoButton] 모양을 사용합니다. + * 적응형 버튼으로, Material [TextButton] 또는 테두리가 없는 [CupertinoLiquidButton] 모양을 사용합니다. * * Material Design에서는 [TextButton] 컴포넌트를 사용하고, Cupertino 테마에서는 [CupertinoLiquidButton] 컴포넌트를 사용하여 * 운영체제별로 적절한 UI를 제공합니다. From 83b0df2c0c5727d5ce0b0e87756b542b988a3197 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 11:35:22 +0900 Subject: [PATCH 051/109] Update hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt index 89095e1d..6209712d 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt @@ -178,7 +178,7 @@ fun AdaptiveTextButton( } /** - * 적응형 버튼으로, Material [FilledTonalButton] 또는 테두리가 있는 [CupertinoButton] 모양을 사용합니다. + * 적응형 버튼으로, Material [FilledTonalButton] 또는 테두리가 있는 [CupertinoLiquidButton] 모양을 사용합니다. * * Material Design에서는 [FilledTonalButton] 컴포넌트를 사용하고, Cupertino 테마에서는 [CupertinoLiquidButton] 컴포넌트를 사용하여 * 운영체제별로 적절한 UI를 제공합니다. From 0b1e692e9d1256edd30ef47b8b8ba43e2e0491c7 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 11:35:29 +0900 Subject: [PATCH 052/109] Update hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt index 81afafd2..d94a2d6a 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt @@ -39,7 +39,7 @@ import zone.ien.hig.CupertinoSwitchColors import zone.ien.hig.CupertinoSwitchDefaults /** - * 현재 [Theme]에 따라 적응형 스위치입니다. + * 현재 [Theme]에 따라 스타일이 결정되는 적응형 스위치입니다. * * 스위치는 단일 항목을 켜거나 끕니다. * From 10ca67d73f362910e42bddb6211870603d96db15 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 11:35:36 +0900 Subject: [PATCH 053/109] Update hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt index 4aafc1bd..dbc8438c 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt @@ -42,7 +42,7 @@ import zone.ien.hig.CupertinoTriStateCheckBox * [AdaptiveCheckbox]는 Material Design의 [Checkbox] 컴포넌트와 Cupertino의 [CupertinoCheckBox] 컴포넌트를 사용하여 * 운영체제별로 적절한 체크박스를 제공합니다. * - * @param checked 체크박스의 현재 상태 (-checked) + * @param checked 체크박스의 현재 체크 여부 * @param onCheckedChange 체크박스 상태 변경 시 호출되는 함수 * @param modifier 요소에 적용할 Modifier * @param enabled 체크박스가 활성화되어 있는지 여부 From 17e566e602176e87e41f5bfefd8356f10724f02a Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 11:37:20 +0900 Subject: [PATCH 054/109] =?UTF-8?q?=20fix(core):=20AdaptiveCheckbox=20?= =?UTF-8?q?=EB=AC=B8=EC=84=9C=20=EC=A3=BC=EC=84=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 체크박스 상태 설명 문구를 (-checked)에서 (checked)로 수정 - API 문서 정확성 향상 - 코드 일관성 유지 --- .../commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt index 4aafc1bd..8fe077a6 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt @@ -42,7 +42,7 @@ import zone.ien.hig.CupertinoTriStateCheckBox * [AdaptiveCheckbox]는 Material Design의 [Checkbox] 컴포넌트와 Cupertino의 [CupertinoCheckBox] 컴포넌트를 사용하여 * 운영체제별로 적절한 체크박스를 제공합니다. * - * @param checked 체크박스의 현재 상태 (-checked) + * @param checked 체크박스의 현재 상태 (checked) * @param onCheckedChange 체크박스 상태 변경 시 호출되는 함수 * @param modifier 요소에 적용할 Modifier * @param enabled 체크박스가 활성화되어 있는지 여부 From 4d33f2f4db02b049be3e80a462a8a541063aafa5 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 11:38:56 +0900 Subject: [PATCH 055/109] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(core):=20?= =?UTF-8?q?Accessibility.kt=20=ED=8C=8C=EC=9D=BC=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Accessibility.kt 파일의 불필요한 suppress 주석 제거 - Color 확장 함수 accessible 로직 개선 및 코드 정리 - 시스템 접근성 설정 확인 로직 통합 및 최적화 --- .../kotlin/zone/ien/hig/Accessibility.kt | 32 +------------------ 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/hig-core/src/commonMain/kotlin/zone/ien/hig/Accessibility.kt b/hig-core/src/commonMain/kotlin/zone/ien/hig/Accessibility.kt index fe00e713..98f3f4e0 100644 --- a/hig-core/src/commonMain/kotlin/zone/ien/hig/Accessibility.kt +++ b/hig-core/src/commonMain/kotlin/zone/ien/hig/Accessibility.kt @@ -16,37 +16,7 @@ * limitations under the License. */ -@file:Suppress("unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", "unused", - "unused", "unused", "unused" -) +@file:Suppress("unused") package zone.ien.hig From 0d1d82181a552dff02ec759d3dc0e2cd350b0339 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 11:40:10 +0900 Subject: [PATCH 056/109] Update hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt index 560fc18e..8d307aa8 100644 --- a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt +++ b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp import androidx.compose.ui.window.PopupProperties import com.kyant.backdrop.Backdrop From 132c66678bc85fd9b41f5a0d132ac85172a8691c Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 11:40:16 +0900 Subject: [PATCH 057/109] Update hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt index 8d307aa8..92362838 100644 --- a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt +++ b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt @@ -31,6 +31,7 @@ import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.dp import androidx.compose.ui.window.PopupProperties import com.kyant.backdrop.Backdrop From d9046f4baae41e888a3b55adfa127d92d4b10cdb Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 11:40:32 +0900 Subject: [PATCH 058/109] Update hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDialogsNative.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../commonMain/kotlin/zone/ien/hig/CupertinoDialogsNative.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDialogsNative.kt b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDialogsNative.kt index 93692d7d..476748bb 100644 --- a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDialogsNative.kt +++ b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDialogsNative.kt @@ -56,7 +56,7 @@ expect fun CupertinoAlertDialogNative( ) /** - * Native analog for to compose [CupertinoActionSheet]. + * Native analog to [CupertinoActionSheet]. * * @param visible whether the action sheet is visible * @param onDismissRequest called when dialog is already dismissed. Must not be ignored From 1c0f339428bca9339a164a8c0ccc41539e4de23d Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 11:48:34 +0900 Subject: [PATCH 059/109] Update hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDialogsNative.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../commonMain/kotlin/zone/ien/hig/CupertinoDialogsNative.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDialogsNative.kt b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDialogsNative.kt index 476748bb..4c9f3c0f 100644 --- a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDialogsNative.kt +++ b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDialogsNative.kt @@ -56,7 +56,7 @@ expect fun CupertinoAlertDialogNative( ) /** - * Native analog to [CupertinoActionSheet]. + * Native analog for the compose [CupertinoActionSheet]. * * @param visible whether the action sheet is visible * @param onDismissRequest called when dialog is already dismissed. Must not be ignored From babe720bc48eaae87929613d29d67d4d7c3c9bc1 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 11:48:52 +0900 Subject: [PATCH 060/109] Update hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt index 92362838..0bd09ba3 100644 --- a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt +++ b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt @@ -42,7 +42,8 @@ import com.kyant.backdrop.Backdrop * @param enabled whether the menu item is enabled * @param icon an optional icon painter for the menu item * @param isDestructive whether this is a destructive menu action - * @param onClick callback when the menu item is clicked +@Immutable +data class CupertinoMenuItemData( */ data class CupertinoMenuItemData( val title: String, From 3de2f4ea9aba68ef3d0bf615e66fb81409cf0023 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 11:49:15 +0900 Subject: [PATCH 061/109] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(core):=20?= =?UTF-8?q?CupertinoDropdownMenuNative.kt=20=ED=8C=8C=EC=9D=BC=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ExperimentalComposeUiApi 및 Immutable import 순서 정리 - CupertinoMenuItemData 및 CupertinoMenuSectionData 데이터 클래스 구조 개선 - HigMenuOptions enum 클래스 정의 추가 - CupertinoDropdownMenuNative composable 함수 파라미터 기본값 수정 - backdrop 파라미터 기본값 emptyList()로 변경 --- .../ien/hig/CupertinoDropdownMenuNative.kt | 37 +++---------------- 1 file changed, 5 insertions(+), 32 deletions(-) diff --git a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt index 0bd09ba3..7502b0d9 100644 --- a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt +++ b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt @@ -24,6 +24,7 @@ import androidx.compose.foundation.ScrollState import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.rememberScrollState import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -61,6 +62,7 @@ data class CupertinoMenuItemData( * @param options display options for the section * @param items the list of menu items in this section */ +@Immutable data class CupertinoMenuSectionData( val title: String, val icon: Painter? = null, @@ -99,42 +101,13 @@ expect fun CupertinoDropdownMenuNative( expanded: Boolean, onDismissRequest: () -> Unit, modifier: Modifier = Modifier, - offset: DpOffset = DpOffset(0.dp, 0.dp), - paddingValues: PaddingValues = CupertinoDropdownMenuDefaults.PaddingValues, - containerColor: Color = CupertinoDropdownMenuDefaults.ContainerColor, - width: Dp = CupertinoDropdownMenuDefaults.DefaultWidth, - scrollState: ScrollState = rememberScrollState(), - properties: PopupProperties = PopupProperties(focusable = true, clippingEnabled = false), - backdrop: Backdrop, - items: List = listOf(), - sections: List = listOf(), -) - -data class CupertinoMenuSectionData( - val title: String, - val icon: Painter? = null, - val options: HigMenuOptions = HigMenuOptions.DisplayInline, - val items: List, -) - -enum class HigMenuOptions { - DisplayInline, SingleSelection, DisplayAsPalette, Destructive -} - -@OptIn(ExperimentalComposeUiApi::class) -@ExperimentalCupertinoApi -@Composable -expect fun CupertinoDropdownMenuNative( - expanded: Boolean, - onDismissRequest: () -> Unit, - modifier: Modifier = Modifier, - offset: DpOffset = DpOffset(0.dp, 0.dp), + offset: DpOffset = DpOffset.Zero, paddingValues: PaddingValues = CupertinoDropdownMenuDefaults.PaddingValues, containerColor: Color = CupertinoDropdownMenuDefaults.ContainerColor, width: Dp = CupertinoDropdownMenuDefaults.DefaultWidth, scrollState: ScrollState = rememberScrollState(), properties: PopupProperties = PopupProperties(focusable = true, clippingEnabled = false), backdrop: Backdrop, - items: List = listOf(), - sections: List = listOf(), + items: List = emptyList(), + sections: List = emptyList(), ) \ No newline at end of file From 027c7ed6094b5d7c400e132ac86a1dbe4787c106 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 11:51:29 +0900 Subject: [PATCH 062/109] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(core):=20?= =?UTF-8?q?=ED=96=85=ED=8B=B1=20=ED=94=BC=EB=93=9C=EB=B0=B1=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EB=AC=B8=EC=84=9C=ED=99=94=20=EB=B0=8F=20=EC=A0=84?= =?UTF-8?q?=ED=99=98=20=EC=A7=80=EC=86=8D=20=EC=8B=9C=EA=B0=84=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CupertinoHapticFeedback.kt 파일에 haptic feedback 타입에 대한 문서화 추가 - CupertinoTween.kt 파일에서 Cupertino 전환 지속 시간에 대한 주석 추가 - SystemBarAppearance.kt 파일에서 코드 변경 사항 적용 --- .../kotlin/zone/ien/hig/CupertinoHapticFeedback.kt | 8 -------- .../src/commonMain/kotlin/zone/ien/hig/CupertinoTween.kt | 1 - .../commonMain/kotlin/zone/ien/hig/SystemBarAppearance.kt | 1 - 3 files changed, 10 deletions(-) diff --git a/hig-core/src/commonMain/kotlin/zone/ien/hig/CupertinoHapticFeedback.kt b/hig-core/src/commonMain/kotlin/zone/ien/hig/CupertinoHapticFeedback.kt index 00bdc5a4..8b6af0fe 100644 --- a/hig-core/src/commonMain/kotlin/zone/ien/hig/CupertinoHapticFeedback.kt +++ b/hig-core/src/commonMain/kotlin/zone/ien/hig/CupertinoHapticFeedback.kt @@ -44,42 +44,34 @@ object CupertinoHapticFeedback { * Selection changed haptic feedback type. */ val SelectionChanged: HapticFeedbackType = HapticFeedbackType(1001) - /** * Success haptic feedback type. */ val Success: HapticFeedbackType = HapticFeedbackType(2001) - /** * Warning haptic feedback type. */ val Warning: HapticFeedbackType = HapticFeedbackType(2002) - /** * Error haptic feedback type. */ val Error: HapticFeedbackType = HapticFeedbackType(2003) - /** * Impact light haptic feedback type. */ val ImpactLight: HapticFeedbackType = HapticFeedbackType(3001) - /** * Impact medium haptic feedback type. */ val ImpactMedium: HapticFeedbackType = HapticFeedbackType(3002) - /** * Impact heavy haptic feedback type. */ val ImpactHeavy: HapticFeedbackType = HapticFeedbackType(3003) - /** * Impact rigid haptic feedback type. */ val ImpactRigid: HapticFeedbackType = HapticFeedbackType(3004) - /** * Impact soft haptic feedback type. */ diff --git a/hig-core/src/commonMain/kotlin/zone/ien/hig/CupertinoTween.kt b/hig-core/src/commonMain/kotlin/zone/ien/hig/CupertinoTween.kt index 16d11c6d..94c8a969 100644 --- a/hig-core/src/commonMain/kotlin/zone/ien/hig/CupertinoTween.kt +++ b/hig-core/src/commonMain/kotlin/zone/ien/hig/CupertinoTween.kt @@ -51,7 +51,6 @@ fun cupertinoTween( * The easing function used for Cupertino transitions. */ val CupertinoEasing = CubicBezierEasing(0.2833f, 0.99f, 0.31833f, 0.99f) - /** * The default transition duration for Cupertino transitions. */ diff --git a/hig-core/src/commonMain/kotlin/zone/ien/hig/SystemBarAppearance.kt b/hig-core/src/commonMain/kotlin/zone/ien/hig/SystemBarAppearance.kt index 4a7ba187..961d9e07 100644 --- a/hig-core/src/commonMain/kotlin/zone/ien/hig/SystemBarAppearance.kt +++ b/hig-core/src/commonMain/kotlin/zone/ien/hig/SystemBarAppearance.kt @@ -28,4 +28,3 @@ import androidx.compose.runtime.Composable @Composable @InternalCupertinoApi expect fun SystemBarAppearance(dark: Boolean) - From 010fd17b2cd195a17cd8289528d8a0b202d02cbc Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 13:40:53 +0900 Subject: [PATCH 063/109] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(core):=20?= =?UTF-8?q?Adaptive=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81=20=EB=B0=8F=20=ED=85=8C=EB=A7=88=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AdaptiveComponent, AdaptiveButton, AdaptiveCheckbox 등 17개 컴포넌트 리팩토링 - Shapes.kt 및 테마 관련 파일 수정으로 디자인 시스템 일관성 강화 - iOS 특화 컴포넌트 및 테마 파일 업데이트하여 크로스 플랫폼 호환성 개선 --- .../zone/ien/hig/adaptive/Adaptation.kt | 9 ++ .../ien/hig/adaptive/AdaptiveAlertDialog.kt | 6 + .../zone/ien/hig/adaptive/AdaptiveButton.kt | 15 +++ .../zone/ien/hig/adaptive/AdaptiveCheckbox.kt | 6 + .../ien/hig/adaptive/AdaptiveComponent.kt | 22 ++++ .../ien/hig/adaptive/AdaptiveDatePicker.kt | 8 ++ .../zone/ien/hig/adaptive/AdaptiveDivider.kt | 9 ++ .../ien/hig/adaptive/AdaptiveIconButton.kt | 7 ++ .../ien/hig/adaptive/AdaptiveNavigationBar.kt | 105 +++++++----------- .../hig/adaptive/AdaptiveProgressIndicator.kt | 6 + .../zone/ien/hig/adaptive/AdaptiveScaffold.kt | 5 +- .../zone/ien/hig/adaptive/AdaptiveSlider.kt | 48 ++++---- .../zone/ien/hig/adaptive/AdaptiveSurface.kt | 13 +++ .../zone/ien/hig/adaptive/AdaptiveTheme.kt | 18 ++- .../kotlin/zone/ien/hig/adaptive/Shapes.kt | 33 ++++++ .../zone/ien/hig/adaptive/DefaultTheme.ios.kt | 4 + .../ien/hig/adaptive/icons/SystemImage.kt | 9 ++ 17 files changed, 222 insertions(+), 101 deletions(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/Adaptation.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/Adaptation.kt index 6264f618..f0313100 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/Adaptation.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/Adaptation.kt @@ -53,6 +53,15 @@ sealed interface AdaptationScope { fun material(block: @Composable M.() -> Unit) } +/** + * Base class for creating custom adaptations between Material and Cupertino implementations. + * + * This class provides a framework for defining how Material and Cupertino widgets should be adapted + * by specifying how their properties can be customized. + * + * @param C The type of the Cupertino adaptation object. + * @param M The type of the Material adaptation object. + */ @Stable @ExperimentalAdaptiveApi abstract class Adaptation: AdaptationScope { diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveAlertDialog.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveAlertDialog.kt index 5632e41c..0c4243cf 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveAlertDialog.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveAlertDialog.kt @@ -177,6 +177,12 @@ class CupertinoAlertAdaptation internal constructor( var buttonsOrientation: Orientation by mutableStateOf(buttonsOrientation) } +/** + * Adaptation class for alert dialog that manages theme-specific values for alert dialogs. + * + * This class handles the adaptation between Cupertino and Material design for alert dialogs + * by providing appropriate values for container color, shape, and button orientation. + */ @ExperimentalAdaptiveApi @Stable private class AlertDialogAdaptation: Adaptation() { diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt index abbb369f..19c44428 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt @@ -232,10 +232,25 @@ class MaterialButtonAdaptation internal constructor( var border: BorderStroke? by mutableStateOf(border) } +/** + * Enum representing the different button types that can be adapted + * + * @property Filled - Represents a filled button style + * @property Text - Represents a text button style + * @property Tonal - Represents a tonal button style + */ private enum class ButtonType { Filled, Text, Tonal } +/** + * Adaptation class for button components that manages theme-specific values for buttons. + * + * This class handles the adaptation between Cupertino and Material design for buttons, + * providing appropriate styling for different button types (Filled, Text, Tonal). + * + * @param type The type of button to adapt (Filled, Text, or Tonal) + */ @ExperimentalAdaptiveApi private class ButtonAdaptation( private val type: ButtonType, diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt index 701c344d..b1f35282 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt @@ -122,6 +122,12 @@ class CupertinoCheckBoxAdaptation( var colors: CupertinoCheckboxColors by mutableStateOf(colors) } +/** + * Adaptation class for checkbox components that manages theme-specific values for checkboxes. + * + * This class handles the adaptation between Cupertino and Material design for checkboxes, + * providing appropriate color styling for both design systems. + */ @OptIn(ExperimentalAdaptiveApi::class) @Stable private class CheckBoxAdaptation: Adaptation(){ diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveComponent.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveComponent.kt index d1ae7c1f..b8adfb30 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveComponent.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveComponent.kt @@ -22,6 +22,16 @@ package zone.ien.hig.adaptive import androidx.compose.runtime.Composable +/** + * Creates an adaptive component that chooses between Material and Cupertino implementations based on the current theme. + * + * This function provides a way to define platform-specific implementations for a composable component. + * The implementation that is used depends on the current theme set by [AdaptiveTheme]. + * + * @param material The Material implementation of the component. This is used when the current theme is [Theme.Material3]. + * @param cupertino The Cupertino implementation of the component. This is used when the current theme is [Theme.Cupertino]. + * @return The result of the selected implementation based on the current theme. + */ @Composable @ExperimentalAdaptiveApi fun adaptiveComponent( @@ -34,6 +44,18 @@ fun adaptiveComponent( } } +/** + * Creates an adaptive component using the provided [adaptation] to customize both Material and Cupertino implementations. + * + * This function allows for more complex adaptations where the Material and Cupertino implementations need different + * customization parameters. It uses the provided [adaptation] to configure both implementations. + * + * @param adaptation The adaptation object that defines how to customize both Material and Cupertino implementations. + * @param material The Material implementation of the component. This is used when the current theme is [Theme.Material3]. + * @param cupertino The Cupertino implementation of the component. This is used when the current theme is [Theme.Cupertino]. + * @param adaptationScope The scope for customizing both Material and Cupertino implementations using [AdaptationScope]. + * @return The result of the selected implementation based on the current theme. + */ @Composable @ExperimentalAdaptiveApi fun adaptiveComponent( diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveDatePicker.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveDatePicker.kt index b2cbc682..4fc023a1 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveDatePicker.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveDatePicker.kt @@ -165,6 +165,14 @@ class MaterialDatePickerAdaptation internal constructor( private val DatePickerTitlePadding = PaddingValues(start = 24.dp, end = 12.dp, top = 16.dp) private val DatePickerHeadlinePadding = PaddingValues(start = 24.dp, end = 12.dp, bottom = 12.dp) +/** + * Adaptation class for date picker components that manages theme-specific values for date pickers. + * + * This class handles the adaptation between Cupertino and Material design for date pickers, + * providing appropriate styling and behavior for both design systems. + * + * @param state The DatePickerState used for managing the state of the date picker + */ @ExperimentalAdaptiveApi @OptIn(ExperimentalMaterial3Api::class) private class DatePickerAdaptation( diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveDivider.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveDivider.kt index 93fb54e1..d9390d30 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveDivider.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveDivider.kt @@ -132,6 +132,15 @@ private class DividerAdaptationScope: Adaptation Int, val onTabSelected: (Int) -> Unit, ) internal val LocalNavigationBarState = compositionLocalOf { null } +/** + * An adaptive navigation bar that adapts between Cupertino and Material design based on the platform. + * + * This composable provides a navigation bar that automatically switches between Cupertino (iOS) and Material (Android) + * design patterns based on the target platform. It supports tab navigation with appropriate styling for each platform. + * + * @param modifier optional [Modifier] for customizing the appearance and behavior + * @param selectedTabIndex lambda that returns the currently selected tab index + * @param onTabSelected callback to be invoked when a tab is selected + * @param tabsCount the total number of tabs + * @param adaptation lambda for customizing the adaptation behavior + * @param content composable content of the navigation bar items + */ @OptIn(ExperimentalCupertinoApi::class) @ExperimentalAdaptiveApi @Composable @@ -184,18 +189,6 @@ fun AdaptiveNavigationBarNative( } } -/** - * An adaptive navigation bar that adapts between Cupertino and Material design based on the platform. - * - * This composable provides a navigation bar that automatically switches between Cupertino (iOS) and Material (Android) - * design patterns based on the target platform. It supports native navigation bar items with appropriate styling for each platform. - * - * @param modifier optional [Modifier] for customizing the appearance and behavior - * @param selectedTabIndex lambda that returns the currently selected tab index - * @param onTabSelected callback to be invoked when a tab is selected - * @param adaptation lambda for customizing the adaptation behavior - * @param items list of navigation bar items for the Material implementation - */ /** * An adaptive navigation bar item that adapts between Cupertino and Material design based on the platform. * @@ -230,43 +223,6 @@ fun RowScope.AdaptiveNavigationBarItem( onClick() } - AdaptiveWidget( - adaptation = remember { - NavigationBarItemAdaptation() - }, - adaptationScope = adaptation, - cupertino = { - CupertinoNavigationBarItem( - onClick = resolvedOnClick, - icon = icon, - modifier = modifier, - enabled = enabled, - label = label, - interactionSource = interactionSource, - ) - }, - material = { - NavigationBarItem( - selected = selected, - onClick = resolvedOnClick, - icon = icon, - modifier = modifier, - enabled = enabled, - label = label, - alwaysShowLabel = it.alwaysShowLabel, - colors = it.colors, - interactionSource = interactionSource - ) - } - ) -} - } - ) - } - ) - } -} - AdaptiveWidget( adaptation = remember { NavigationBarItemAdaptation() @@ -298,6 +254,16 @@ fun RowScope.AdaptiveNavigationBarItem( ) } +/** + * Material navigation bar adaptation. + * + * Container class for Material navigation bar adaptation properties. + * + * @param containerColor color for the container + * @param contentColor color for the content + * @param tonalElevation elevation for the navigation bar + * @param windowInsets window insets to be used for the navigation bar + */ class MaterialNavigationBarAdaptation internal constructor( containerColor: Color, contentColor: Color, @@ -310,6 +276,15 @@ class MaterialNavigationBarAdaptation internal constructor( var windowInsets: WindowInsets by mutableStateOf(windowInsets) } +/** + * Cupertino navigation bar adaptation. + * + * Container class for Cupertino navigation bar adaptation properties. + * + * @param colors colors to be used for the navigation bar + * @param windowInsets window insets to be used for the navigation bar + * @param backdrop backdrop to use for the navigation bar + */ @OptIn(ExperimentalCupertinoApi::class) class CupertinoNavigationBarAdaptation internal constructor( colors: CupertinoNavigationBarColors, diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveProgressIndicator.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveProgressIndicator.kt index d6f5783b..609fe2f4 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveProgressIndicator.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveProgressIndicator.kt @@ -119,6 +119,12 @@ class CupertinoCircularProgressIndicatorAdaptation internal constructor( var minAlpha: Float by mutableStateOf(minAlpha) } +/** + * Adaptation class for progress indicator components that manages theme-specific values for progress indicators. + * + * This class handles the adaptation between Cupertino and Material design for progress indicators, + * providing appropriate styling for both design systems. + */ @OptIn(ExperimentalAdaptiveApi::class) private class ProgressIndicatorAdaptation : Adaptation() { diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveScaffold.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveScaffold.kt index ed32ea20..aa19d95b 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveScaffold.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveScaffold.kt @@ -109,8 +109,6 @@ fun AdaptiveScaffold( } ) } - ) -} /** * An adaptive scaffold that adapts between Cupertino and Material design based on the platform. @@ -200,6 +198,9 @@ class ScaffoldAdaptation internal constructor( /** * Implementation of [Adaptation] for [ScaffoldAdaptation]. + * + * This class manages the adaptation between Cupertino and Material design for scaffolds, + * providing appropriate container and content colors for both design systems. */ @OptIn(ExperimentalAdaptiveApi::class) @Stable diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSlider.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSlider.kt index ad001e03..283e8f69 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSlider.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSlider.kt @@ -16,6 +16,29 @@ * limitations under the License. */ +package zone.ien.hig.adaptive + +import androidx.compose.foundation.interaction.Interaction +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.material3.RangeSlider +import androidx.compose.material3.Slider +import androidx.compose.material3.SliderColors +import androidx.compose.material3.SliderDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import com.kyant.backdrop.Backdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop +import zone.ien.hig.utils.rememberDefaultBackdrop +import zone.ien.hig.CupertinoSlider +import zone.ien.hig.CupertinoLiquidSliderDefaults +import zone.ien.hig.CupertinoRangeSlider +import zone.ien.hig.CupertinoSliderColors + /** * An adaptive slider that adapts between Cupertino and Material design based on the platform. * @@ -43,29 +66,6 @@ * [Interaction]s and customize the appearance / behavior of this slider in different states. * @param adaptation adaptation scope */ -package zone.ien.hig.adaptive - -import androidx.compose.foundation.interaction.Interaction -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.material3.RangeSlider -import androidx.compose.material3.Slider -import androidx.compose.material3.SliderColors -import androidx.compose.material3.SliderDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import com.kyant.backdrop.Backdrop -import com.kyant.backdrop.backdrops.rememberLayerBackdrop -import zone.ien.hig.utils.rememberDefaultBackdrop -import zone.ien.hig.CupertinoSlider -import zone.ien.hig.CupertinoLiquidSliderDefaults -import zone.ien.hig.CupertinoRangeSlider -import zone.ien.hig.CupertinoSliderColors - @ExperimentalAdaptiveApi @Composable fun AdaptiveSlider( @@ -228,6 +228,8 @@ class MaterialSliderAdaptation internal constructor( * Slider adaptation implementation. * * Implementation of [Adaptation] for slider adaptation. + * + * @param steps The number of discrete steps for the slider, or 0 for continuous */ @OptIn(ExperimentalAdaptiveApi::class) @Stable diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSurface.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSurface.kt index d515a741..5e2cb537 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSurface.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSurface.kt @@ -32,6 +32,19 @@ import zone.ien.hig.LocalContentColor import zone.ien.hig.CupertinoSurface import zone.ien.hig.theme.CupertinoTheme +/** + * A composable that renders a surface that adapts its appearance based on the current theme. + * + * This composable provides a consistent surface implementation across both Material and Cupertino design systems. + * It automatically chooses the appropriate surface implementation based on the current theme set by [AdaptiveTheme]. + * + * @param modifier Modifier to be applied to the surface. + * @param shape The shape of the surface. Defaults to [RectangleShape]. + * @param color The background color of the surface. Defaults to [Color.Unspecified]. + * @param contentColor The color of the content within the surface. Defaults to [Color.Unspecified]. + * @param shadowElevation The elevation of the surface's shadow. Defaults to 0.dp. + * @param content The content to be displayed within the surface. + */ @ExperimentalAdaptiveApi @Composable fun AdaptiveSurface( diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt index 6740635a..3c92b0d0 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt @@ -16,15 +16,6 @@ * limitations under the License. */ -/** - * Adaptive theme that allows seamless use of both Material and Cupertino widgets. - * - * This theme provides a way to seamlessly use Material and Cupertino widgets together in the same application - * by adapting based on the target theme. It also makes [Text] and [CupertinoText], as well as [Icon] and [CupertinoIcon] - * behave identically in both design systems. - * - * The current theme target can be accessed inside the [content] using [currentTheme] property. - */ package zone.ien.hig.adaptive import androidx.compose.material3.Icon @@ -55,6 +46,9 @@ import androidx.compose.material3.LocalTextStyle as MaterialLocalTextStyle /** * The supported themes for adaptive widgets. + * + * This enum defines the two supported themes that can be used in [AdaptiveTheme]. + * The choice of theme affects how adaptive widgets behave and which design system they follow. */ enum class Theme { Cupertino, Material3 @@ -63,9 +57,11 @@ enum class Theme { /** * Adaptive theme depending on [target]. It allows to seamlessly use Material and Cupertino widgets. * - * This theme also make [Text] <-> [CupertinoText] and [Icon] <-> [CupertinoIcon] behave identically + * This theme provides a way to seamlessly use Material and Cupertino widgets together in the same application + * by adapting based on the target theme. It also makes [Text] and [CupertinoText], as well as [Icon] and [CupertinoIcon] + * behave identically in both design systems. * - * Current theme target can be accessed inside the [content] using [currentTheme] property + * The current theme target can be accessed inside the [content] using [currentTheme] property. * * @param target theme for adaptive widgets. Defaults to [Theme.Cupertino] for iOS and [Theme.Material3] for other platforms * @param material [MaterialTheme] specification. NOTE: You must use lambda parameter as a content diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/Shapes.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/Shapes.kt index 24ab0119..22263cc7 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/Shapes.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/Shapes.kt @@ -28,6 +28,24 @@ import com.kyant.shapes.RoundedRectangle import com.kyant.shapes.UnevenRoundedRectangle import zone.ien.hig.theme.Shapes as CupertinoShapes import androidx.compose.material3.Shapes as MaterialShapes + +/** + * A class that holds the shapes for different design systems. + * + * This class provides a consistent set of shapes that can be used across both Material and Cupertino design systems. + * It includes shapes for different sizes and also provides HIG-specific shapes. + * + * @param extraSmall The extra small shape, typically used for small components. + * @param small The small shape, typically used for small components. + * @param medium The medium shape, typically used for medium components. + * @param large The large shape, typically used for large components. + * @param extraLarge The extra large shape, typically used for large components. + * @param higExtraSmall The extra small HIG shape, typically used for small components. + * @param higSmall The small HIG shape, typically used for small components. + * @param higMedium The medium HIG shape, typically used for medium components. + * @param higLarge The large HIG shape, typically used for large components. + * @param higExtraLarge The extra large HIG shape, typically used for large components. + */ @Immutable class Shapes( val extraSmall: CornerBasedShape = RoundedCornerShape(4.dp), @@ -41,6 +59,21 @@ class Shapes( val higLarge: RoundedRectangle = RoundedRectangle(16.dp), val higExtraLarge: RoundedRectangle = RoundedRectangle(24.dp) ) { + /** + * Creates a copy of this Shapes object with the specified values replaced. + * + * @param extraSmall The extra small shape, typically used for small components. + * @param small The small shape, typically used for small components. + * @param medium The medium shape, typically used for medium components. + * @param large The large shape, typically used for large components. + * @param extraLarge The extra large shape, typically used for large components. + * @param higExtraSmall The extra small HIG shape, typically used for small components. + * @param higSmall The small HIG shape, typically used for small components. + * @param higMedium The medium HIG shape, typically used for medium components. + * @param higLarge The large HIG shape, typically used for large components. + * @param higExtraLarge The extra large HIG shape, typically used for large components. + * @return A new Shapes object with the specified values replaced. + */ fun copy( extraSmall: CornerBasedShape = this.extraSmall, small: CornerBasedShape = this.small, diff --git a/hig-adaptive/src/iosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.ios.kt b/hig-adaptive/src/iosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.ios.kt index e3acaaf9..ac6618b9 100644 --- a/hig-adaptive/src/iosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.ios.kt +++ b/hig-adaptive/src/iosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.ios.kt @@ -21,6 +21,10 @@ * * This file defines the default theme for iOS platforms to use Cupertino design. * It ensures consistent theming across iOS applications using this library. + * + * On iOS platforms, the default theme is set to [Theme.Cupertino] to provide a native Cupertino experience. + * + * @return The default theme for iOS platforms, which is [Theme.Cupertino]. */ package zone.ien.hig.adaptive diff --git a/hig-adaptive/src/iosMain/kotlin/zone/ien/hig/adaptive/icons/SystemImage.kt b/hig-adaptive/src/iosMain/kotlin/zone/ien/hig/adaptive/icons/SystemImage.kt index 2ad3b1ff..0c9359be 100644 --- a/hig-adaptive/src/iosMain/kotlin/zone/ien/hig/adaptive/icons/SystemImage.kt +++ b/hig-adaptive/src/iosMain/kotlin/zone/ien/hig/adaptive/icons/SystemImage.kt @@ -25,5 +25,14 @@ import androidx.compose.ui.graphics.painter.Painter import zone.ien.hig.icons.CupertinoIcons import zone.ien.hig.named +/** + * Platform-specific implementation for retrieving system icons on iOS platforms. + * + * This function provides a way to retrieve system icons for iOS platforms. + * It delegates to the Cupertino icon implementation using the [CupertinoIcons.named] function. + * + * @param name The name of the system icon to retrieve. + * @return A [Painter] for the specified system icon, or null if the icon is not found. + */ @Composable internal actual fun systemImage(name: String): Painter? = CupertinoIcons.named(name) From 29aea43419b77e17a408b805583dd10c966ed852 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 13:48:26 +0900 Subject: [PATCH 064/109] Update hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt index 3c92b0d0..8b906e53 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt @@ -63,10 +63,10 @@ enum class Theme { * * The current theme target can be accessed inside the [content] using [currentTheme] property. * - * @param target theme for adaptive widgets. Defaults to [Theme.Cupertino] for iOS and [Theme.Material3] for other platforms - * @param material [MaterialTheme] specification. NOTE: You must use lambda parameter as a content - * @param cupertino [CupertinoTheme] specification. NOTE: You must use lambda parameter as a content - * @param content themed content + * @param target theme for adaptive widgets. Defaults to [Theme.Cupertino] for iOS and [Theme.Material3] for other platforms. + * @param material [MaterialThemeSpec] specification. + * @param cupertino [CupertinoThemeSpec] specification. + * @param content themed content. */ @ExperimentalAdaptiveApi @Composable From 618284643e10ba9458cadb94283ea4ece2e9f3b5 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 13:49:21 +0900 Subject: [PATCH 065/109] =?UTF-8?q?=F0=9F=8E=A8=20style(adaptive):=20KDoc?= =?UTF-8?q?=20=EB=B0=8F=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=84=A0=EC=96=B8?= =?UTF-8?q?=20=EC=9C=84=EC=B9=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - KDoc 주석을 관련 컴포저블 및 객체 선언 바로 위로 이동하여 가독성 개선 - 패키지 선언을 라이선스 헤더 직후로 이동하여 Kotlin 코딩 컨벤션 준수 --- .../zone/ien/hig/adaptive/AdaptiveSwitch.kt | 36 +++++++++---------- .../ien/hig/adaptive/AdaptiveTopAppBar.kt | 26 +++++++------- .../ien/hig/adaptive/icons/AdaptiveIcons.kt | 12 +++---- .../zone/ien/hig/adaptive/DefaultTheme.ios.kt | 4 +-- .../ien/hig/adaptive/DefaultTheme.nonIos.kt | 4 +-- 5 files changed, 41 insertions(+), 41 deletions(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt index 2ed6b61a..51624744 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt @@ -16,24 +16,6 @@ * limitations under the License. */ -/** - * An adaptive switch that adapts between Cupertino and Material design based on the platform. - * - * Switches toggle the state of a single item on or off. - * This composable automatically switches between Cupertino (iOS) and Material (Android) design patterns - * based on the current theme. - * - * @param checked whether or not this switch is checked - * @param onCheckedChange called when this switch is clicked. If `null`, then this switch will not - * be interactable, unless something else handles its input events and updates its state. - * @param modifier the [Modifier] to be applied to this switch - * @param thumbContent content that will be drawn inside the thumb - * @param enabled controls the enabled state of this switch. When `false`, this component will not - * respond to user input, and it will appear visually disabled and disabled to accessibility - * services. - * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s - * @param adaptation configuration block for theme-dependent properties for this switch - */ package zone.ien.hig.adaptive import androidx.compose.foundation.interaction.Interaction @@ -55,6 +37,24 @@ import zone.ien.hig.CupertinoSwitch import zone.ien.hig.CupertinoSwitchColors import zone.ien.hig.CupertinoSwitchDefaults +/** + * An adaptive switch that adapts between Cupertino and Material design based on the platform. + * + * Switches toggle the state of a single item on or off. + * This composable automatically switches between Cupertino (iOS) and Material (Android) design patterns + * based on the current theme. + * + * @param checked whether or not this switch is checked + * @param onCheckedChange called when this switch is clicked. If `null`, then this switch will not + * be interactable, unless something else handles its input events and updates its state. + * @param modifier the [Modifier] to be applied to this switch + * @param thumbContent content that will be drawn inside the thumb + * @param enabled controls the enabled state of this switch. When `false`, this component will not + * respond to user input, and it will appear visually disabled and disabled to accessibility + * services. + * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s + * @param adaptation configuration block for theme-dependent properties for this switch + */ @ExperimentalAdaptiveApi @Composable fun AdaptiveSwitch( diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTopAppBar.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTopAppBar.kt index adf8d21b..a74ad046 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTopAppBar.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTopAppBar.kt @@ -16,19 +16,6 @@ * limitations under the License. */ -/** - * An adaptive top app bar that adapts between Cupertino and Material design based on the platform. - * - * This composable provides a top app bar that automatically switches between Cupertino (iOS) and Material (Android) - * design patterns based on the target platform. The content adapts to appropriate design guidelines and styles. - * - * @param title composable for the title - * @param modifier optional [Modifier] for customizing the appearance and behavior - * @param navigationIcon composable for the navigation icon - * @param actions composable for the actions - * @param windowInsets the window insets to be used for the content - * @param adaptation lambda for customizing the adaptation behavior - */ package zone.ien.hig.adaptive import androidx.compose.foundation.layout.RowScope @@ -54,6 +41,19 @@ import zone.ien.hig.CupertinoTopAppBarColors import zone.ien.hig.CupertinoTopAppBarDefaults import zone.ien.hig.ExperimentalCupertinoApi +/** + * An adaptive top app bar that adapts between Cupertino and Material design based on the platform. + * + * This composable provides a top app bar that automatically switches between Cupertino (iOS) and Material (Android) + * design patterns based on the target platform. The content adapts to appropriate design guidelines and styles. + * + * @param title composable for the title + * @param modifier optional [Modifier] for customizing the appearance and behavior + * @param navigationIcon composable for the navigation icon + * @param actions composable for the actions + * @param windowInsets the window insets to be used for the content + * @param adaptation lambda for customizing the adaptation behavior + */ @OptIn(ExperimentalMaterial3Api::class, ExperimentalCupertinoApi::class) @ExperimentalAdaptiveApi @Composable diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/icons/AdaptiveIcons.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/icons/AdaptiveIcons.kt index 25f7437e..c921355f 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/icons/AdaptiveIcons.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/icons/AdaptiveIcons.kt @@ -16,12 +16,6 @@ * limitations under the License. */ -/** - * Provides adaptive icons for both Cupertino and Material design. - * - * This object contains a collection of adaptive icons that switch between Material and Cupertino design based on - * the current theme. Icons are defined for both filled and outlined variants. - */ package zone.ien.hig.adaptive.icons import androidx.compose.material.icons.Icons @@ -80,6 +74,12 @@ import zone.ien.hig.icons.outlined.WrenchAndScrewdriver import zone.ien.hig.icons.outlined.Xmark import zone.ien.hig.icons.outlined.XmarkCircle +/** + * Provides adaptive icons for both Cupertino and Material design. + * + * This object contains a collection of adaptive icons that switch between Material and Cupertino design based on + * the current theme. Icons are defined for both filled and outlined variants. + */ object AdaptiveIcons { object Filled diff --git a/hig-adaptive/src/iosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.ios.kt b/hig-adaptive/src/iosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.ios.kt index ac6618b9..1bf79302 100644 --- a/hig-adaptive/src/iosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.ios.kt +++ b/hig-adaptive/src/iosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.ios.kt @@ -16,6 +16,8 @@ * limitations under the License. */ +package zone.ien.hig.adaptive + /** * Platform-specific default theme for iOS platforms. * @@ -26,7 +28,5 @@ * * @return The default theme for iOS platforms, which is [Theme.Cupertino]. */ -package zone.ien.hig.adaptive - internal actual val DefaultTheme: Theme get() = Theme.Cupertino \ No newline at end of file diff --git a/hig-adaptive/src/nonIosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.nonIos.kt b/hig-adaptive/src/nonIosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.nonIos.kt index a1b47552..c2ec058b 100644 --- a/hig-adaptive/src/nonIosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.nonIos.kt +++ b/hig-adaptive/src/nonIosMain/kotlin/zone/ien/hig/adaptive/DefaultTheme.nonIos.kt @@ -16,13 +16,13 @@ * limitations under the License. */ +package zone.ien.hig.adaptive + /** * Platform-specific default theme for non-iOS platforms. * * This file defines the default theme for non-iOS platforms (such as Android) to use Material 3 design. * It ensures consistent theming across Android applications using this library. */ -package zone.ien.hig.adaptive - internal actual val DefaultTheme: Theme get() = Theme.Material3 \ No newline at end of file From e15121f4f7977ae4ea57be91ce892c63c38a4cb8 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 13:54:39 +0900 Subject: [PATCH 066/109] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(core):=20?= =?UTF-8?q?AdaptiveWidget.kt=20=EB=B0=8F=20Util.adaptive.kt=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AdaptiveWidget 함수의 문서화 및 import 순서 정리 - Color?.takeOrElse 함수에 대한 문서화 추가 및 설명 개선 - 코드 구조 개선을 위한 패키지 선언 및 주석 정리 --- .../kotlin/zone/ien/hig/adaptive/AdaptiveWidget.kt | 10 ++++++---- .../kotlin/zone/ien/hig/adaptive/Util.adaptive.kt | 9 +++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveWidget.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveWidget.kt index 60172894..c2a9abb0 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveWidget.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveWidget.kt @@ -16,6 +16,10 @@ * limitations under the License. */ +package zone.ien.hig.adaptive + +import androidx.compose.runtime.Composable + /** * A composable that adapts between Cupertino and Material design based on the current theme. * @@ -24,11 +28,8 @@ * * @param material composable for the material design implementation * @param cupertino composable for the cupertino design implementation + * @param content The content that will be rendered based on the current theme */ -package zone.ien.hig.adaptive - -import androidx.compose.runtime.Composable - @Composable @ExperimentalAdaptiveApi fun AdaptiveWidget( @@ -51,6 +52,7 @@ fun AdaptiveWidget( * @param material composable for the material design implementation with adaptation * @param cupertino composable for the cupertino design implementation with adaptation * @param adaptationScope lambda that allows customization of the adaptation behavior + * @param content The content that will be rendered based on the current theme and adaptation */ @Composable @ExperimentalAdaptiveApi diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/Util.adaptive.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/Util.adaptive.kt index 25bf2ea3..2bedc886 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/Util.adaptive.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/Util.adaptive.kt @@ -23,4 +23,13 @@ package zone.ien.hig.adaptive import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.isSpecified +/** + * Returns this Color if it is specified, otherwise returns the result of calling [block]. + * + * This utility function provides a clean way to handle optional Colors that may be null or unspecified. + * It's commonly used in adaptive components where default colors need to be provided. + * + * @param block The block that provides a default Color value if this Color is null or unspecified. + * @return This Color if it is specified, otherwise the result of [block]. + */ inline fun Color?.takeOrElse(block: () -> Color): Color = if (this != null && isSpecified) this else block() From 5ad5153f621f0fc5832bc4bb9698c8fd4bdf0018 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 14:01:20 +0900 Subject: [PATCH 067/109] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(core):=20?= =?UTF-8?q?Adaptive=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=ED=99=94=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AdaptiveCheckbox 문서화 수정 및 파라미터 설명 추가 - AdaptiveSwitch 컴포넌트 설명 간소화 및 파라미터 정리 - AdaptiveTheme 테마 메시지 영문으로 변경 - AdaptiveButton 컴포넌트 설명 추가 및 파라미터 정리 --- .../zone/ien/hig/adaptive/AdaptiveButton.kt | 21 ++++++++++--------- .../zone/ien/hig/adaptive/AdaptiveCheckbox.kt | 2 +- .../zone/ien/hig/adaptive/AdaptiveSwitch.kt | 18 +++++++--------- .../zone/ien/hig/adaptive/AdaptiveTheme.kt | 2 +- 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt index 7735b882..db1a1114 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt @@ -50,19 +50,20 @@ import zone.ien.hig.ExperimentalCupertinoApi import zone.ien.hig.theme.CupertinoTheme /** - * 적응형 버튼으로, Material [Button] 또는 테두리가 있는 [CupertinoLiquidButton] 모양을 사용합니다. + * An adaptive button that uses either Material [Button] or [CupertinoLiquidButton] + * depending on the current theme. * - * Material Design에서는 [Button] 컴포넌트를 사용하고, Cupertino 테마에서는 [CupertinoLiquidButton] 컴포넌트를 사용하여 - * 운영체제별로 적절한 UI를 제공합니다. + * Material Design uses the [Button] component, while the Cupertino theme uses + * the [CupertinoLiquidButton] component to provide a native look and feel on each platform. * - * @param onClick 버튼이 클릭되었을 때 호출되는 함수 - * @param modifier 버튼에 적용할 Modifier - * @param enabled 버튼이 활성화되어 있는지 여부 - * @param interactionSource 상호작용 소스 - * @param adaptation [CupertinoButtonAdaptation]와 [MaterialButtonAdaptation]에 대한 사용자 정의 설정 함수 - * @param content 버튼 내부에 표시될 내용 + * @param onClick called when the button is clicked + * @param modifier the [Modifier] to be applied to this button + * @param enabled controls the enabled state of this button + * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s + * @param adaptation configuration block for [CupertinoButtonAdaptation] and [MaterialButtonAdaptation] + * @param content the content to be displayed inside the button * @see AdaptiveWidget - * @see CupertinoButton + * @see CupertinoLiquidButton * @see Button */ @OptIn(ExperimentalCupertinoApi::class) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt index dd626f61..806491d3 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt @@ -202,4 +202,4 @@ private class CheckBoxAdaptation: Adaptation Date: Tue, 12 May 2026 18:33:03 +0900 Subject: [PATCH 068/109] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(core):=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=E9=87=8D=E6=9E=84=20=EB=B0=8F=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 19개 파일에서 대규모 리팩토링 및 기능 추가 - CalendarModel, CupertinoButton, DropdownMenu 등 주요 컴포넌트 업데이트 - Android 및 iOS 플랫폼별 구현 코드 통합 및 개선 --- .../zone/ien/hig/CalendarModelImpl.android.kt | 142 +++++++- .../zone/ien/hig/CupertinoDialogs.android.kt | 16 +- .../kotlin/zone/ien/hig/HazePlatform.kt | 24 +- .../kotlin/zone/ien/hig/Luminance.android.kt | 34 ++ .../kotlin/zone/ien/hig/Modifier.android.kt | 7 + .../ien/hig/PlatformDateFormat.android.kt | 81 ++++- .../zone/ien/hig/utils/Coroutines.android.kt | 24 ++ .../kotlin/zone/ien/hig/CalendarModel.kt | 39 +- .../kotlin/zone/ien/hig/CalendarModelImpl.kt | 8 + .../ien/hig/CupertinoActivityIndicator.kt | 39 +- .../zone/ien/hig/CupertinoBottomAppBar.kt | 21 +- .../zone/ien/hig/CupertinoBottomSheet.kt | 14 +- .../ien/hig/CupertinoBottomSheetScaffold.kt | 29 +- .../kotlin/zone/ien/hig/CupertinoButton.kt | 334 +++++++++++++----- .../kotlin/zone/ien/hig/CupertinoCheckbox.kt | 12 + .../zone/ien/hig/CupertinoDropdownMenu.kt | 110 +++++- .../zone/ien/hig/CupertinoLiquidButton.kt | 26 ++ .../zone/ien/hig/CupertinoTextField.ios.kt | 25 ++ .../kotlin/zone/ien/hig/PlatformDateFormat.kt | 1 - 19 files changed, 847 insertions(+), 139 deletions(-) diff --git a/hig/src/androidMain/kotlin/zone/ien/hig/CalendarModelImpl.android.kt b/hig/src/androidMain/kotlin/zone/ien/hig/CalendarModelImpl.android.kt index 52853ddb..6a995b6f 100644 --- a/hig/src/androidMain/kotlin/zone/ien/hig/CalendarModelImpl.android.kt +++ b/hig/src/androidMain/kotlin/zone/ien/hig/CalendarModelImpl.android.kt @@ -16,8 +16,6 @@ * limitations under the License. */ - - package zone.ien.hig import android.os.Build @@ -40,22 +38,44 @@ import java.time.format.FormatStyle import java.time.format.TextStyle import java.time.temporal.WeekFields import java.util.Locale +import androidx.compose.ui.platform.LocalLocale +/** + * Gets the default locale for the device. + * + * @return The default [CalendarLocale] for the device + */ @Composable @ReadOnlyComposable internal actual fun defaultLocale(): CalendarLocale = LocalConfiguration.current.run { val locales = locales - if (locales.isEmpty) Locale.getDefault() else locales[0] + if (locales.isEmpty) LocalLocale.current.platformLocale else locales[0] } +/** + * Gets the current locale for the device. + * + * @return The current [CalendarLocale] for the device + */ internal actual fun currentLocale(): CalendarLocale = Locale.getDefault() /** * A [CalendarModel] implementation for API >= 26. + * + * This implementation uses Java 8 Time APIs available on Android API level 26 and above. + * + * @property today The current date + * @property firstDayOfWeek The first day of the week for the default locale + * @property weekdayNames The weekday names for the default locale */ @RequiresApi(Build.VERSION_CODES.O) internal class AndroidCalendarModelImpl: CalendarModel { + /** + * Gets the current date. + * + * @return The [CalendarDate] representing today + */ override val today get(): CalendarDate { val systemLocalDate = LocalDate.now() @@ -72,10 +92,26 @@ internal class AndroidCalendarModelImpl: CalendarModel { ) } + /** + * Gets the first day of the week for the default locale. + * + * @return The first day of the week (1 = Sunday, 2 = Monday, etc.) + */ override val firstDayOfWeek: Int = WeekFields.of(Locale.getDefault()).firstDayOfWeek.value + /** + * Gets the weekday names for the default locale. + * + * @return A list of pairs containing full and short weekday names + */ override val weekdayNames: List> = weekdayNames(Locale.getDefault()) + /** + * Gets the weekday names for a specific locale. + * + * @param locale The [Locale] to get weekday names for + * @return A list of pairs containing full and short weekday names + */ fun weekdayNames(locale: Locale): List> = // This will start with Monday as the first day, according to ISO-8601. with(locale) { @@ -93,6 +129,12 @@ internal class AndroidCalendarModelImpl: CalendarModel { } } + /** + * Gets the date input format for the given locale. + * + * @param locale The [Locale] to get the input format for + * @return The [DateInputFormat] for the specified locale + */ override fun getDateInputFormat(locale: Locale): DateInputFormat = datePatternAsInputFormat( DateTimeFormatterBuilder.getLocalizedDateTimePattern( @@ -107,6 +149,12 @@ internal class AndroidCalendarModelImpl: CalendarModel { ), ) + /** + * Gets the canonical date from a timestamp. + * + * @param timeInMillis The timestamp in milliseconds + * @return The [CalendarDate] for the given timestamp + */ override fun getCanonicalDate(timeInMillis: Long): CalendarDate { val localDate = Instant.ofEpochMilli(timeInMillis).atZone(utcTimeZoneId).toLocalDate() @@ -118,6 +166,12 @@ internal class AndroidCalendarModelImpl: CalendarModel { ) } + /** + * Gets the month from a timestamp. + * + * @param timeInMillis The timestamp in milliseconds + * @return The [CalendarMonth] for the given timestamp + */ override fun getMonth(timeInMillis: Long): CalendarMonth = getMonth( Instant @@ -127,21 +181,55 @@ internal class AndroidCalendarModelImpl: CalendarModel { .toLocalDate(), ) + /** + * Gets the month from a [CalendarDate]. + * + * @param date The [CalendarDate] to get the month for + * @return The [CalendarMonth] for the given date + */ override fun getMonth(date: CalendarDate): CalendarMonth = getMonth(LocalDate.of(date.year, date.month, 1)) + /** + * Gets the month from year and month values. + * + * @param year The year + * @param month The month (1-12) + * @return The [CalendarMonth] for the given year and month + */ override fun getMonth( year: Int, month: Int, ): CalendarMonth = getMonth(LocalDate.of(year, month, 1)) + /** + * Gets a date with the specified year, month, and day. + * + * @param year The year + * @param month The month (1-12) + * @param day The day of month (1-31) + * @return The [CalendarDate] for the given year, month, and day + */ override fun getDate( year: Int, month: Int, day: Int, ): CalendarDate = CalendarDate(year, month, day, 0) + /** + * Gets the day of week for the given date. + * + * @param date The [CalendarDate] to get the day of week for + * @return The day of week (1 = Sunday, 2 = Monday, etc.) + */ override fun getDayOfWeek(date: CalendarDate): Int = date.toLocalDate().dayOfWeek.value + /** + * Adds months to a month. + * + * @param from The [CalendarMonth] to add months to + * @param addedMonthsCount The number of months to add + * @return The [CalendarMonth] with added months + */ override fun plusMonths( from: CalendarMonth, addedMonthsCount: Int, @@ -153,6 +241,13 @@ internal class AndroidCalendarModelImpl: CalendarModel { return getMonth(laterMonth) } + /** + * Subtracts months from a month. + * + * @param from The [CalendarMonth] to subtract months from + * @param subtractedMonthsCount The number of months to subtract + * @return The [CalendarMonth] with subtracted months + */ override fun minusMonths( from: CalendarMonth, subtractedMonthsCount: Int, @@ -164,12 +259,27 @@ internal class AndroidCalendarModelImpl: CalendarModel { return getMonth(earlierMonth) } + /** + * Formats a date using a custom pattern. + * + * @param utcTimeMillis The UTC timestamp to format (milliseconds from epoch) + * @param pattern The date format pattern to use + * @param locale The [Locale] to use when formatting the date + * @return The formatted date string + */ override fun formatWithPattern( utcTimeMillis: Long, pattern: String, locale: Locale, ): String = Companion.formatWithPattern(utcTimeMillis, pattern, locale) + /** + * Parses a date string using the provided pattern. + * + * @param date The date string to parse + * @param pattern The date format pattern to use + * @return The parsed [CalendarDate] or null if parsing fails + */ override fun parse( date: String, pattern: String, @@ -194,6 +304,11 @@ internal class AndroidCalendarModelImpl: CalendarModel { } } + /** + * Returns a string representation of this calendar model. + * + * @return The string "CalendarModel" + */ override fun toString(): String = "CalendarModel" companion object { @@ -203,6 +318,7 @@ internal class AndroidCalendarModelImpl: CalendarModel { * @param utcTimeMillis a UTC timestamp to format (milliseconds from epoch) * @param pattern a date format pattern * @param locale the [Locale] to use when formatting the given timestamp + * @return The formatted date string */ fun formatWithPattern( utcTimeMillis: Long, @@ -222,10 +338,18 @@ internal class AndroidCalendarModelImpl: CalendarModel { /** * Holds a UTC [ZoneId]. + * + * @return The UTC [ZoneId] for time zone operations */ internal val utcTimeZoneId: ZoneId = ZoneId.of("UTC") } + /** + * Gets a calendar month from a LocalDate. + * + * @param firstDayLocalDate The first day of the month as a LocalDate + * @return The [CalendarMonth] for the given LocalDate + */ private fun getMonth(firstDayLocalDate: LocalDate): CalendarMonth { val difference = firstDayLocalDate.dayOfWeek.value - firstDayOfWeek val daysFromStartOfWeekToFirstOfMonth = @@ -249,14 +373,24 @@ internal class AndroidCalendarModelImpl: CalendarModel { ) } + /** + * Converts a CalendarMonth to a LocalDate. + * + * @return The [LocalDate] representation of the calendar month + */ private fun CalendarMonth.toLocalDate(): LocalDate = Instant.ofEpochMilli(startUtcTimeMillis).atZone( utcTimeZoneId ).toLocalDate() + /** + * Converts a CalendarDate to a LocalDate. + * + * @return The [LocalDate] representation of the calendar date + */ private fun CalendarDate.toLocalDate(): LocalDate = LocalDate.of( this.year, this.month, this.dayOfMonth, ) -} +} \ No newline at end of file diff --git a/hig/src/androidMain/kotlin/zone/ien/hig/CupertinoDialogs.android.kt b/hig/src/androidMain/kotlin/zone/ien/hig/CupertinoDialogs.android.kt index 62d6b2df..ba28b7b0 100644 --- a/hig/src/androidMain/kotlin/zone/ien/hig/CupertinoDialogs.android.kt +++ b/hig/src/androidMain/kotlin/zone/ien/hig/CupertinoDialogs.android.kt @@ -17,13 +17,20 @@ */ - package zone.ien.hig import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.ui.window.DialogProperties +/** + * Creates platform-specific dialog properties for fullscreen popups on Android. + * + * @param dismissOnBackPress Whether to dismiss the dialog when the back button is pressed + * @param dismissOnClickOutside Whether to dismiss the dialog when clicking outside + * @param usePlatformDefaultWidth Whether to use the platform default width + * @return The [DialogProperties] for the dialog + */ @Composable @ReadOnlyComposable internal actual fun FullscreenPopupProperties( @@ -38,4 +45,11 @@ internal actual fun FullscreenPopupProperties( usePlatformDefaultWidth = usePlatformDefaultWidth, ) +/** + * Gets the platform-specific insets for dialog properties. + * + * For Android, this returns true, indicating that platform insets should be used. + * + * @return true, indicating platform insets are used on Android + */ actual val DialogProperties.platformInsets: Boolean get() = true diff --git a/hig/src/androidMain/kotlin/zone/ien/hig/HazePlatform.kt b/hig/src/androidMain/kotlin/zone/ien/hig/HazePlatform.kt index 35acb479..4b008cfb 100644 --- a/hig/src/androidMain/kotlin/zone/ien/hig/HazePlatform.kt +++ b/hig/src/androidMain/kotlin/zone/ien/hig/HazePlatform.kt @@ -29,9 +29,13 @@ import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp /** - * With CMP + Android, we can't do much other than display a transparent scrim. - * See `:haze-jetpack-compose` for a working blur on Android, but we need Compose 1.6.0 APIs, - * which are not available in CMP (yet). + * Android implementation of the HazeNode for drawing effects. + * + * @param areas The list of rectangles to draw the effect on + * @param backgroundColor The background color for the effect + * @param tint The tint color for the effect + * @param blurRadius The blur radius for the effect + * @param density The density for drawing operations */ internal actual class HazeNode actual constructor( private var areas: List, @@ -41,6 +45,15 @@ internal actual class HazeNode actual constructor( private val density: Density, ): Modifier.Node(), DrawModifierNode { + + /** + * Updates the HazeNode with new parameters. + * + * @param areas The list of rectangles to draw the effect on + * @param backgroundColor The background color for the effect + * @param tint The tint color for the effect + * @param blurRadius The blur radius for the effect + */ actual fun update( areas: List, backgroundColor: Color, @@ -53,6 +66,11 @@ internal actual class HazeNode actual constructor( this.blurRadius = blurRadius } + /** + * Draws the haze effect on the content. + * + * @param drawContent The content to draw + */ override fun ContentDrawScope.draw() { drawContent() diff --git a/hig/src/androidMain/kotlin/zone/ien/hig/Luminance.android.kt b/hig/src/androidMain/kotlin/zone/ien/hig/Luminance.android.kt index 229295f5..aa31f16a 100644 --- a/hig/src/androidMain/kotlin/zone/ien/hig/Luminance.android.kt +++ b/hig/src/androidMain/kotlin/zone/ien/hig/Luminance.android.kt @@ -1,3 +1,21 @@ +/* + * Copyright (c) 2023-2024. Compose Cupertino project and open source contributors. + * Copyright (c) 2025. Scott Lanoue. + * Copyright (c) 2026. IENGROUND of IENLAB. + * + * 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 zone.ien.hig import android.graphics.Bitmap @@ -5,12 +23,28 @@ import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.asImageBitmap +/** + * Scales an image bitmap to the specified dimensions. + * + * @param width The target width + * @param height The target height + * @return The scaled [ImageBitmap] + */ actual fun ImageBitmap.scale(width: Int, height: Int): ImageBitmap { return Bitmap.createScaledBitmap(this.asAndroidBitmap(), 5, 5, false) .copy(Bitmap.Config.ARGB_8888, false) .asImageBitmap() } +/** + * Crops an image bitmap to the specified region. + * + * @param x The x-coordinate of the top-left corner of the crop region + * @param y The y-coordinate of the top-left corner of the crop region + * @param width The width of the crop region + * @param height The height of the crop region + * @return The cropped [ImageBitmap] + */ actual fun ImageBitmap.crop(x: Int, y: Int, width: Int, height: Int): ImageBitmap { val endX = (x + width).coerceAtMost(this.width) val endY = (y + height).coerceAtMost(this.height) diff --git a/hig/src/androidMain/kotlin/zone/ien/hig/Modifier.android.kt b/hig/src/androidMain/kotlin/zone/ien/hig/Modifier.android.kt index a27ee33e..df69fbe6 100644 --- a/hig/src/androidMain/kotlin/zone/ien/hig/Modifier.android.kt +++ b/hig/src/androidMain/kotlin/zone/ien/hig/Modifier.android.kt @@ -21,4 +21,11 @@ package zone.ien.hig import androidx.compose.ui.Modifier import androidx.compose.foundation.systemGestureExclusion as androidSystemGestureExclusion +/** + * Applies system gesture exclusion to the modifier. + * + * This modifier is used to exclude areas from system gestures on Android devices. + * + * @return The modifier with system gesture exclusion applied + */ internal actual fun Modifier.systemGestureExclusion() = androidSystemGestureExclusion() diff --git a/hig/src/androidMain/kotlin/zone/ien/hig/PlatformDateFormat.android.kt b/hig/src/androidMain/kotlin/zone/ien/hig/PlatformDateFormat.android.kt index f756e61f..2e4f7a46 100644 --- a/hig/src/androidMain/kotlin/zone/ien/hig/PlatformDateFormat.android.kt +++ b/hig/src/androidMain/kotlin/zone/ien/hig/PlatformDateFormat.android.kt @@ -16,8 +16,6 @@ * limitations under the License. */ - - package zone.ien.hig import android.os.Build @@ -27,17 +25,35 @@ import androidx.annotation.ChecksSdkIntAtLeast internal actual object PlatformDateFormat { + /** + * The delegate for date formatting operations based on API level. + * For API level 26 and above, uses AndroidCalendarModelImpl; otherwise, throws an error. + */ private val delegate by lazy { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { AndroidCalendarModelImpl() } else error("should not be used for api < 26") } + + /** + * Gets the first day of the week for the current locale. + * + * @return the first day of the week (1 = Sunday, 2 = Monday, etc.) + */ actual val firstDayOfWeek: Int get() = apiCheck( old = { LegacyDateFormat.firstDayOfWeek }, new = { delegate.firstDayOfWeek } ) + /** + * Formats a date using a custom pattern. + * + * @param utcTimeMillis The UTC timestamp to format (milliseconds from epoch) + * @param pattern The date format pattern to use + * @param locale The [CalendarLocale] to use when formatting the date + * @return The formatted date string + */ actual fun formatWithPattern( utcTimeMillis: Long, pattern: String, @@ -51,6 +67,14 @@ internal actual object PlatformDateFormat { } ) + /** + * Formats a date using a skeleton pattern. + * + * @param utcTimeMillis The UTC timestamp to format (milliseconds from epoch) + * @param skeleton The date format skeleton to use + * @param locale The [CalendarLocale] to use when formatting the date + * @return The formatted date string + */ actual fun formatWithSkeleton( utcTimeMillis: Long, skeleton: String, @@ -65,6 +89,13 @@ internal actual object PlatformDateFormat { } ) + /** + * Parses a date string using the provided pattern. + * + * @param date The date string to parse + * @param pattern The date format pattern to use + * @return The parsed [CalendarDate] or null if parsing fails + */ actual fun parse( date: String, pattern: String @@ -77,6 +108,12 @@ internal actual object PlatformDateFormat { } ) + /** + * Gets the date input format for the given locale. + * + * @param locale The [CalendarLocale] to get the input format for + * @return The [DateInputFormat] for the specified locale + */ actual fun getDateInputFormat(locale: CalendarLocale): DateInputFormat { return apiCheck( old = { LegacyDateFormat.getDateInputFormat(locale) }, @@ -84,27 +121,55 @@ internal actual object PlatformDateFormat { ) } - // From CalendarModelImpl.android.kt weekdayNames. - // - // Legacy model returns short ('Mon') format while newer version returns narrow ('M') format + /** + * Gets the weekday names for the given locale. + * + * From CalendarModelImpl.android.kt weekdayNames. + * + * Legacy model returns short ('Mon') format while newer version returns narrow ('M') format + * + * @param locale The [CalendarLocale] to get weekday names for + * @return A list of pairs containing full and short weekday names + */ actual fun weekdayNames(locale: CalendarLocale): List> { return apiCheck( old = { LegacyDateFormat.weekdayNames(locale) }, new = { delegate.weekdayNames(locale) } ) } + + /** + * Gets the month names for the given locale. + * + * @param locale The [CalendarLocale] to get month names for + * @return A list of month names + */ actual fun monthsNames(locale: CalendarLocale): List { return LegacyDateFormat.monthsNames(locale) } - // https://android.googlesource.com/platform/frameworks/base/+/jb-release/core/java/android/text/format/DateFormat.java - // - // public static boolean is24HourFormat(Context context) -- used by Android date format + /** + * Checks if the time format is 24-hour for the given locale. + * + * https://android.googlesource.com/platform/frameworks/base/+/jb-release/core/java/android/text/format/DateFormat.java + * + * public static boolean is24HourFormat(Context context) -- used by Android date format + * + * @param locale The [CalendarLocale] to check + * @return true if the locale uses 24-hour format, false otherwise + */ actual fun is24HourFormat(locale: CalendarLocale): Boolean { return LegacyDateFormat.is24HourFormat(locale) } } +/** + * Checks API level and chooses the appropriate implementation. + * + * @param old The function to execute on older API versions + * @param new The function to execute on newer API versions + * @return The result of the appropriate function + */ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O, lambda = 2) private fun apiCheck(old: () -> T, new: () -> T): T { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) diff --git a/hig/src/androidMain/kotlin/zone/ien/hig/utils/Coroutines.android.kt b/hig/src/androidMain/kotlin/zone/ien/hig/utils/Coroutines.android.kt index d9ba2bf2..085e55c1 100644 --- a/hig/src/androidMain/kotlin/zone/ien/hig/utils/Coroutines.android.kt +++ b/hig/src/androidMain/kotlin/zone/ien/hig/utils/Coroutines.android.kt @@ -1,7 +1,31 @@ +/* + * Copyright (c) 2023-2024. Compose Cupertino project and open source contributors. + * Copyright (c) 2025. Scott Lanoue. + * Copyright (c) 2026. IENGROUND of IENLAB. + * + * 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 zone.ien.hig.utils import kotlinx.coroutines.android.awaitFrame +/** + * Suspends the current coroutine until the next frame is ready. + * + * This function is used to await the next frame in Android applications, + * typically for animation or UI updates. + */ actual suspend fun awaitFrame() { awaitFrame() } \ No newline at end of file diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CalendarModel.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CalendarModel.kt index 4f6a3c9e..1f943fb5 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CalendarModel.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CalendarModel.kt @@ -25,17 +25,29 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.ReadOnlyComposable +/** + * Represents a calendar locale for formatting dates. + * + * This class is used to represent the locale used when formatting dates in the calendar. + */ expect class CalendarLocale /** * A composable function that returns the default [CalendarLocale]. * * When running on an Android platform, it will be recomposed when the `Configuration` gets updated. + * + * @return The default calendar locale */ @Composable @ReadOnlyComposable internal expect fun defaultLocale(): CalendarLocale +/** + * Returns the current [CalendarLocale]. + * + * @return The current calendar locale + */ internal expect fun currentLocale(): CalendarLocale /** @@ -47,9 +59,10 @@ internal expect fun currentLocale(): CalendarLocale * One difference is that order is irrelevant. For example, "MMMMd" will return "MMMM d" in the * en_US locale, but "d. MMMM" in the de_CH locale. * - * @param utcTimeMillis a UTC timestamp to format (milliseconds from epoch) - * @param skeleton a date format skeleton - * @param locale the [CalendarLocale] to use when formatting the given timestamp + * @param utcTimeMillis A UTC timestamp to format (milliseconds from epoch) + * @param skeleton A date format skeleton + * @param locale The [CalendarLocale] to use when formatting the given timestamp + * @return The formatted date string */ internal fun formatWithSkeleton( utcTimeMillis: Long, @@ -58,16 +71,26 @@ internal fun formatWithSkeleton( ): String = PlatformDateFormat.formatWithSkeleton(utcTimeMillis, skeleton, locale) +/** + * Represents a calendar model interface for date calculations and formatting. + * + * This interface provides methods for working with calendar dates and months, including + * formatting dates and retrieving calendar information like first day of week and weekday names. + */ internal interface CalendarModel { /** * A [CalendarDate] representing the current day. + * + * @return The current day as a [CalendarDate] */ val today: CalendarDate /** * Hold the first day of the week at the current `Locale` as an integer. The integer value * follows the ISO-8601 standard and refer to Monday as 1, and Sunday as 7. + * + * @return The first day of the week as an integer (1-7) */ val firstDayOfWeek: Int @@ -80,6 +103,8 @@ internal interface CalendarModel { * Newer APIs (i.e. API 26+), a [Pair] will hold a full name and the first letter of the * day. * Older APIs that predate API 26 will hold a full name and the first three letters of the day. + * + * @return A list of weekday names and abbreviations as [Pair] objects */ val weekdayNames: List> @@ -98,6 +123,9 @@ internal interface CalendarModel { * - dd-MM-yyyy * - dd.MM.yyyy * - MM/dd/yyyy + * + * @param locale The [CalendarLocale] to use when formatting the given timestamp + * @return The date input format for the given locale */ fun getDateInputFormat(locale: CalendarLocale): DateInputFormat @@ -108,6 +136,7 @@ internal interface CalendarModel { * be different than the one provided to this function. * * @param timeInMillis UTC milliseconds from the epoch + * @return The [CalendarDate] for the given time */ fun getCanonicalDate(timeInMillis: Long): CalendarDate @@ -115,6 +144,7 @@ internal interface CalendarModel { * Returns a [CalendarMonth] from a given _UTC_ time in milliseconds. * * @param timeInMillis UTC milliseconds from the epoch for the first day the month + * @return The [CalendarMonth] for the given time */ fun getMonth(timeInMillis: Long): CalendarMonth @@ -124,7 +154,8 @@ internal interface CalendarModel { * Note: This function ignores the [CalendarDate.dayOfMonth] value and just uses the date's * year and month to resolve a [CalendarMonth]. * - * @param date a [CalendarDate] to resolve into a month + * @param date A [CalendarDate] to resolve into a month + * @return The [CalendarMonth] for the given date */ fun getMonth(date: CalendarDate): CalendarMonth diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CalendarModelImpl.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CalendarModelImpl.kt index 9494c73a..a9c1f265 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CalendarModelImpl.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CalendarModelImpl.kt @@ -35,6 +35,14 @@ import kotlinx.datetime.toLocalDateTime import kotlin.time.Clock import kotlin.time.Instant + +/** + * A composable function that returns the default [CalendarLocale]. + * + * When running on an Android platform, it will be recomposed when the `Configuration` gets updated. + * + * @return The default calendar locale + */ internal class CalendarModelImpl: CalendarModel { override val today: CalendarDate diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoActivityIndicator.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoActivityIndicator.kt index 2c6cbaae..23963da6 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoActivityIndicator.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoActivityIndicator.kt @@ -50,16 +50,19 @@ import zone.ien.hig.theme.Gray import kotlin.math.roundToInt /** + * iOS-like segmented circle progress indicator. * - * iOS-like segmented circle progress indicator + * This composable displays an activity indicator similar to iOS's UIActivityIndicatorView. * - * @param modifier indicator modifier - * @param color color of the indicator - * @param count number of paths of the activity indicator - * @param innerRadius radius of the inner circle relative to indicator radius - * @param animationSpec infinite repeatable animation spec - * @param minAlpha opacity of the most translucent item - * */ + * @param modifier The modifier to be applied to the indicator + * @param size The size of the indicator + * @param color The color of the indicator + * @param count The number of paths of the activity indicator + * @param innerRadius The radius of the inner circle relative to indicator radius (0-1) + * @param strokeWidth The stroke width of the indicator, if unspecified uses default value + * @param animationSpec The infinite repeatable animation spec + * @param minAlpha The opacity of the most translucent item (0-1) + */ @Composable @ExperimentalCupertinoApi fun CupertinoActivityIndicator( @@ -160,16 +163,20 @@ fun CupertinoActivityIndicator( } /** + * iOS-like segmented circle progress indicator with progress tracking. * - * iOS-like segmented circle progress indicator + * This composable displays an activity indicator similar to iOS's UIActivityIndicatorView with progress tracking. * - * @param modifier indicator modifier - * @param color color of the indicator - * @param count number of paths of the activity indicator - * @param innerRadius radius of the inner circle relative to indicator radius - * @param animationSpec infinite repeatable animation spec - * @param minAlpha opacity of the most translucent item - * */ + * @param modifier The modifier to be applied to the indicator + * @param progress The progress value (0-1) to indicate completion + * @param size The size of the indicator + * @param color The color of the indicator + * @param count The number of paths of the activity indicator + * @param innerRadius The radius of the inner circle relative to indicator radius (0-1) + * @param strokeWidth The stroke width of the indicator, if unspecified uses default value + * @param animationSpec The infinite repeatable animation spec + * @param minAlpha The opacity of the most translucent item (0-1) + */ @Composable @ExperimentalCupertinoApi fun CupertinoActivityIndicator( diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoBottomAppBar.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoBottomAppBar.kt index cfb4bb39..1caf424a 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoBottomAppBar.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoBottomAppBar.kt @@ -41,7 +41,9 @@ import zone.ien.hig.theme.CupertinoTheme /** * Return true if container can't scroll forward - * */ + * + * @return True if the container cannot scroll forward + */ inline val ScrollableState.isNavigationBarTransparent: Boolean get() = !canScrollForward @@ -56,7 +58,8 @@ inline val ScrollableState.isNavigationBarTransparent: Boolean * @param color navigation bar container color. Alpha is controlled by the [CupertinoScaffold] * @param isTransparent if navigation bar currently should be transparent. See [CupertinoNavigationBar] * for use cases example. - * */ + * @return The appropriate color for the navigation bar + */ @Composable @ExperimentalCupertinoApi fun cupertinoTranslucentBottomBarColor( @@ -86,6 +89,20 @@ fun cupertinoTranslucentBottomBarColor( return Color.Transparent } +/** + * Composable function that creates a Cupertino-style bottom app bar. + * + * This composable displays a bottom app bar with Cupertino styling, similar to iOS's bottom navigation bar. + * + * @param modifier The modifier to be applied to the bottom app bar + * @param isTranslucent Whether the bottom app bar should be translucent + * @param isTransparent Whether the bottom app bar should be transparent + * @param containerColor The color of the bottom app bar container + * @param contentColor The color of the content in the bottom app bar + * @param contentPadding The padding around the content in the bottom app bar + * @param windowInsets The window insets to be applied to the bottom app bar + * @param content The content to be displayed in the bottom app bar + */ @ExperimentalCupertinoApi @Composable fun CupertinoBottomAppBar( diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoBottomSheet.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoBottomSheet.kt index ec76da31..6cbe5e11 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoBottomSheet.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoBottomSheet.kt @@ -66,7 +66,19 @@ import kotlin.jvm.JvmName /** * Content of the Cupertino modal bottom sheet. - * */ + * + * This composable creates the content area for a Cupertino modal bottom sheet. + * + * @param modifier The modifier to be applied to the bottom sheet + * @param containerColor The color of the bottom sheet container + * @param contentColor The color of the content in the bottom sheet + * @param appBarsAlpha The alpha value for app bars + * @param appBarsBlurRadius The blur radius for app bars + * @param hasNavigationTitle Whether the sheet has a navigation title + * @param topBar The top bar content of the bottom sheet + * @param bottomBar The bottom bar content of the bottom sheet + * @param content The main content of the bottom sheet + */ @Composable @ExperimentalCupertinoApi fun CupertinoBottomSheetContent( diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoBottomSheetScaffold.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoBottomSheetScaffold.kt index b74b712f..7ea5fb73 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoBottomSheetScaffold.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoBottomSheetScaffold.kt @@ -77,7 +77,7 @@ import kotlin.math.max import kotlin.math.roundToInt /** - * Standard bottom sheets co-exist with the screen’s main UI region and allow for simultaneously + * Standard bottom sheets co-exist with the screen's main UI region and allow for simultaneously * viewing and interacting with both regions. They are commonly used to keep a feature or * secondary content visible on screen when content in main UI region is frequently scrolled or * panned. @@ -86,18 +86,23 @@ import kotlin.math.roundToInt * screen, by ensuring proper layout strategy for them and collecting necessary data so these * components will work together correctly. * - * @param sheetContent the content of the bottom sheet - * @param modifier the [Modifier] to be applied to this scaffold - * @param scaffoldState the state of the bottom sheet scaffold - * @param colors color of the scaffold, sheet, scrim and background. - * @param sheetShape the shape of the bottom sheet children - * @param sheetShadowElevation the shadow elevation of the bottom sheet - * @param sheetDragHandle optional visual marker to pull the scaffold's bottom sheet - * @param sheetSwipeEnabled whether the sheet swiping is enabled and should react to the user's + * @param sheetContent The content of the bottom sheet + * @param modifier The [Modifier] to be applied to this scaffold + * @param windowInsets The window insets to be applied to the scaffold + * @param scaffoldState The state of the bottom sheet scaffold + * @param colors Color of the scaffold, sheet, scrim and background. + * @param sheetShape The shape of the bottom sheet children + * @param sheetShadowElevation The shadow elevation of the bottom sheet + * @param sheetDragHandle Optional visual marker to pull the scaffold's bottom sheet + * @param sheetSwipeEnabled Whether the sheet swiping is enabled and should react to the user's * input - * @param topBar top app bar of the screen, typically a [CupertinoTopAppBar] - * to have no color. - * @param content content of the screen. The lambda receives a [PaddingValues] that should be + * @param topBar Top app bar of the screen, typically a [CupertinoTopAppBar] + * @param bottomBar Bottom app bar of the screen + * @param appBarsBlurAlpha The alpha value for app bars + * @param appBarsBlurRadius The blur radius for app bars + * @param hasNavigationTitle Whether the screen has a navigation title + * @param applyContentScaling Whether to apply content scaling based on sheet position + * @param content Content of the screen. The lambda receives a [PaddingValues] that should be * applied to the content root via [Modifier.padding] and [Modifier.consumeWindowInsets] to * properly offset top and bottom bars. If using [Modifier.verticalScroll], apply this modifier to * the child of the scroll, and not on the scroll itself. diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoButton.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoButton.kt index c3a0d39e..db14b532 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoButton.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoButton.kt @@ -61,6 +61,16 @@ import zone.ien.hig.theme.DefaultAlpha import zone.ien.hig.theme.Shapes import zone.ien.hig.theme.Typography +/** + * Represents the size configuration for Cupertino buttons. + * + * This enum defines different button sizes including Small, Regular, Large, and ExtraLarge. + * Each size has associated shape, text style, and content padding values. + * + * @property shape The shape function that returns the appropriate shape for the button size + * @property textStyle The text style function that returns an appropriate text style for the button size + * @property contentPadding The padding values for the button content + */ enum class CupertinoButtonSize( val shape: (Shapes) -> Shape, val textStyle: (Typography) -> TextStyle = { it.body }, @@ -88,6 +98,30 @@ enum class CupertinoButtonSize( ), } +/** + * A composable function that creates a Cupertino-styled button. + * + * This button has Cupertino styling and can be customized with various sizes, colors, and appearances. + * It handles press animations and interaction states. + * + * @param onClick The action to perform when the button is clicked + * @param modifier Modifier to be applied to the button + * @param enabled Whether the button is enabled and interactive + * @param size The size of the button (Small, Regular, Large, ExtraLarge) + * @param colors The colors to use for the button appearance + * @param border The border stroke to apply to the button, or null for no border + * @param shape The shape of the button + * @param contentPadding The padding to apply to the button content + * @param interactionSource The interaction source to track button interactions + * @param content The content to display inside the button + * + * Sample usage: + * ``` + * CupertinoButton(onClick = { /* handle click */ }) { + * Text("Button") + * } + * ``` + */ @Composable @ExperimentalCupertinoApi fun CupertinoButton( @@ -145,6 +179,27 @@ fun CupertinoButton( } } +/** + * Creates a composable icon button with Cupertino styling. + * + * This function creates a button that displays only an icon. It uses the [CupertinoButton] internally + * and applies icon-specific sizing and layout. + * + * @param onClick The action to perform when the button is clicked + * @param modifier Modifier to be applied to the button + * @param enabled Whether the button is enabled and interactive + * @param colors The colors to use for the button appearance + * @param border The border stroke to apply to the button, or null for no border + * @param interactionSource The interaction source to track button interactions + * @param content The icon content to display inside the button + * + * Sample usage: + * ``` + * CupertinoIconButton(onClick = { /* handle click */ }) { + * CupertinoIcon(CupertinoIcons.star) + * } + * ``` + */ @ExperimentalCupertinoApi @Composable fun CupertinoIconButton( @@ -181,6 +236,19 @@ fun CupertinoIconButton( } +/** + * Represents the color configuration for Cupertino buttons. + * + * This immutable class holds color values for different button states (enabled, disabled) + * and different visual styles (filled, plain). + * + * @property containerColor The container color when the button is enabled + * @property contentColor The content color when the button is enabled + * @property disabledContainerColor The container color when the button is disabled + * @property disabledContentColor The content color when the button is disabled + * @property indicationColor The color used for visual feedback during interactions + * @property isPlain Indicates if this is a plain button style (no background) + */ @Immutable class CupertinoButtonColors internal constructor( private val containerColor: Color, @@ -190,23 +258,25 @@ class CupertinoButtonColors internal constructor( internal val indicationColor: Color, internal val isPlain: Boolean = false ) { - /** - * Represents the container color for this button, depending on [enabled]. - * - * @param enabled whether the button is enabled - */ - @Composable - fun containerColor(enabled: Boolean): State { +/** + * Represents the container color for this button, depending on [enabled]. + * + * @param enabled whether the button is enabled + * @return The appropriate container color based on enabled state + */ +@Composable +fun containerColor(enabled: Boolean): State { return rememberUpdatedState(if (enabled) containerColor else disabledContainerColor) } - /** - * Represents the content color for this button, depending on [enabled]. - * - * @param enabled whether the button is enabled - */ - @Composable - fun contentColor(enabled: Boolean): State { +/** + * Represents the content color for this button, depending on [enabled]. + * + * @param enabled whether the button is enabled + * @return The appropriate content color based on enabled state + */ +@Composable +fun contentColor(enabled: Boolean): State { return rememberUpdatedState(if (enabled) contentColor else disabledContentColor) } @@ -232,15 +302,32 @@ class CupertinoButtonColors internal constructor( } +/** + * The default values and color configurations for Cupertino buttons. + * + * This object provides factory methods to create different button color schemes + * that match Apple's Cupertino design guidelines, including filled, plain, + * tinted, and gray button styles. + */ @Immutable object CupertinoButtonDefaults { - /** - * Tinted button with .bordered SwiftUI style with default gray tint - * */ - @Composable - @ReadOnlyComposable - fun grayButtonColors( +/** + * Tinted button with .bordered SwiftUI style with default gray tint + * + * Creates a button with a gray background and accent text color. + * + * @param contentColor The color of the button's text/content + * @param containerColor The background color of the button + * @param disabledContentColor The color of the button's text/content when disabled + * @param disabledContainerColor The background color of the button when disabled + * @param indicationColor The color used for visual feedback during interactions + * + * @return A [CupertinoButtonColors] object with the specified configuration + */ +@Composable +@ReadOnlyComposable +fun grayButtonColors( contentColor: Color = CupertinoTheme.colorScheme.accent, containerColor: Color = CupertinoTheme.colorScheme.quaternarySystemFill, disabledContentColor: Color = CupertinoTheme.colorScheme.tertiaryLabel, @@ -254,12 +341,22 @@ object CupertinoButtonDefaults { indicationColor = indicationColor, ) - /** - * SwiftUI .borderless button - * */ - @Composable - @ReadOnlyComposable - fun plainButtonColors( +/** + * SwiftUI .borderless button + * + * Creates a button with no background that uses transparent container colors. + * + * @param contentColor The color of the button's text/content + * @param containerColor The background color of the button (default transparent) + * @param disabledContentColor The color of the button's text/content when disabled + * @param disabledContainerColor The background color of the button when disabled + * @param indicationColor The color used for visual feedback during interactions + * + * @return A [CupertinoButtonColors] object with the specified configuration + */ +@Composable +@ReadOnlyComposable +fun plainButtonColors( contentColor: Color = CupertinoTheme.colorScheme.accent, containerColor: Color = Color.Transparent, disabledContentColor: Color = CupertinoTheme.colorScheme.tertiaryLabel, @@ -274,12 +371,22 @@ object CupertinoButtonDefaults { indicationColor = indicationColor ) - /** - * Filled button with .borderedProminent SwiftUI style - * */ - @Composable - @ReadOnlyComposable - fun filledButtonColors( +/** + * Filled button with .borderedProminent SwiftUI style + * + * Creates a button with a prominent filled appearance with white text. + * + * @param contentColor The color of the button's text/content (default white) + * @param containerColor The background color of the button (default accent color) + * @param disabledContentColor The color of the button's text/content when disabled + * @param disabledContainerColor The background color of the button when disabled + * @param indicationColor The color used for visual feedback during interactions + * + * @return A [CupertinoButtonColors] object with the specified configuration + */ +@Composable +@ReadOnlyComposable +fun filledButtonColors( contentColor: Color = Color.White, containerColor: Color = CupertinoTheme.colorScheme.accent, disabledContentColor: Color = CupertinoTheme.colorScheme.tertiaryLabel, @@ -293,12 +400,22 @@ object CupertinoButtonDefaults { indicationColor = indicationColor ) - /** - * Tinted button with .bordered SwiftUI style and [contentColor] tint - * */ - @Composable - @ReadOnlyComposable - fun tintedButtonColors( +/** + * Tinted button with .bordered SwiftUI style and [contentColor] tint + * + * Creates a button with a tinted background based on the content color. + * + * @param contentColor The color of the button's text/content + * @param containerColor The background color of the button (computed from contentColor) + * @param disabledContentColor The color of the button's text/content when disabled + * @param disabledContainerColor The background color of the button when disabled + * @param indicationColor The color used for visual feedback during interactions + * + * @return A [CupertinoButtonColors] object with the specified configuration + */ +@Composable +@ReadOnlyComposable +fun tintedButtonColors( contentColor: Color = CupertinoTheme.colorScheme.accent, containerColor: Color = contentColor.copy(alpha = CupertinoButtonTokens.BorderedButtonAlpha), disabledContentColor: Color = CupertinoTheme.colorScheme.tertiaryLabel, @@ -312,19 +429,29 @@ object CupertinoButtonDefaults { indicationColor = indicationColor, ) - /** - * Filled button with .borderedProminent SwiftUI style - * */ - @Composable - @ReadOnlyComposable - @Deprecated( - "Use filledButtonColors instead", - replaceWith = ReplaceWith( - "filledButtonColors(contentColor,containerColor,disabledContentColor,disabledContainerColor,indicationColor)", - "import zone.ien.hig.filledButtonColors" - ) +/** + * Filled button with .borderedProminent SwiftUI style + * + * @param contentColor The color of the button's text/content (default white) + * @param containerColor The background color of the button (default accent color) + * @param disabledContentColor The color of the button's text/content when disabled + * @param disabledContainerColor The background color of the button when disabled + * @param indicationColor The color used for visual feedback during interactions + * + * @return A [CupertinoButtonColors] object with the specified configuration + * + * @deprecated Use [filledButtonColors] instead + */ +@Composable +@ReadOnlyComposable +@Deprecated( + "Use filledButtonColors instead", + replaceWith = ReplaceWith( + "filledButtonColors(contentColor,containerColor,disabledContentColor,disabledContainerColor,indicationColor)", + "import zone.ien.hig.filledButtonColors" ) - fun borderedProminentButtonColors( +) +fun borderedProminentButtonColors( contentColor: Color = Color.White, containerColor: Color = CupertinoTheme.colorScheme.accent, disabledContentColor: Color = CupertinoTheme.colorScheme.tertiaryLabel, @@ -338,19 +465,29 @@ object CupertinoButtonDefaults { indicationColor = indicationColor ) - /** - * Tinted button with .bordered SwiftUI style and [contentColor] tint - * */ - @Deprecated( - "Use tintedButtonColors instead", - replaceWith = ReplaceWith( - "tintedButtonColors(contentColor,containerColor,disabledContentColor,disabledContainerColor,indicationColor)", - "import zone.ien.hig.tintedButtonColors" - ) +/** + * Tinted button with .bordered SwiftUI style and [contentColor] tint + * + * @param contentColor The color of the button's text/content + * @param containerColor The background color of the button (computed from contentColor) + * @param disabledContentColor The color of the button's text/content when disabled + * @param disabledContainerColor The background color of the button when disabled + * @param indicationColor The color used for visual feedback during interactions + * + * @return A [CupertinoButtonColors] object with the specified configuration + * + * @deprecated Use [tintedButtonColors] instead + */ +@Composable +@ReadOnlyComposable +@Deprecated( + "Use tintedButtonColors instead", + replaceWith = ReplaceWith( + "tintedButtonColors(contentColor,containerColor,disabledContentColor,disabledContainerColor,indicationColor)", + "import zone.ien.hig.tintedButtonColors" ) - @Composable - @ReadOnlyComposable - fun borderedButtonColors( +) +fun borderedButtonColors( contentColor: Color = CupertinoTheme.colorScheme.accent, containerColor: Color = contentColor.copy(alpha = CupertinoButtonTokens.BorderedButtonAlpha), disabledContentColor: Color = CupertinoTheme.colorScheme.tertiaryLabel, @@ -364,19 +501,29 @@ object CupertinoButtonDefaults { indicationColor = indicationColor, ) - /** - * SwiftUI .borderless button - * */ - @Deprecated( - "Use plainButtonColors instead", - replaceWith = ReplaceWith( - "plainButtonColors(contentColor,containerColor,disabledContentColor,disabledContainerColor,indicationColor)", - "import zone.ien.hig.plainButtonColors" - ) +/** + * SwiftUI .borderless button + * + * @param contentColor The color of the button's text/content + * @param containerColor The background color of the button (default transparent) + * @param disabledContentColor The color of the button's text/content when disabled + * @param disabledContainerColor The background color of the button when disabled + * @param indicationColor The color used for visual feedback during interactions + * + * @return A [CupertinoButtonColors] object with the specified configuration + * + * @deprecated Use [plainButtonColors] instead + */ +@Composable +@ReadOnlyComposable +@Deprecated( + "Use plainButtonColors instead", + replaceWith = ReplaceWith( + "plainButtonColors(contentColor,containerColor,disabledContentColor,disabledContainerColor,indicationColor)", + "import zone.ien.hig.plainButtonColors" ) - @Composable - @ReadOnlyComposable - fun borderlessButtonColors( +) +fun borderlessButtonColors( contentColor: Color = CupertinoTheme.colorScheme.accent, containerColor: Color = Color.Transparent, disabledContentColor: Color = CupertinoTheme.colorScheme.tertiaryLabel, @@ -390,19 +537,29 @@ object CupertinoButtonDefaults { indicationColor = indicationColor ) - /** - * Tinted button with .bordered SwiftUI with default tint - * */ - @Deprecated( - "Use grayButtonColors instead", - replaceWith = ReplaceWith( - "grayButtonColors(contentColor,containerColor,disabledContentColor,disabledContainerColor,indicationColor)", - "import zone.ien.hig.grayButtonColors" - ) +/** + * Tinted button with .bordered SwiftUI with default tint + * + * @param contentColor The color of the button's text/content + * @param containerColor The background color of the button (computed from contentColor) + * @param disabledContentColor The color of the button's text/content when disabled + * @param disabledContainerColor The background color of the button when disabled + * @param indicationColor The color used for visual feedback during interactions + * + * @return A [CupertinoButtonColors] object with the specified configuration + * + * @deprecated Use [grayButtonColors] instead + */ +@Composable +@ReadOnlyComposable +@Deprecated( + "Use grayButtonColors instead", + replaceWith = ReplaceWith( + "grayButtonColors(contentColor,containerColor,disabledContentColor,disabledContainerColor,indicationColor)", + "import zone.ien.hig.grayButtonColors" ) - @Composable - @ReadOnlyComposable - fun borderedGrayButtonColors( +) +fun borderedGrayButtonColors( contentColor: Color = CupertinoTheme.colorScheme.accent, containerColor: Color = CupertinoTheme.colorScheme.quaternarySystemFill, disabledContentColor: Color = CupertinoTheme.colorScheme.tertiaryLabel, @@ -417,6 +574,11 @@ object CupertinoButtonDefaults { ) } +/** + * Internal token values used for button styling and behavior. + * + * This object contains constants used for button animations, sizing, and other styling properties. + */ internal object CupertinoButtonTokens { const val PressedPlainButonAlpha = .33f val IconButtonSize = 42.dp diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoCheckbox.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoCheckbox.kt index 7b866a60..d26bae1b 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoCheckbox.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoCheckbox.kt @@ -49,6 +49,18 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.util.lerp import zone.ien.hig.theme.CupertinoTheme +/** + * Cupertino-style checkbox composable. + * + * This composable creates a checkbox that follows Cupertino design guidelines. + * + * @param checked Whether the checkbox is currently checked + * @param onCheckedChange Called when the user clicks the checkbox to change its state + * @param modifier The modifier to be applied to the checkbox + * @param enabled Whether the checkbox is enabled or not + * @param colors The colors to be used for the checkbox in different states + * @param interactionSource The interaction source for the checkbox + */ @Composable fun CupertinoCheckBox( checked: Boolean, diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt index 9fa12a9b..6d63ebcf 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt @@ -1,4 +1,4 @@ -/* +/** * Copyright (c) 2023-2024. Compose Cupertino project and open source contributors. * Copyright (c) 2025. Scott Lanoue. * Copyright (c) 2026. IENGROUND of IENLAB. @@ -16,6 +16,9 @@ * limitations under the License. */ +/** + * Contains the implementation of Cupertino-style dropdown menus. + */ package zone.ien.hig import androidx.compose.animation.core.LinearOutSlowInEasing @@ -109,8 +112,26 @@ import kotlin.math.min import kotlin.math.sin import kotlin.math.tanh +/** + * A scope for defining menu items within a dropdown menu. + */ sealed interface CupertinoMenuScope +/** + * Creates a Cupertino-style dropdown menu. + * + * @param expanded Whether the dropdown menu is currently visible. + * @param onDismissRequest Called when the user tries to dismiss the menu. + * @param modifier Modifier to be applied to the menu. + * @param offset Offset for positioning the menu. + * @param paddingValues Padding values for the menu content. + * @param containerColor Color for the menu background. + * @param width Width of the dropdown menu. + * @param scrollState Scroll state for the menu content. + * @param properties Popup properties for the menu. + * @param backdrop Backdrop effect to apply to the menu. + * @param content The content of the dropdown menu. + */ @Composable @ExperimentalCupertinoApi fun CupertinoDropdownMenu( @@ -164,6 +185,13 @@ fun CupertinoDropdownMenu( } } +/** + * Creates a menu item within the dropdown menu. + * + * @param modifier Modifier to be applied to the menu item. + * @param minHeight Minimum height for the menu item. + * @param content Content to be displayed within the menu item. + */ @Composable fun CupertinoMenuScope.MenuItem( modifier: Modifier = Modifier, @@ -187,6 +215,12 @@ fun CupertinoMenuScope.MenuItem( } } +/** + * Creates a menu section with an optional title. + * + * @param title Optional title for the menu section. + * @param content Content to be displayed within the menu section. + */ @Composable inline fun CupertinoMenuScope.MenuSection( noinline title: (@Composable () -> Unit)? = null, @@ -196,6 +230,12 @@ inline fun CupertinoMenuScope.MenuSection( content() } +/** + * Creates a menu title within the dropdown menu. + * + * @param modifier Modifier to be applied to the menu title. + * @param title The title content to be displayed. + */ @Composable fun CupertinoMenuScope.MenuTitle( modifier: Modifier = Modifier, @@ -216,6 +256,18 @@ fun CupertinoMenuScope.MenuTitle( } } +/** + * Creates a menu action within the dropdown menu. + * + * @param onClick Called when the menu action is clicked. + * @param modifier Modifier to be applied to the menu action. + * @param onClickLabel Optional label for accessibility when the action is clicked. + * @param enabled Whether the menu action is enabled. + * @param contentColor Color for the menu action content. + * @param leadingIcon Optional leading icon for the menu action. + * @param trailingIcon Optional trailing icon for the menu action. + * @param title The title content to be displayed. + */ @Composable fun CupertinoMenuScope.MenuAction( onClick: () -> Unit, @@ -251,6 +303,20 @@ fun CupertinoMenuScope.MenuAction( } } +/** + * Creates a menu picker action within the dropdown menu. + * + * @param isSelected Whether the menu picker action is currently selected. + * @param onClick Called when the menu picker action is clicked. + * @param modifier Modifier to be applied to the menu picker action. + * @param onClickLabel Optional label for accessibility when the action is clicked. + * @param enabled Whether the menu picker action is enabled. + * @param contentColor Color for the menu picker action content. + * @param selectionIcon Icon to show when the item is selected. + * @param leadingIcon Optional leading icon for the menu picker action. + * @param trailingIcon Optional trailing icon for the menu picker action. + * @param title The title content to be displayed. + */ @Composable fun CupertinoMenuScope.MenuPickerAction( isSelected: Boolean, @@ -297,6 +363,13 @@ fun CupertinoMenuScope.MenuPickerAction( ) } +/** + * Creates a menu divider within the dropdown menu. + * + * @param modifier Modifier to be applied to the menu divider. + * @param color Color for the menu divider. + * @param height Height of the menu divider. + */ @Composable fun CupertinoMenuScope.MenuDivider( modifier: Modifier = Modifier, @@ -312,6 +385,18 @@ fun CupertinoMenuScope.MenuDivider( ) } +/** + * Creates a menu action without padding, for internal use. + * + * @param onClick Called when the menu action is clicked. + * @param modifier Modifier to be applied to the menu action. + * @param onClickLabel Optional label for accessibility when the action is clicked. + * @param enabled Whether the menu action is enabled. + * @param contentColor Color for the menu action content. + * @param leadingIcon Optional leading icon for the menu action. + * @param trailingIcon Optional trailing icon for the menu action. + * @param title The title content to be displayed. + */ @OptIn(ExperimentalCupertinoApi::class) @Composable private fun CupertinoMenuScope.ActionWithoutPadding( @@ -381,6 +466,9 @@ private fun CupertinoMenuScope.ActionWithoutPadding( } } +/** + * Default values for Cupertino dropdown menu. + */ @Immutable object CupertinoDropdownMenuDefaults { val DefaultWidth = 260.dp @@ -414,6 +502,19 @@ object CupertinoDropdownMenuDefaults { } } +/** + * Creates the content for the dropdown menu. + * + * @param width Width of the menu. + * @param containerColor Color for the menu background. + * @param expandedStates State for the menu expansion animation. + * @param transformOriginState Transform origin for the menu animation. + * @param scrollState Scroll state for the menu content. + * @param paddingValue Padding values for the menu content. + * @param modifier Modifier to be applied to the menu. + * @param backdrop Backdrop effect to apply to the menu. + * @param content The content to be displayed in the menu. + */ @Composable private fun DropdownMenuContent( width: Dp, @@ -528,6 +629,13 @@ private fun DropdownMenuContent( private enum class CupertinoDropdownMenuSlots { Section, Item, Separator } +/** + * Calculates the transform origin for the dropdown menu animation. + * + * @param parentBounds Bounds of the parent element. + * @param menuBounds Bounds of the menu. + * @return The calculated transform origin. + */ internal fun calculateTransformOrigin(parentBounds: IntRect, menuBounds: IntRect): TransformOrigin { val pivotX = when { menuBounds.left >= parentBounds.right -> 0f diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoLiquidButton.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoLiquidButton.kt index 4bda7deb..9dadc5d5 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoLiquidButton.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoLiquidButton.kt @@ -61,6 +61,32 @@ import kotlin.math.tanh import androidx.compose.animation.Animatable as ColorAnimatable import androidx.compose.animation.core.Animatable as FloatAnimatable +/** + * A composable function that creates a Cupertino-styled liquid button with backdrop effects. + * + * This button features liquid glass-like effects with adaptive tinting and surface colors that + * respond to the background theme and content luminance. + * + * @param onClick The action to perform when the button is clicked + * @param modifier Modifier to be applied to the button + * @param enabled Whether the button is enabled and interactive + * @param size The size of the button (Small, Regular, Large, ExtraLarge) + * @param colors The colors to use for the button appearance + * @param shape The shape of the button + * @param contentPadding The padding to apply to the button content + * @param interactionSource The interaction source to track button interactions + * @param backdrop The backdrop effect configuration for the button's visual appearance + * @param isBackgroundAdaptive Whether the button should adapt to the background color + * @param isInteractive Whether the button responds to interactive gestures + * @param content The content to display inside the button + * + * Sample usage: + * ``` + * CupertinoLiquidButton(onClick = { /* handle click */ }, backdrop = backdrop) { + * Text("Button") + * } + * ``` + */ @Composable @ExperimentalCupertinoApi fun CupertinoLiquidButton( diff --git a/hig/src/iosMain/kotlin/zone/ien/hig/CupertinoTextField.ios.kt b/hig/src/iosMain/kotlin/zone/ien/hig/CupertinoTextField.ios.kt index 72a345d7..7a303e72 100644 --- a/hig/src/iosMain/kotlin/zone/ien/hig/CupertinoTextField.ios.kt +++ b/hig/src/iosMain/kotlin/zone/ien/hig/CupertinoTextField.ios.kt @@ -1,9 +1,34 @@ +/* + * Copyright (c) 2023-2024. Compose Cupertino project and open source contributors. + * Copyright (c) 2025. Scott Lanoue. + * Copyright (c) 2026. IENGROUND of IENLAB. + * + * 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 zone.ien.hig import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.text.input.PlatformImeOptions +/** + * Enables native input for keyboard options on iOS. + * + * This function enables native input handling for iOS platforms. + * + * @return The [KeyboardOptions] with native input enabled + */ @OptIn(ExperimentalComposeUiApi::class) internal actual fun KeyboardOptions.enableNativeInput() = this diff --git a/hig/src/iosMain/kotlin/zone/ien/hig/PlatformDateFormat.kt b/hig/src/iosMain/kotlin/zone/ien/hig/PlatformDateFormat.kt index 7e08642f..7330b949 100644 --- a/hig/src/iosMain/kotlin/zone/ien/hig/PlatformDateFormat.kt +++ b/hig/src/iosMain/kotlin/zone/ien/hig/PlatformDateFormat.kt @@ -17,4 +17,3 @@ */ package zone.ien.hig - From c33af69340a8de59ddcf055ac6a2aa38ffde8b1a Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 18:37:45 +0900 Subject: [PATCH 069/109] Update hig/src/commonMain/kotlin/zone/ien/hig/CupertinoButton.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- hig/src/commonMain/kotlin/zone/ien/hig/CupertinoButton.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoButton.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoButton.kt index db14b532..f577a261 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoButton.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoButton.kt @@ -541,7 +541,7 @@ fun borderlessButtonColors( * Tinted button with .bordered SwiftUI with default tint * * @param contentColor The color of the button's text/content - * @param containerColor The background color of the button (computed from contentColor) + * @param containerColor The background color of the button * @param disabledContentColor The color of the button's text/content when disabled * @param disabledContainerColor The background color of the button when disabled * @param indicationColor The color used for visual feedback during interactions From af3f3e3260a49581e1a7c7775b7cda697511faf0 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 18:38:22 +0900 Subject: [PATCH 070/109] Update hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt index 7502b0d9..65345109 100644 --- a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt +++ b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt @@ -32,7 +32,6 @@ import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.dp import androidx.compose.ui.window.PopupProperties import com.kyant.backdrop.Backdrop From 4d7c73e9c510be5ec77cc7fc5da296b4d0bbaa2a Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 18:39:09 +0900 Subject: [PATCH 071/109] Update hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt index 65345109..5f1b63bc 100644 --- a/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt +++ b/hig-native/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.kt @@ -42,9 +42,9 @@ import com.kyant.backdrop.Backdrop * @param enabled whether the menu item is enabled * @param icon an optional icon painter for the menu item * @param isDestructive whether this is a destructive menu action -@Immutable -data class CupertinoMenuItemData( + * @param onClick callback when the menu item is clicked */ +@Immutable data class CupertinoMenuItemData( val title: String, val enabled: Boolean = true, From 88b180c136550fcfb5b194e2f57af6193ae800ef Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 18:41:13 +0900 Subject: [PATCH 072/109] Update hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt index db1a1114..a2989316 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt @@ -316,15 +316,13 @@ private enum class ButtonType { * providing appropriate styling for different button types (Filled, Text, Tonal). * * @param type The type of button to adapt (Filled, Text, or Tonal) - */ -@ExperimentalAdaptiveApi -/** - * 버튼 유형에 따라 적응형 어댑테이션을 제공하는 클래스입니다. - * - * @param type [ButtonType] - 버튼의 유형 (Filled, Text, Tonal) * @see CupertinoButtonAdaptation * @see MaterialButtonAdaptation */ +@ExperimentalAdaptiveApi +private class ButtonAdaptation( + private val type: ButtonType, +): Adaptation() { private class ButtonAdaptation( private val type: ButtonType, ): Adaptation() { From 43dc841a74d5976a0e3bb43366df8a6c140fb6a5 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 18:42:14 +0900 Subject: [PATCH 073/109] Update hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt index a2989316..e44073f0 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt @@ -127,7 +127,7 @@ fun AdaptiveButton( * @param adaptation [CupertinoButtonAdaptation]와 [MaterialButtonAdaptation]에 대한 사용자 정의 설정 함수 * @param content 버튼 내부에 표시될 내용 * @see AdaptiveWidget - * @see CupertinoButton + * @see CupertinoLiquidButton * @see TextButton */ @OptIn(ExperimentalCupertinoApi::class) From 5cdf886e56d83b7aa17f04a5ed8eb2a3643df006 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 18:42:23 +0900 Subject: [PATCH 074/109] Update hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt index e44073f0..d8d878f0 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt @@ -191,7 +191,7 @@ fun AdaptiveTextButton( * @param adaptation [CupertinoButtonAdaptation]와 [MaterialButtonAdaptation]에 대한 사용자 정의 설정 함수 * @param content 버튼 내부에 표시될 내용 * @see AdaptiveWidget - * @see CupertinoButton + * @see CupertinoLiquidButton * @see FilledTonalButton */ @OptIn(ExperimentalCupertinoApi::class) From b2f4470fe2f7e340f23895d7c98803c5cc64bdc9 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 18:42:32 +0900 Subject: [PATCH 075/109] Update hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt index 806491d3..2ec90781 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt @@ -47,7 +47,7 @@ import zone.ien.hig.CupertinoTriStateCheckBox * @param modifier 요소에 적용할 Modifier * @param enabled 체크박스가 활성화되어 있는지 여부 * @param interactionSource 상호작용 소스 - * @param adaptation [CupertinoCheckBoxAdaptation]와 [MaterialCheckBoxAdaptation]에 대한 사용자 정의 설정 함수 + * @param adaptation [CupertinoCheckBoxAdaptation]과 [MaterialCheckBoxAdaptation]에 대한 사용자 정의 설정 함수 * @see AdaptiveWidget * @see CupertinoCheckBox * @see Checkbox From ae428bdcd793397e831cad8da2e34c9eb5f35bc5 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 18:46:12 +0900 Subject: [PATCH 076/109] Update hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt index 289a183c..7e903a83 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt @@ -182,13 +182,7 @@ fun AdaptiveTheme( */ @Immutable @ExperimentalAdaptiveApi -/** - * Material 테마 사양 클래스로, Material Design 3 테마의 컬러, 모양, 타이포그래피를 정의합니다. - * - * @param colorScheme [MaterialColorScheme] - 테마의 컬러 스키마 - * @param shapes [MaterialShapes] - 테마의 모양 - * @param typography [MaterialTypography] - 테마의 타이포그래피 - */ +class MaterialThemeSpec( class MaterialThemeSpec( val colorScheme: MaterialColorScheme = materialLightColorScheme(), val shapes: MaterialShapes = MaterialShapes(), From 9883acc9d94486279e244d519c4218cdb0c000ae Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 18:46:42 +0900 Subject: [PATCH 077/109] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(core):=20?= =?UTF-8?q?AdaptiveButton=20=EB=B0=8F=20AdaptiveSwitch=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AdaptiveButton.kt에서 ExperimentalAdaptiveApi 어노테이션을 OptIn으로 변경하고 ButtonAdaptation 클래스 리팩토링 - AdaptiveSwitch.kt에서 CupertinoSwitchAdaptation 및 MaterialSwitchAdaptation 클래스 구조 개선 및 문서화 강화 - SwitchAdaptation 클래스 내 Composable 함수 및 remember 함수 사용 방식 최적화 --- .../kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt | 5 +---- .../kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt | 8 +------- .../kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt | 1 - 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt index d8d878f0..a7e15d86 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt @@ -319,10 +319,7 @@ private enum class ButtonType { * @see CupertinoButtonAdaptation * @see MaterialButtonAdaptation */ -@ExperimentalAdaptiveApi -private class ButtonAdaptation( - private val type: ButtonType, -): Adaptation() { +@OptIn(ExperimentalAdaptiveApi::class) private class ButtonAdaptation( private val type: ButtonType, ): Adaptation() { diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt index 205e836a..4d107bfd 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt @@ -99,13 +99,6 @@ fun AdaptiveSwitch( /** * Cupertino switch adaptation. * - * Container class for Cupertino switch adaptation properties. - * - * @param colors the colors to be used for the switch - * @param backdrop backdrop to use for the switch - */ -@Stable -/** * Cupertino 스위치에 대한 적응형 어댑테이션 클래스로, 스위치의 다양한 속성을 관리합니다. * * @param colors [CupertinoSwitchColors] - 스위치의 색상 설정 @@ -113,6 +106,7 @@ fun AdaptiveSwitch( * @see CupertinoSwitchColors * @see Backdrop */ +@Stable class CupertinoSwitchAdaptation internal constructor( colors: CupertinoSwitchColors, backdrop: Backdrop diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt index 7e903a83..97177261 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt @@ -182,7 +182,6 @@ fun AdaptiveTheme( */ @Immutable @ExperimentalAdaptiveApi -class MaterialThemeSpec( class MaterialThemeSpec( val colorScheme: MaterialColorScheme = materialLightColorScheme(), val shapes: MaterialShapes = MaterialShapes(), From 692276b93ec4a9f3eab4d0f5c2727d33f0fc601a Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 18:50:21 +0900 Subject: [PATCH 078/109] Update hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../zone/ien/hig/adaptive/AdaptiveCheckbox.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt index 806491d3..bf110d4a 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt @@ -37,17 +37,17 @@ import zone.ien.hig.CupertinoCheckboxDefaults import zone.ien.hig.CupertinoTriStateCheckBox /** - * 적응형 체크박스 컴포넌트로, Material Design과 Cupertino 테마에 따라 다른 체크박스 스타일을 제공합니다. + * An adaptive checkbox component that provides different checkbox styles based on Material Design and Cupertino themes. * - * [AdaptiveCheckbox]는 Material Design의 [Checkbox] 컴포넌트와 Cupertino의 [CupertinoCheckBox] 컴포넌트를 사용하여 - * 운영체제별로 적절한 체크박스를 제공합니다. + * [AdaptiveCheckbox] uses the Material Design [Checkbox] component and the Cupertino [CupertinoCheckBox] component + * to provide an appropriate checkbox for each operating system. * - * @param checked 체크박스의 현재 체크 여부 - * @param onCheckedChange 체크박스 상태 변경 시 호출되는 함수 - * @param modifier 요소에 적용할 Modifier - * @param enabled 체크박스가 활성화되어 있는지 여부 - * @param interactionSource 상호작용 소스 - * @param adaptation [CupertinoCheckBoxAdaptation]와 [MaterialCheckBoxAdaptation]에 대한 사용자 정의 설정 함수 + * @param checked the current checked state of the checkbox + * @param onCheckedChange callback to be invoked when the checkbox state changes + * @param modifier the [Modifier] to be applied to the element + * @param enabled controls the enabled state of the checkbox + * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s + * @param adaptation configuration block for [CupertinoCheckBoxAdaptation] and [MaterialCheckBoxAdaptation] * @see AdaptiveWidget * @see CupertinoCheckBox * @see Checkbox From 12c70711425dbb6fd66a8ca998eb87850768ee7b Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 18:51:01 +0900 Subject: [PATCH 079/109] Update hig-adaptive/src/nonAndroidMain/kotlin/zone/ien/hig/adaptive/SystemTheme.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../kotlin/zone/ien/hig/adaptive/SystemTheme.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/hig-adaptive/src/nonAndroidMain/kotlin/zone/ien/hig/adaptive/SystemTheme.kt b/hig-adaptive/src/nonAndroidMain/kotlin/zone/ien/hig/adaptive/SystemTheme.kt index c81b445e..921d2cd9 100644 --- a/hig-adaptive/src/nonAndroidMain/kotlin/zone/ien/hig/adaptive/SystemTheme.kt +++ b/hig-adaptive/src/nonAndroidMain/kotlin/zone/ien/hig/adaptive/SystemTheme.kt @@ -20,13 +20,13 @@ import androidx.compose.material3.ColorScheme import androidx.compose.runtime.Composable /** - * 시스템 Material Color Scheme을 반환하는 함수 - * - * 이 함수는 Android 플랫폼에서 사용되는 시스템 색상 기준을 반환하며, - * 현재는 null을 반환하는 구현을 가지고 있습니다. - * - * @param dark 다크 모드 여부 - * @return 시스템 색상 스킴 또는 null + * Returns the system Material Color Scheme. + * + * This function returns the system color scheme used on the Android platform, + * and currently has an implementation that returns null. + * + * @param dark whether dark mode is enabled + * @return the system color scheme or null */ @Composable internal actual fun systemMaterialColorScheme(dark: Boolean): ColorScheme? { From 34b5c3c82ead7ed0554fda29c6de7d84baf39fb7 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 18:51:09 +0900 Subject: [PATCH 080/109] Update hig-adaptive/src/nonIosMain/kotlin/zone/ien/hig/adaptive/icons/SystemImage.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../zone/ien/hig/adaptive/icons/SystemImage.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/hig-adaptive/src/nonIosMain/kotlin/zone/ien/hig/adaptive/icons/SystemImage.kt b/hig-adaptive/src/nonIosMain/kotlin/zone/ien/hig/adaptive/icons/SystemImage.kt index 1db5aecc..8a3c6467 100644 --- a/hig-adaptive/src/nonIosMain/kotlin/zone/ien/hig/adaptive/icons/SystemImage.kt +++ b/hig-adaptive/src/nonIosMain/kotlin/zone/ien/hig/adaptive/icons/SystemImage.kt @@ -22,13 +22,13 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.painter.Painter /** - * 비 iOS 플랫폼에 맞춘 시스템 이미지를 반환하는 함수 - * - * 이 함수는 iOS가 아닌 플랫폼(예: Android, Desktop 등)에서 사용되며, - * 항상 null을 반환하여 시스템 이미지 사용을 방지합니다. - * - * @param name 시스템 이미지 이름 - * @return null (시스템 이미지 지원되지 않음) + * Returns a system image for non-iOS platforms. + * + * This function is used on platforms other than iOS (e.g., Android, Desktop, etc.) + * and always returns null to prevent the use of system images. + * + * @param name the name of the system image + * @return null (system images are not supported) */ @Composable internal actual fun systemImage(name: String): Painter? = null From aebc64df5d8a69e4cb9badcff34bec775bb455a8 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 18:51:28 +0900 Subject: [PATCH 081/109] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(core):=20?= =?UTF-8?q?AdaptiveButton=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20?= =?UTF-8?q?=EB=B0=8F=20=EC=A3=BC=EC=84=9D=20=EC=98=81=EB=AC=B8=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AdaptiveButton.kt 파일의 주석을 영문으로 변경하고, 코드 구조를 개선함 - ButtonAdaptation 클래스의 컴포저블 함수 로직을 재구성하여 코드 가독성 향상 - AdaptiveTheme.kt 파일의 주석을 영문으로 갱신함 --- .../zone/ien/hig/adaptive/AdaptiveButton.kt | 27 +++++++------------ .../zone/ien/hig/adaptive/AdaptiveTheme.kt | 2 +- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt index db1a1114..7b3f8fcc 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt @@ -115,19 +115,19 @@ fun AdaptiveButton( } /** - * 적응형 버튼으로, Material [TextButton] 또는 테두리가 없는 [CupertinoLiquidButton] 모양을 사용합니다. + * An adaptive button that uses either Material [TextButton] or borderless [CupertinoLiquidButton] appearance. * - * Material Design에서는 [TextButton] 컴포넌트를 사용하고, Cupertino 테마에서는 [CupertinoLiquidButton] 컴포넌트를 사용하여 - * 운영체제별로 적절한 UI를 제공합니다. + * Material Design uses the [TextButton] component, while the Cupertino theme uses + * the [CupertinoLiquidButton] component to provide a native look and feel on each platform. * - * @param onClick 버튼이 클릭되었을 때 호출되는 함수 - * @param modifier 버튼에 적용할 Modifier - * @param enabled 버튼이 활성화되어 있는지 여부 - * @param interactionSource 상호작용 소스 - * @param adaptation [CupertinoButtonAdaptation]와 [MaterialButtonAdaptation]에 대한 사용자 정의 설정 함수 - * @param content 버튼 내부에 표시될 내용 + * @param onClick called when the button is clicked + * @param modifier the [Modifier] to be applied to this button + * @param enabled controls the enabled state of this button + * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s + * @param adaptation configuration block for [CupertinoButtonAdaptation] and [MaterialButtonAdaptation] + * @param content the content to be displayed inside the button * @see AdaptiveWidget - * @see CupertinoButton + * @see CupertinoLiquidButton * @see TextButton */ @OptIn(ExperimentalCupertinoApi::class) @@ -318,13 +318,6 @@ private enum class ButtonType { * @param type The type of button to adapt (Filled, Text, or Tonal) */ @ExperimentalAdaptiveApi -/** - * 버튼 유형에 따라 적응형 어댑테이션을 제공하는 클래스입니다. - * - * @param type [ButtonType] - 버튼의 유형 (Filled, Text, Tonal) - * @see CupertinoButtonAdaptation - * @see MaterialButtonAdaptation - */ private class ButtonAdaptation( private val type: ButtonType, ): Adaptation() { diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt index 289a183c..8cb63098 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt @@ -298,7 +298,7 @@ class CupertinoThemeSpec( /** - * [AdaptiveTheme]에서 선언된 테마로, 현재 테마를 확인할 수 있습니다. + * The theme declared as a target in [AdaptiveTheme], allowing you to check the current theme. */ @ExperimentalAdaptiveApi val currentTheme: Theme From ddb605da11277686a576c443451457c834ac7a56 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 18:53:57 +0900 Subject: [PATCH 082/109] =?UTF-8?q?=20fix(core):=20=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EB=B2=84=EC=A0=84=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - libs.versions.toml 파일의 lib-version-name 버전을 1.1.0-alpha04에서 1.1.0으로 변경 - 의존성 버전 정합성 유지 위한 간단한 수정 - 빌드 시스템 호환성 개선을 위한 버전 업그레이드 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fff8dd46..2e39adb4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "9.0.0" kotlin = "2.3.21" -lib-version-name = "1.1.0-alpha04" +lib-version-name = "1.1.0" # plugin compose-plugin = "1.11.0-rc01" From c847e899e6b3f78e632767a623d354cfc933b454 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 19:03:27 +0900 Subject: [PATCH 083/109] Update example/composeApp/src/commonMain/kotlin/test/TestScreen.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- example/composeApp/src/commonMain/kotlin/test/TestScreen.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt b/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt index 116e126e..932398c1 100644 --- a/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt +++ b/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt @@ -92,8 +92,8 @@ fun TestScreen( AnimatedVisibility( visible = enabled, - enter = slideInHorizontally(tween(3000)) { -it }, - exit = slideOutHorizontally(tween(3000)) { -it } +enter = slideInHorizontally(tween(300)) { -it }, +exit = slideOutHorizontally(tween(300)) { -it } ) { Box { CupertinoLiquidIconButton( From 6f8325d44f6f3858de9a64a07cfb6037028d7a8a Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 19:04:25 +0900 Subject: [PATCH 084/109] Update hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt index 6d63ebcf..b3a388ea 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt @@ -281,7 +281,7 @@ fun CupertinoMenuScope.MenuAction( ) { this as CupertinoMenuScopeImpl - // ✅ 아이콘이 있는 아이템이 하나라도 있으면 hasIcon = true 등록 +// If at least one item has an icon, set hasIcon to true if (leadingIcon != null) { DisposableEffect(this) { val prev = hasIcon From deff28bcb8de555d33e2c713e748cc7407c7cf8d Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 19:05:01 +0900 Subject: [PATCH 085/109] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(core):=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20?= =?UTF-8?q?=EB=B0=8F=20=EB=AC=B8=EC=84=9C=ED=99=94=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CupertinoWidgetsScreen.kt에서 불필요한 주석 제거 및 네이티브 컴포넌트 로직 개선 - gradle.properties에서 보안 정보 제거 및 설정 정리 - AdaptiveButton.kt 문서화 내용 영어로 번역 및 개선 - CupertinoDropdownMenu.kt 주석 정리 및 코드 정리 - TestScreen.kt 애니메이션 관련 코드 정리 및 일관성 유지 --- .../cupertino/CupertinoWidgetsScreen.kt | 22 +++++++------------ .../src/commonMain/kotlin/test/TestScreen.kt | 8 +++---- gradle.properties | 8 +------ .../zone/ien/hig/adaptive/AdaptiveButton.kt | 18 +++++++-------- .../zone/ien/hig/CupertinoDropdownMenu.kt | 2 +- 5 files changed, 23 insertions(+), 35 deletions(-) diff --git a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt index 7f3f1010..522daf2b 100644 --- a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt +++ b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt @@ -868,10 +868,8 @@ private fun BottomBarSample( var tab by remember { mutableStateOf(0) } val content = listOf( "Profile" to Icons.Default.Delete, -// "Menu" to Icons.Default.Save, -// "Profile" to AdaptiveIcons.Outlined.Person, -// "Menu" to AdaptiveIcons.Outlined.Menu, -// "Settings" to AdaptiveIcons.Outlined.Settings, + "Menu" to Icons.Default.Save, + "Profile" to AdaptiveIcons.Outlined.Person, ) if (isNative) { @@ -890,20 +888,17 @@ private fun BottomBarSample( } ) } else { - CupertinoNavigationBar( -// AdaptiveNavigationBar( + AdaptiveNavigationBar( selectedTabIndex = { tab }, onTabSelected = { tab = it }, tabsCount = content.size, - backdrop = backdrop, -// adaptation = { -// cupertino { this.backdrop = backdrop } -// }, + adaptation = { + cupertino { this.backdrop = backdrop } + }, ) { content.forEachIndexed { index, pair -> - CupertinoNavigationBarItem( -// AdaptiveNavigationBarItem( -// index = index, + AdaptiveNavigationBarItem( + index = index, onClick = { tab = index }, icon = { Icon( @@ -947,7 +942,6 @@ private fun SheetSample( CupertinoText("Done") } }, -// isTransparent = sheetListState.isTopBarTransparent ) } ) { pv -> diff --git a/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt b/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt index 932398c1..ceb02bd5 100644 --- a/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt +++ b/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt @@ -92,8 +92,8 @@ fun TestScreen( AnimatedVisibility( visible = enabled, -enter = slideInHorizontally(tween(300)) { -it }, -exit = slideOutHorizontally(tween(300)) { -it } + enter = slideInHorizontally(tween(300)) { -it }, + exit = slideOutHorizontally(tween(300)) { -it } ) { Box { CupertinoLiquidIconButton( @@ -359,8 +359,8 @@ exit = slideOutHorizontally(tween(300)) { -it } ) { AnimatedVisibility( visible = enabled, - enter = slideInHorizontally(tween(3000)), - exit = slideOutHorizontally(tween(3000)) + enter = slideInHorizontally(tween(300)), + exit = slideOutHorizontally(tween(300)) ) { CupertinoLiquidIconButton( onClick = { }, diff --git a/gradle.properties b/gradle.properties index 76c4eea4..ac6d0600 100644 --- a/gradle.properties +++ b/gradle.properties @@ -45,10 +45,4 @@ org.jetbrains.compose.experimental.macos.enabled=true kotlin.mpp.stability.nowarn=true kotlin.mpp.enableCInteropCommonization=true kotlin.mpp.androidSourceSetLayoutVersion=2 -kotlin.native.ignoreDisabledTargets=true - -mavenCentralUsername = vkBvsK -mavenCentralPassword = n8V4ktdFm5tmMmi7CbZ4FDwI3tDid74xy -signing.keyId = 2100B93E -signing.password = thisis1en9r0vnd -signing.secretKeyRingFile = /Users/ienground/.gnupg/secring.gpg \ No newline at end of file +kotlin.native.ignoreDisabledTargets=true \ No newline at end of file diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt index 5794caaa..6e48fdbe 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt @@ -179,17 +179,17 @@ fun AdaptiveTextButton( } /** - * 적응형 버튼으로, Material [FilledTonalButton] 또는 테두리가 있는 [CupertinoLiquidButton] 모양을 사용합니다. + * An adaptive button that uses either Material [FilledTonalButton] or bordered [CupertinoLiquidButton] appearance. * - * Material Design에서는 [FilledTonalButton] 컴포넌트를 사용하고, Cupertino 테마에서는 [CupertinoLiquidButton] 컴포넌트를 사용하여 - * 운영체제별로 적절한 UI를 제공합니다. + * Material Design uses the [FilledTonalButton] component, while the Cupertino theme uses + * the [CupertinoLiquidButton] component to provide a native look and feel on each platform. * - * @param onClick 버튼이 클릭되었을 때 호출되는 함수 - * @param modifier 버튼에 적용할 Modifier - * @param enabled 버튼이 활성화되어 있는지 여부 - * @param interactionSource 상호작용 소스 - * @param adaptation [CupertinoButtonAdaptation]와 [MaterialButtonAdaptation]에 대한 사용자 정의 설정 함수 - * @param content 버튼 내부에 표시될 내용 + * @param onClick called when the button is clicked + * @param modifier the [Modifier] to be applied to this button + * @param enabled controls the enabled state of this button + * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s + * @param adaptation configuration block for [CupertinoButtonAdaptation] and [MaterialButtonAdaptation] + * @param content the content to be displayed inside the button * @see AdaptiveWidget * @see CupertinoLiquidButton * @see FilledTonalButton diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt index b3a388ea..f288a920 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt @@ -281,7 +281,7 @@ fun CupertinoMenuScope.MenuAction( ) { this as CupertinoMenuScopeImpl -// If at least one item has an icon, set hasIcon to true + // If at least one item has an icon, set hasIcon to true if (leadingIcon != null) { DisposableEffect(this) { val prev = hasIcon From 80e272eb368e390537cdc2def5bf3bd3f7211666 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 19:16:55 +0900 Subject: [PATCH 086/109] Update hig/src/iosMain/kotlin/zone/ien/hig/CupertinoTextField.ios.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- hig/src/iosMain/kotlin/zone/ien/hig/CupertinoTextField.ios.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hig/src/iosMain/kotlin/zone/ien/hig/CupertinoTextField.ios.kt b/hig/src/iosMain/kotlin/zone/ien/hig/CupertinoTextField.ios.kt index 7a303e72..fcb516ad 100644 --- a/hig/src/iosMain/kotlin/zone/ien/hig/CupertinoTextField.ios.kt +++ b/hig/src/iosMain/kotlin/zone/ien/hig/CupertinoTextField.ios.kt @@ -32,4 +32,4 @@ import androidx.compose.ui.text.input.PlatformImeOptions @OptIn(ExperimentalComposeUiApi::class) internal actual fun KeyboardOptions.enableNativeInput() = this -// copy(platformImeOptions = PlatformImeOptions { usingNativeTextInput(true) }) \ No newline at end of file +// copy(platformImeOptions = PlatformImeOptions { usingNativeTextInput(true) }) // TODO: iOS 네이티브 입력 시 키보드 레이아웃 깨짐 이슈로 인해 비활성화됨 \ No newline at end of file From 50eaa016dbc57a21858c9a738ef57d79979a68be Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 19:17:07 +0900 Subject: [PATCH 087/109] Update example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../cupertino/CupertinoWidgetsScreen.kt | 123 ++++++------------ 1 file changed, 41 insertions(+), 82 deletions(-) diff --git a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt index 522daf2b..22acd299 100644 --- a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt +++ b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt @@ -1822,90 +1822,49 @@ private fun DropdownExample( val red = CupertinoColors.systemRed - if (isNative) { - CupertinoDropdownMenuNative( - expanded = dropdownVisible, - onDismissRequest = { dropdownVisible = false }, - backdrop = backdrop, - items = listOf( - CupertinoMenuItemData( - title = "Share", - onClick = { - dropdownVisible = false - }, - icon = AdaptiveIcons.painter( - material = { CupertinoIcons.Default.SquareAndArrowUp }, - cupertino = { "square.and.arrow.up" } - ) - ), - CupertinoMenuItemData( - title = "Add to Favorites", - enabled = false, - onClick = { - dropdownVisible = false - }, - icon = AdaptiveIcons.painter( - material = { CupertinoIcons.Default.Bookmark}, - cupertino = { "bookmark" } - ) - ), - CupertinoMenuItemData( - title = "Delete", - onClick = { - dropdownVisible = false - }, - isDestructive = true, - icon = AdaptiveIcons.painter( - material = { CupertinoIcons.Default.Trash}, - cupertino = { "trash" } - ) - ), - ), - sections = listOf( - CupertinoMenuSectionData( - title = "Menu", - options = HigMenuOptions.SingleSelection, - icon = AdaptiveIcons.painter( - material = { CupertinoIcons.Default.SquareAndArrowUp }, - cupertino = { "square.and.arrow.up" } - ), - items = listOf( - CupertinoMenuItemData( - title = "Share", - onClick = { - dropdownVisible = false - }, - icon = AdaptiveIcons.painter( - material = { CupertinoIcons.Default.SquareAndArrowUp }, - cupertino = { "square.and.arrow.up" } - ) - ), - CupertinoMenuItemData( - title = "Add to Favorites", - enabled = false, - onClick = { - dropdownVisible = false - }, - icon = AdaptiveIcons.painter( - material = { CupertinoIcons.Default.Bookmark}, - cupertino = { "bookmark" } - ) - ), - CupertinoMenuItemData( - title = "Delete", - onClick = { - dropdownVisible = false - }, - isDestructive = true, - icon = AdaptiveIcons.painter( - material = { CupertinoIcons.Default.Trash}, - cupertino = { "trash" } - ) - ), - ) - ) +CupertinoDropdownMenuNative( + expanded = dropdownVisible, + onDismissRequest = { dropdownVisible = false }, + backdrop = backdrop, + sections = listOf( + CupertinoMenuSectionData( + title = "Menu", + options = HigMenuOptions.SingleSelection, + icon = AdaptiveIcons.painter( + material = { CupertinoIcons.Default.SquareAndArrowUp }, + cupertino = { "square.and.arrow.up" } + ), + items = listOf( + CupertinoMenuItemData( + title = "Share", + onClick = { dropdownVisible = false }, + icon = AdaptiveIcons.painter( + material = { CupertinoIcons.Default.SquareAndArrowUp }, + cupertino = { "square.and.arrow.up" } + ) + ), + CupertinoMenuItemData( + title = "Add to Favorites", + enabled = false, + onClick = { dropdownVisible = false }, + icon = AdaptiveIcons.painter( + material = { CupertinoIcons.Default.Bookmark }, + cupertino = { "bookmark" } + ) + ), + CupertinoMenuItemData( + title = "Delete", + onClick = { dropdownVisible = false }, + isDestructive = true, + icon = AdaptiveIcons.painter( + material = { CupertinoIcons.Default.Trash }, + cupertino = { "trash" } ) ) + ) + ) + ) +) } else { CupertinoDropdownMenu( expanded = dropdownVisible, From 8b7faf1167c344c23c7bd0bcd4f7cbe1d13f9dfc Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 19:18:50 +0900 Subject: [PATCH 088/109] Update hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../zone/ien/hig/adaptive/AdaptiveButton.kt | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt index 6e48fdbe..ccaa6d75 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt @@ -244,18 +244,31 @@ fun AdaptiveTonalButton( @Stable -/** - * [CupertinoButton]에 대한 적응형 어댑테이션 클래스로, Cupertino 버튼 스타일의 다양한 속성을 관리합니다. - * - * @param colors [CupertinoLiquidButtonColors] - 버튼의 색상 설정 - * @param backdrop [LayerBackdrop] - 버튼의 레이어 배경 설정 - * @param isBackgroundAdaptive 배경이 적응형으로 처리되는지 여부 - * @see CupertinoLiquidButtonColors - * @see LayerBackdrop - * @see CupertinoButtonSize - * @see Shape - * @see PaddingValues - */ +@@ -247,12 +247,12 @@ +-/** +- * [CupertinoButton]에 대한 적응형 어댑테이션 클래스로, Cupertino 버튼 스타일의 다양한 속성을 관리합니다. +- * +- * @param colors [CupertinoLiquidButtonColors] - 버튼의 색상 설정 +- * @param backdrop [LayerBackdrop] - 버튼의 레이어 배경 설정 +- * @param isBackgroundAdaptive 배경이 적응형으로 처리되는지 여부 +- * @see CupertinoLiquidButtonColors +- * @see LayerBackdrop +- * @see CupertinoButtonSize +- * @see Shape +- * @see PaddingValues +- */ ++/** ++ * An adaptive adaptation class for [CupertinoButton] that manages various properties of the Cupertino button style. ++ * ++ * @param colors [CupertinoLiquidButtonColors] - The color configuration for the button ++ * @param backdrop [LayerBackdrop] - The layer backdrop configuration for the button ++ * @param isBackgroundAdaptive Whether the background is treated as adaptive ++ * @see CupertinoLiquidButtonColors ++ * @see LayerBackdrop ++ * @see CupertinoButtonSize ++ * @see Shape ++ * @see PaddingValues ++ */ class CupertinoButtonAdaptation internal constructor( colors: CupertinoLiquidButtonColors, backdrop: LayerBackdrop, From 1be93a6ca13232885a69c0d0162a68b82e3f9eef Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Tue, 12 May 2026 19:22:56 +0900 Subject: [PATCH 089/109] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(core):=20?= =?UTF-8?q?CupertinoWidgetsScreen=20=EB=B0=8F=20util.ios.kt=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CupertinoWidgetsScreen.kt에 필요한 Compose import문 정리 및 중복 제거 - util.ios.kt 파일 주석 번역 및 텍스트 개선 - 코드 일관성 및 가독성 향상을 위한 구조 개선 --- .../cupertino/CupertinoWidgetsScreen.kt | 87 ++++++++++--------- .../iosMain/kotlin/zone/ien/hig/util.ios.kt | 2 +- 2 files changed, 46 insertions(+), 43 deletions(-) diff --git a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt index 22acd299..e2ece301 100644 --- a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt +++ b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt @@ -72,6 +72,7 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Save +import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -869,7 +870,7 @@ private fun BottomBarSample( val content = listOf( "Profile" to Icons.Default.Delete, "Menu" to Icons.Default.Save, - "Profile" to AdaptiveIcons.Outlined.Person, + "Setting" to Icons.Default.Settings, ) if (isNative) { @@ -1822,49 +1823,51 @@ private fun DropdownExample( val red = CupertinoColors.systemRed -CupertinoDropdownMenuNative( - expanded = dropdownVisible, - onDismissRequest = { dropdownVisible = false }, - backdrop = backdrop, - sections = listOf( - CupertinoMenuSectionData( - title = "Menu", - options = HigMenuOptions.SingleSelection, - icon = AdaptiveIcons.painter( - material = { CupertinoIcons.Default.SquareAndArrowUp }, - cupertino = { "square.and.arrow.up" } - ), - items = listOf( - CupertinoMenuItemData( - title = "Share", - onClick = { dropdownVisible = false }, - icon = AdaptiveIcons.painter( - material = { CupertinoIcons.Default.SquareAndArrowUp }, - cupertino = { "square.and.arrow.up" } - ) - ), - CupertinoMenuItemData( - title = "Add to Favorites", - enabled = false, - onClick = { dropdownVisible = false }, - icon = AdaptiveIcons.painter( - material = { CupertinoIcons.Default.Bookmark }, - cupertino = { "bookmark" } - ) - ), - CupertinoMenuItemData( - title = "Delete", - onClick = { dropdownVisible = false }, - isDestructive = true, - icon = AdaptiveIcons.painter( - material = { CupertinoIcons.Default.Trash }, - cupertino = { "trash" } + if (isNative) { + + CupertinoDropdownMenuNative( + expanded = dropdownVisible, + onDismissRequest = { dropdownVisible = false }, + backdrop = backdrop, + sections = listOf( + CupertinoMenuSectionData( + title = "Menu", + options = HigMenuOptions.SingleSelection, + icon = AdaptiveIcons.painter( + material = { CupertinoIcons.Default.SquareAndArrowUp }, + cupertino = { "square.and.arrow.up" } + ), + items = listOf( + CupertinoMenuItemData( + title = "Share", + onClick = { dropdownVisible = false }, + icon = AdaptiveIcons.painter( + material = { CupertinoIcons.Default.SquareAndArrowUp }, + cupertino = { "square.and.arrow.up" } + ) + ), + CupertinoMenuItemData( + title = "Add to Favorites", + enabled = false, + onClick = { dropdownVisible = false }, + icon = AdaptiveIcons.painter( + material = { CupertinoIcons.Default.Bookmark }, + cupertino = { "bookmark" } + ) + ), + CupertinoMenuItemData( + title = "Delete", + onClick = { dropdownVisible = false }, + isDestructive = true, + icon = AdaptiveIcons.painter( + material = { CupertinoIcons.Default.Trash }, + cupertino = { "trash" } + ) + ) + ) + ) ) ) - ) - ) - ) -) } else { CupertinoDropdownMenu( expanded = dropdownVisible, diff --git a/hig-native/src/iosMain/kotlin/zone/ien/hig/util.ios.kt b/hig-native/src/iosMain/kotlin/zone/ien/hig/util.ios.kt index a7db95af..8566886d 100644 --- a/hig-native/src/iosMain/kotlin/zone/ien/hig/util.ios.kt +++ b/hig-native/src/iosMain/kotlin/zone/ien/hig/util.ios.kt @@ -97,7 +97,7 @@ fun Painter.toImageBitmap( canvas = canvas, size = Size(width.toFloat(), height.toFloat()) ) { - // 흰색으로 강제 렌더링 → AlwaysTemplate이 tint를 올바르게 곱할 수 있음 + // Force render as white -> so AlwaysTemplate can multiply tint correctly with(this) { drawContext.canvas.let { c -> val paint = androidx.compose.ui.graphics.Paint().apply { From 816a4ea399268b6c207d842933b99fccf658001c Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 13 May 2026 01:19:34 +0900 Subject: [PATCH 090/109] Update hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../zone/ien/hig/adaptive/AdaptiveCheckbox.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt index bf110d4a..d55e5a99 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt @@ -89,17 +89,17 @@ fun AdaptiveCheckbox( } /** - * 적응형 삼중 상태 체크박스 컴포넌트로, Material Design과 Cupertino 테마에 따라 다른 체크박스 스타일을 제공합니다. + * An adaptive tri-state checkbox component that provides different checkbox styles based on Material Design and Cupertino themes. * - * [AdaptiveTriStateCheckbox]는 Material Design의 [TriStateCheckbox] 컴포넌트와 Cupertino의 [CupertinoTriStateCheckBox] 컴포넌트를 사용하여 - * 운영체제별로 적절한 삼중 상태 체크박스를 제공합니다. + * [AdaptiveTriStateCheckbox] uses the Material Design [TriStateCheckbox] component and the Cupertino [CupertinoTriStateCheckBox] component + * to provide an appropriate tri-state checkbox for each operating system. * - * @param state 체크박스의 상태 (Unchecked, Checked, Indeterminate) - * @param onClick 체크박스 클릭 시 호출되는 함수 - * @param modifier 요소에 적용할 Modifier - * @param enabled 체크박스가 활성화되어 있는지 여부 - * @param interactionSource 상호작용 소스 - * @param adaptation [CupertinoCheckBoxAdaptation]와 [MaterialCheckBoxAdaptation]에 대한 사용자 정의 설정 함수 + * @param state The state of the checkbox (Unchecked, Checked, Indeterminate) + * @param onClick The function to be called when the checkbox is clicked + * @param modifier The modifier to be applied to the element + * @param enabled Whether the checkbox is enabled + * @param interactionSource The interaction source + * @param adaptation Custom settings function for [CupertinoCheckBoxAdaptation] and [MaterialCheckBoxAdaptation] * @see AdaptiveWidget * @see CupertinoTriStateCheckBox * @see TriStateCheckbox From 4376e39bc5a463600f78ae4eff515434cdeff641 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 13 May 2026 01:20:47 +0900 Subject: [PATCH 091/109] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(core):=20?= =?UTF-8?q?AdaptiveButton.kt=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CupertinoButtonAdaptation 및 MaterialButtonAdaptation 클래스의 주석 및 파라미터 정리 - ButtonAdaptation 클래스 내 컴포저블 함수 리팩토링 - 버튼 타입별 색상 및 스타일 적용 로직 개선 --- .../zone/ien/hig/adaptive/AdaptiveButton.kt | 29 +++++-------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt index ccaa6d75..53659b6e 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveButton.kt @@ -244,20 +244,7 @@ fun AdaptiveTonalButton( @Stable -@@ -247,12 +247,12 @@ --/** -- * [CupertinoButton]에 대한 적응형 어댑테이션 클래스로, Cupertino 버튼 스타일의 다양한 속성을 관리합니다. -- * -- * @param colors [CupertinoLiquidButtonColors] - 버튼의 색상 설정 -- * @param backdrop [LayerBackdrop] - 버튼의 레이어 배경 설정 -- * @param isBackgroundAdaptive 배경이 적응형으로 처리되는지 여부 -- * @see CupertinoLiquidButtonColors -- * @see LayerBackdrop -- * @see CupertinoButtonSize -- * @see Shape -- * @see PaddingValues -- */ -+/** +/** + * An adaptive adaptation class for [CupertinoButton] that manages various properties of the Cupertino button style. + * + * @param colors [CupertinoLiquidButtonColors] - The color configuration for the button @@ -282,21 +269,21 @@ class CupertinoButtonAdaptation internal constructor( var contentPadding: PaddingValues? by mutableStateOf(null) } -@Stable /** - * [MaterialButton]에 대한 적응형 어댑테이션 클래스로, Material 버튼 스타일의 다양한 속성을 관리합니다. + * An adaptive adaptation class for [MaterialButton] that manages various properties of the Material button style. * - * @param colors [ButtonColors] - 버튼의 색상 설정 - * @param elevation [ButtonElevation] - 버튼의 그림자 효과 - * @param shape [Shape] - 버튼의 모양 - * @param contentPadding [PaddingValues] - 버튼 내용의 패딩 - * @param border [BorderStroke] - 버튼 테두리 스타일 + * @param colors [ButtonColors] - The color configuration for the button + * @param elevation [ButtonElevation] - The shadow effect for the button + * @param shape [Shape] - The shape of the button + * @param contentPadding [PaddingValues] - The padding for the button content + * @param border [BorderStroke] - The border style for the button * @see ButtonColors * @see ButtonElevation * @see Shape * @see PaddingValues * @see BorderStroke */ +@Stable class MaterialButtonAdaptation internal constructor( colors: ButtonColors, elevation: ButtonElevation?, From 8632884d0d725c1a928eb9987d84d9ebe4a92a3d Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 13 May 2026 01:25:45 +0900 Subject: [PATCH 092/109] Update hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt index ff51be45..3b295e68 100644 --- a/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt +++ b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt @@ -212,5 +212,5 @@ private fun isPerformPrimaryActionAvailable(): Boolean { val systemVersion = UIDevice.currentDevice.systemVersion val major = systemVersion.split(".").firstOrNull()?.toIntOrNull() ?: 0 val minor = systemVersion.split(".").getOrNull(1)?.toIntOrNull() ?: 0 - return major >= 17 && minor >= 4 + return major > 17 || (major == 17 && minor >= 4) } From 5e6892dd2ccb439c3acac2dbe11e4dd3e129ecd1 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 13 May 2026 01:26:05 +0900 Subject: [PATCH 093/109] Update hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt index f288a920..8270093f 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt @@ -602,7 +602,7 @@ private fun DropdownMenuContent( val minWidth = with(density) { width.roundToPx() } val itemConstraints = constraints.copy( minWidth = minWidth, - maxWidth = minWidth + maxWidth = constraints.maxWidth ) val itemPlaceables = subcompose(CupertinoDropdownMenuSlots.Item, content) .fastMap { it.measure(itemConstraints) } From 2f7aab56784868750ccdfbf9550a0dfa1e606b86 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 13 May 2026 01:28:29 +0900 Subject: [PATCH 094/109] Update hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveNavigationBar.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../kotlin/zone/ien/hig/adaptive/AdaptiveNavigationBar.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveNavigationBar.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveNavigationBar.kt index e245e897..19b73581 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveNavigationBar.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveNavigationBar.kt @@ -172,10 +172,7 @@ fun AdaptiveNavigationBarNative( onClick = item.onClick, icon = { Icon( - painter = - if (selected && item.selectedIcon != null) item.selectedIcon!! - else item.icon - , + painter = (if (selected) item.selectedIcon else null) ?: item.icon, contentDescription = item.label, ) }, From 38574483ba052df52bf881bfc05a2fe6fa96cf17 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 13 May 2026 01:28:42 +0900 Subject: [PATCH 095/109] Update hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt index b465b255..c6df525e 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt @@ -147,8 +147,11 @@ fun CupertinoNavigationBar( // NavBar 전체 너비 = padding*2 + itemWidth*n + gap*(n-1) // → itemWidth = (availableWidth - padding*2 - gap*(n-1)) / n val availableWidthPx = constraints.maxWidth.toFloat() - val calculatedItemWidthPx = + val calculatedItemWidthPx = if (tabsCount > 0) { (availableWidthPx - paddingPx * 2f - gapPx * (tabsCount - 1)) / tabsCount + } else { + 0f + } // 균등 분할 너비가 최솟값(90dp) 이상이면 고정, 미만이면 균등 분할 val itemWidthPx = if (calculatedItemWidthPx >= minItemWidthPx) { From 01e0b75e1c534c81ae1292e8a15b28684946639d Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 13 May 2026 01:29:02 +0900 Subject: [PATCH 096/109] Update hig-native/src/iosMain/kotlin/zone/ien/hig/util.ios.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- hig-native/src/iosMain/kotlin/zone/ien/hig/util.ios.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hig-native/src/iosMain/kotlin/zone/ien/hig/util.ios.kt b/hig-native/src/iosMain/kotlin/zone/ien/hig/util.ios.kt index 8566886d..7b5dd93d 100644 --- a/hig-native/src/iosMain/kotlin/zone/ien/hig/util.ios.kt +++ b/hig-native/src/iosMain/kotlin/zone/ien/hig/util.ios.kt @@ -74,7 +74,7 @@ internal fun UIView.applyTheme(dark: Boolean) { fun ImageBitmap.toUIImage(): UIImage { val skiaImage = Image.makeFromBitmap(this.asSkiaBitmap()) val pngData = skiaImage.encodeToData(EncodedImageFormat.PNG) - ?: throw Exception("Failed to encode ImageBitmap to PNG") + ?: error("Failed to encode ImageBitmap to PNG") val pngBytes = pngData.bytes val nsData = pngBytes.usePinned { pinned -> NSData.dataWithBytes(pinned.addressOf(0), pngBytes.size.toULong()) From 1f3ffa4991efaded82624f3744c863d75b891091 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 13 May 2026 12:12:22 +0900 Subject: [PATCH 097/109] Update hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt index 9fe38358..b774303b 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt @@ -188,12 +188,12 @@ class MaterialThemeSpec( val typography: MaterialTypography = MaterialTypography(), ) { /** - * MaterialThemeSpec 인스턴스를 복사하여 새로운 인스턴스를 생성합니다. + * Creates a copy of this MaterialThemeSpec with the specified values replaced. * - * @param colorScheme 새로운 컬러 스키마 - * @param shapes 새로운 모양 - * @param typography 새로운 타이포그래피 - * @return 복사된 MaterialThemeSpec 인스턴스 + * @param colorScheme the new color scheme + * @param shapes the new shapes + * @param typography the new typography + * @return a copy of this MaterialThemeSpec */ fun copy( colorScheme: MaterialColorScheme = this.colorScheme, From 48cf4d236dd1d03e65ce9306e27fb8edd0dd8186 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 13 May 2026 12:13:12 +0900 Subject: [PATCH 098/109] Update hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt index d55e5a99..f2b2bc7f 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt @@ -142,9 +142,9 @@ fun AdaptiveTriStateCheckbox( @Stable /** - * Material Design 체크박스에 대한 적응형 어댑테이션 클래스로, 체크박스 색상 속성을 관리합니다. + * An adaptive adaptation class for Material Design checkboxes that manages checkbox color properties. * - * @param colors [CheckboxColors] - 체크박스의 색상 설정 + * @param colors [CheckboxColors] - The color configuration for the checkbox * @see CheckboxColors * @see CheckboxDefaults */ From a490e995468dee84e6a70b599cb5d6bfffd48e8b Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 13 May 2026 12:13:29 +0900 Subject: [PATCH 099/109] Update hig-native/src/iosMain/kotlin/zone/ien/hig/navigation/TabBarManager.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../iosMain/kotlin/zone/ien/hig/navigation/TabBarManager.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hig-native/src/iosMain/kotlin/zone/ien/hig/navigation/TabBarManager.kt b/hig-native/src/iosMain/kotlin/zone/ien/hig/navigation/TabBarManager.kt index 981c7ae1..e521cf98 100644 --- a/hig-native/src/iosMain/kotlin/zone/ien/hig/navigation/TabBarManager.kt +++ b/hig-native/src/iosMain/kotlin/zone/ien/hig/navigation/TabBarManager.kt @@ -10,8 +10,7 @@ import zone.ien.hig.resized import zone.ien.hig.toUIImage /** - * Calf code - * com.mohamedrejeb.calf.ui.navigation + * Reference: com.mohamedrejeb.calf.ui.navigation */ /** From 136f2ef2a5e4a52a173ddba8e86f36323e10c0a8 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 13 May 2026 12:14:27 +0900 Subject: [PATCH 100/109] Update hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt index c6df525e..da16950b 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt @@ -162,7 +162,7 @@ fun CupertinoNavigationBar( val itemWidthDp: Dp = with(density) { itemWidthPx.toDp() } // NavBar 전체 너비 = padding*2 + itemWidth*n + gap*(n-1) - val rowWidth = paddingPx * 2f + itemWidthPx * tabsCount + gapPx * (tabsCount - 1) + val rowWidth = (paddingPx * 2f + itemWidthPx * tabsCount + gapPx * (tabsCount - 1)).coerceAtLeast(1f) fun itemLeftX(index: Float): Float = paddingPx + (itemWidthPx + gapPx) * index fun itemCenterX(index: Float): Float = itemLeftX(index) + itemWidthPx / 2f From 533dc9fdc78ebc100e2bf58d69176d83a1397b9d Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 13 May 2026 12:14:50 +0900 Subject: [PATCH 101/109] Update hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt index 3b295e68..35d15cd6 100644 --- a/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt +++ b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt @@ -94,7 +94,7 @@ actual fun CupertinoDropdownMenuNative( var btnW by remember { mutableStateOf(0.0) } var btnH by remember { mutableStateOf(0.0) } - // UIButton을 뷰 계층 최하단에 삽입 (터치 가로채기 방지) + // Insert UIButton at the bottom of the view hierarchy (to prevent touch interception) DisposableEffect(viewController) { val button = UIButton.buttonWithType(UIButtonTypePlain).apply { backgroundColor = UIColor.clearColor From e6d73a50042dbcbbe7fe9b6eabc926883220ee1d Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 13 May 2026 12:14:59 +0900 Subject: [PATCH 102/109] Update hig/src/iosMain/kotlin/zone/ien/hig/CupertinoTextField.ios.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- hig/src/iosMain/kotlin/zone/ien/hig/CupertinoTextField.ios.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hig/src/iosMain/kotlin/zone/ien/hig/CupertinoTextField.ios.kt b/hig/src/iosMain/kotlin/zone/ien/hig/CupertinoTextField.ios.kt index fcb516ad..dc8ccd89 100644 --- a/hig/src/iosMain/kotlin/zone/ien/hig/CupertinoTextField.ios.kt +++ b/hig/src/iosMain/kotlin/zone/ien/hig/CupertinoTextField.ios.kt @@ -32,4 +32,4 @@ import androidx.compose.ui.text.input.PlatformImeOptions @OptIn(ExperimentalComposeUiApi::class) internal actual fun KeyboardOptions.enableNativeInput() = this -// copy(platformImeOptions = PlatformImeOptions { usingNativeTextInput(true) }) // TODO: iOS 네이티브 입력 시 키보드 레이아웃 깨짐 이슈로 인해 비활성화됨 \ No newline at end of file +// copy(platformImeOptions = PlatformImeOptions { usingNativeTextInput(true) }) // TODO: Disabled due to keyboard layout breaking issues when using native input on iOS \ No newline at end of file From 43d4d8b6bcb4088f40aa0830b1d6fb97f46d348d Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 13 May 2026 19:14:39 +0900 Subject: [PATCH 103/109] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(core):=20?= =?UTF-8?q?Cupertino=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81=20=EB=B0=8F=20=EB=B9=8C=EB=93=9C=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CupertinoNavigationBar.kt 파일의 패키지 선언 및 주석 추가 - CupertinoWidgetsScreen.kt에서 Compose 의존성 정렬 및 애니메이션 컴포넌트 추가 - 빌드 파일에서 ExperimentalTime 옵션 변경 및 iOS native 컴포넌트 의존성 정리 - multiplatform-module-convention.gradle.kts 및 build.gradle.kts 파일의 컴파일러 옵션 일관성 유지 --- ...multiplatform-module-convention.gradle.kts | 2 +- example/composeApp/build.gradle.kts | 2 +- .../cupertino/CupertinoWidgetsScreen.kt | 20 ++-- .../hig/CupertinoDropdownMenuNative.ios.kt | 100 +++++++++--------- .../zone/ien/hig/CupertinoNavigationBar.kt | 33 +++--- 5 files changed, 86 insertions(+), 71 deletions(-) diff --git a/buildSrc/src/main/kotlin/multiplatform-module-convention.gradle.kts b/buildSrc/src/main/kotlin/multiplatform-module-convention.gradle.kts index eb8f3f14..45d7c193 100644 --- a/buildSrc/src/main/kotlin/multiplatform-module-convention.gradle.kts +++ b/buildSrc/src/main/kotlin/multiplatform-module-convention.gradle.kts @@ -131,5 +131,5 @@ kotlin { } } - compilerOptions.freeCompilerArgs.add("-Xopt-in=kotlin.time.ExperimentalTime") + compilerOptions.freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") } \ No newline at end of file diff --git a/example/composeApp/build.gradle.kts b/example/composeApp/build.gradle.kts index e273ab70..dcf7046e 100644 --- a/example/composeApp/build.gradle.kts +++ b/example/composeApp/build.gradle.kts @@ -107,7 +107,7 @@ kotlin { } } - compilerOptions.freeCompilerArgs.add("-Xopt-in=kotlin.time.ExperimentalTime") + compilerOptions.freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") } dependencies { diff --git a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt index e2ece301..b5390244 100644 --- a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt +++ b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt @@ -43,6 +43,7 @@ import RootDetails import RootRoute import RootUiState import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background @@ -1813,12 +1814,16 @@ private fun DropdownExample( Spacer(Modifier.weight(1f)) //Menu bar should be in the box with anchor to align correctly Box { - CupertinoLiquidButton( - colors = CupertinoLiquidButtonDefaults.glassProminentButtonColors(), - backdrop = layerBackdrop, - onClick = { dropdownVisible = !dropdownVisible } + androidx.compose.animation.AnimatedVisibility( + visible = !dropdownVisible ) { - CupertinoText("Menu") + CupertinoLiquidButton( + colors = CupertinoLiquidButtonDefaults.glassProminentButtonColors(), + backdrop = layerBackdrop, + onClick = { dropdownVisible = !dropdownVisible } + ) { + CupertinoText("Menu") + } } val red = CupertinoColors.systemRed @@ -1827,7 +1832,10 @@ private fun DropdownExample( CupertinoDropdownMenuNative( expanded = dropdownVisible, - onDismissRequest = { dropdownVisible = false }, + onDismissRequest = { + println("onDismissRequest on Screen"); + dropdownVisible = false + }, backdrop = backdrop, sections = listOf( CupertinoMenuSectionData( diff --git a/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt index 3b295e68..a8b1bbc5 100644 --- a/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt +++ b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt @@ -6,15 +6,11 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.layout.positionInWindow +import androidx.compose.ui.layout.positionInRoot import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.uikit.LocalUIViewController import androidx.compose.ui.unit.Dp @@ -22,28 +18,24 @@ import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.window.PopupProperties import com.kyant.backdrop.Backdrop import kotlinx.cinterop.BetaInteropApi +import kotlinx.cinterop.CValue import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.cinterop.useContents -import platform.CoreGraphics.CGPointMake +import platform.CoreGraphics.CGPoint import platform.CoreGraphics.CGRectMake -import platform.Foundation.NSAttributedString -import platform.Foundation.NSMakeRange -import platform.Foundation.NSMutableAttributedString -import platform.Foundation.NSMutableAttributedStringMeta -import platform.Foundation.create -import platform.Foundation.setAttributedString -import platform.UIKit.NSForegroundColorAttributeName import platform.UIKit.UIAction import platform.UIKit.UIButton import platform.UIKit.UIButtonTypePlain import platform.UIKit.UIColor -import platform.UIKit.UIContextMenuConfigurationElementOrder +import platform.UIKit.UIContextMenuConfiguration import platform.UIKit.UIContextMenuConfigurationElementOrderFixed +import platform.UIKit.UIContextMenuInteraction +import platform.UIKit.UIContextMenuInteractionAnimatingProtocol +import platform.UIKit.UIContextMenuInteractionDelegateProtocol import platform.UIKit.UIControlStateNormal import platform.UIKit.UIDevice import platform.UIKit.UIEvent import platform.UIKit.UIGestureRecognizer -import platform.UIKit.UIImage import platform.UIKit.UIMenu import platform.UIKit.UIMenuElement import platform.UIKit.UIMenuElementAttributesDestructive @@ -52,8 +44,7 @@ import platform.UIKit.UIMenuOptionsDestructive import platform.UIKit.UIMenuOptionsDisplayAsPalette import platform.UIKit.UIMenuOptionsDisplayInline import platform.UIKit.UIMenuOptionsSingleSelection -import platform.UIKit.UIMenuOptionsVar -import platform.UIKit.secondaryLabelColor +import platform.UIKit.addInteraction import platform.UIKit.touchesBegan import platform.darwin.NSObject @@ -89,18 +80,14 @@ actual fun CupertinoDropdownMenuNative( delegate.sections = sections delegate.onDismissRequest = onDismissRequest - var posX by remember { mutableStateOf(0.0) } - var posY by remember { mutableStateOf(0.0) } - var btnW by remember { mutableStateOf(0.0) } - var btnH by remember { mutableStateOf(0.0) } - - // UIButton을 뷰 계층 최하단에 삽입 (터치 가로채기 방지) + // UIButton을 뷰 계층 최하단에 삽입 DisposableEffect(viewController) { val button = UIButton.buttonWithType(UIButtonTypePlain).apply { backgroundColor = UIColor.clearColor setTitle("", forState = UIControlStateNormal) showsMenuAsPrimaryAction = true preferredMenuElementOrder = UIContextMenuConfigurationElementOrderFixed + addInteraction(UIContextMenuInteraction(delegate)) } viewController.view.insertSubview(button, atIndex = 0) delegate.button = button @@ -111,11 +98,6 @@ actual fun CupertinoDropdownMenuNative( } } - // 버튼 위치/크기 동기화 - LaunchedEffect(posX, posY, btnW, btnH) { - delegate.button?.setFrame(CGRectMake(posX, posY, btnW, btnH)) - } - // 아이템/섹션 변경 시 메뉴 갱신 LaunchedEffect(items, sections) { delegate.button?.menu = delegate.buildMenu() @@ -126,6 +108,7 @@ actual fun CupertinoDropdownMenuNative( val button = delegate.button ?: return@LaunchedEffect if (!expanded) return@LaunchedEffect + delegate.isMenuVisible = true // 메뉴 표시 중 프레임 업데이트 방지 if (isPerformPrimaryActionAvailable()) { button.performPrimaryAction() } else { @@ -138,41 +121,56 @@ actual fun CupertinoDropdownMenuNative( } gesture?.touchesBegan(emptySet(), UIEvent()) } - // 메뉴가 표시된 직후 Compose 측 상태를 닫힘으로 리셋 - onDismissRequest() } - // Compose 레이아웃으로 버튼 위치 추적 (투명 Box) Box( modifier = modifier .onGloballyPositioned { coords -> - // positionInWindow()는 Compose 윈도우 기준 - // → UIKit view 좌표계로 변환하려면 rootView 기준 offset을 빼야 함 - val rootView = viewController.view - val windowOrigin = rootView.window - ?.convertPoint( - point = CGPointMake(0.0, 0.0), - fromView = rootView, - ) - - val pos = coords.positionInWindow() - val offsetY = windowOrigin?.useContents { y } ?: 0.0 - - posX = pos.x.toDouble() / density.density - // rootView가 window 기준으로 얼마나 내려와 있는지 보정 - posY = (pos.y.toDouble() / density.density) - offsetY - btnW = coords.size.width.toDouble() / density.density - btnH = coords.size.height.toDouble() / density.density + if (delegate.isMenuVisible) return@onGloballyPositioned + + val d = density.density + val pos = coords.positionInRoot() + + val newX = kotlin.math.round(pos.x.toDouble() / d) + val newY = kotlin.math.round(pos.y.toDouble() / d) + val newW = kotlin.math.round(coords.size.width.toDouble() / d) + val newH = kotlin.math.round(coords.size.height.toDouble() / d) + + delegate.button?.let { btn -> + btn.frame.useContents { + if (kotlin.math.abs(origin.x - newX) >= 1.0 || + kotlin.math.abs(origin.y - newY) >= 1.0 || + kotlin.math.abs(size.width - newW) >= 1.0 || + kotlin.math.abs(size.height - newH) >= 1.0) { + btn.setFrame(CGRectMake(newX, newY, newW, newH)) + } + } + } } ) } @OptIn(ExperimentalForeignApi::class) -internal class CupertinoDropdownMenuDelegate : NSObject() { +internal class CupertinoDropdownMenuDelegate : NSObject(), UIContextMenuInteractionDelegateProtocol { var items: List = emptyList() var sections: List = emptyList() var onDismissRequest: () -> Unit = {} var button: UIButton? = null + var isMenuVisible: Boolean = false + + override fun contextMenuInteraction( + interaction: UIContextMenuInteraction, + configurationForMenuAtLocation: CValue + ): UIContextMenuConfiguration? = null + + override fun contextMenuInteraction( + interaction: UIContextMenuInteraction, + willEndForConfiguration: UIContextMenuConfiguration, + animator: UIContextMenuInteractionAnimatingProtocol? + ) { + isMenuVisible = false + onDismissRequest() + } fun buildMenu(): UIMenu { val topActions: List = items.map { it.toUIAction() } @@ -202,8 +200,8 @@ private fun CupertinoMenuItemData.toUIAction(): UIAction { ) { _ -> onClick() } var attributes: ULong = 0u - if (isDestructive) attributes = attributes or UIMenuElementAttributesDestructive - if (!enabled) attributes = attributes or UIMenuElementAttributesDisabled + if (isDestructive) attributes = attributes or platform.UIKit.UIMenuElementAttributesDestructive + if (!enabled) attributes = attributes or platform.UIKit.UIMenuElementAttributesDisabled action.attributes = attributes return action } diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt index da16950b..72998ca8 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt @@ -16,6 +16,9 @@ * limitations under the License. */ +/** + * Contains the implementation of the Cupertino navigation bar. + */ package zone.ien.hig import androidx.compose.animation.core.Animatable @@ -101,13 +104,19 @@ import kotlin.math.sign private val NavBarPadding = 4.dp private val NavBarItemGap = 0.dp -private val NavBarItemMinWidth = 90.dp // 아이템이 소수일 때의 고정 너비 +private val NavBarItemMinWidth = 90.dp // Fixed width when items are few /** - * Cupertino 스타일의 리퀴드 네비게이션 바입니다. + * Cupertino bottom navigation tab bar. + * + * [CupertinoNavigationBarItem]s should be used as navigation bar content. * - * @param tabsCount 네비게이션 아이템의 총 개수입니다. [content] 내의 아이템 개수와 일치해야 합니다. - * @param content 네비게이션 아이템들을 포함하는 블록입니다. + * Note: navigation bar itself does not produce cupertino thin material glass effect. + * This effect works only inside [CupertinoScaffold], [CupertinoBottomSheetScaffold], [CupertinoBottomSheetContent]. + * To achieve this effect with custom bottom bar use [cupertinoTranslucentTopBarColor] + * function that will communicate with scaffold and return either + * [Color.Transparent] if color was successfully applied to scaffold (and top bar itself + * should be transparent) or passed color if scaffold wasn't found. */ @Composable @ExperimentalCupertinoApi @@ -133,18 +142,18 @@ fun CupertinoNavigationBar( .wrapContentWidth() .windowInsetsPadding(windowInsets) ) { - // BoxWithConstraints로 windowInsets 적용 후 실제 가용 너비를 측정 - BoxWithConstraints( - contentAlignment = Alignment.CenterStart, - ) { + // Calculate actual available width after applying windowInsets + BoxWithConstraints( + contentAlignment = Alignment.CenterStart, + ) { val density = LocalDensity.current val paddingPx = with(density) { NavBarPadding.toPx() } val gapPx = with(density) { NavBarItemGap.toPx() } val minItemWidthPx = with(density) { NavBarItemMinWidth.toPx() } - // 가용 너비 기준으로 균등 분할 시 아이템 너비 계산 - // NavBar 전체 너비 = padding*2 + itemWidth*n + gap*(n-1) + // Calculate item width based on available width + // NavBar total width = padding*2 + itemWidth*n + gap*(n-1) // → itemWidth = (availableWidth - padding*2 - gap*(n-1)) / n val availableWidthPx = constraints.maxWidth.toFloat() val calculatedItemWidthPx = if (tabsCount > 0) { @@ -153,7 +162,7 @@ fun CupertinoNavigationBar( 0f } - // 균등 분할 너비가 최솟값(90dp) 이상이면 고정, 미만이면 균등 분할 + // If evenly distributed width is greater than or equal to minimum width (90dp), use fixed width, otherwise use evenly distributed width val itemWidthPx = if (calculatedItemWidthPx >= minItemWidthPx) { minItemWidthPx } else { @@ -161,7 +170,7 @@ fun CupertinoNavigationBar( } val itemWidthDp: Dp = with(density) { itemWidthPx.toDp() } - // NavBar 전체 너비 = padding*2 + itemWidth*n + gap*(n-1) + // NavBar total width = padding*2 + itemWidth*n + gap*(n-1) val rowWidth = (paddingPx * 2f + itemWidthPx * tabsCount + gapPx * (tabsCount - 1)).coerceAtLeast(1f) fun itemLeftX(index: Float): Float = paddingPx + (itemWidthPx + gapPx) * index From dea9029e2efd424848f955e044573d505519af46 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 13 May 2026 21:43:28 +0900 Subject: [PATCH 104/109] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(core):=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20?= =?UTF-8?q?=EB=B0=8F=20=EB=AC=B8=EC=84=9C=ED=99=94=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Adaptive 컴포넌트 문서화 내용 영문으로 변경 - 네비게이션 그래프 및 테스트 화면에서 주석 및 텍스트 개선 - Cupertino 컴포넌트 관련 어댑테이션 클래스 주석 정리 및 명확화 --- .../commonMain/kotlin/RootNavigationGraph.kt | 2 +- .../src/commonMain/kotlin/test/TestScreen.kt | 22 +++++----- .../zone/ien/hig/adaptive/AdaptiveCheckbox.kt | 6 +-- .../zone/ien/hig/adaptive/AdaptiveDivider.kt | 41 ++++++++++--------- .../zone/ien/hig/adaptive/AdaptiveScaffold.kt | 12 +++--- .../zone/ien/hig/adaptive/AdaptiveSwitch.kt | 12 +++--- .../zone/ien/hig/adaptive/AdaptiveTheme.kt | 18 ++++---- .../ien/hig/adaptive/AdaptiveTopAppBar.kt | 16 ++++---- .../hig/CupertinoDropdownMenuNative.ios.kt | 8 ++-- .../zone/ien/hig/CupertinoDropdownMenu.kt | 6 +-- .../zone/ien/hig/CupertinoLiquidButton.kt | 2 +- .../zone/ien/hig/CupertinoNavigationBar.kt | 16 ++++---- .../kotlin/zone/ien/hig/CupertinoTopBars.kt | 28 ++++++------- .../kotlin/zone/ien/hig/Luminance.skiko.kt | 4 +- 14 files changed, 97 insertions(+), 96 deletions(-) diff --git a/example/composeApp/src/commonMain/kotlin/RootNavigationGraph.kt b/example/composeApp/src/commonMain/kotlin/RootNavigationGraph.kt index e5b90c90..eaefe741 100644 --- a/example/composeApp/src/commonMain/kotlin/RootNavigationGraph.kt +++ b/example/composeApp/src/commonMain/kotlin/RootNavigationGraph.kt @@ -45,7 +45,7 @@ fun RootNavigationGraph( modifier = modifier, entryDecorators = listOf( rememberSaveableStateHolderNavEntryDecorator(), - rememberViewModelStoreNavEntryDecorator() // 뷰모델 꺼짐 보장 + rememberViewModelStoreNavEntryDecorator() // ViewModel lifecycle guarantee ), entryProvider = entryProvider { entry { diff --git a/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt b/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt index ceb02bd5..6f82758d 100644 --- a/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt +++ b/example/composeApp/src/commonMain/kotlin/test/TestScreen.kt @@ -171,12 +171,12 @@ fun TestScreen( CupertinoNavigationTitle( subtitle = { Text( - text = "87개의 메모" + text = "87 memos" ) } ) { Text( - text = "메모", + text = "Memo", ) } @@ -232,31 +232,31 @@ fun TestScreen( backdrop = rememberDefaultBackdrop(), items = listOf( CupertinoMenuItemData( - title = "메시지 선택", + title = "Message Select", onClick = { expanded = false coroutineScope.launch { - snackbarState.showSnackbar("메시지 선택") + snackbarState.showSnackbar("Message Select") } }, icon = rememberVectorPainter(CupertinoIcons.Default.CheckmarkCircle) ), CupertinoMenuItemData( - title = "고정 편집", + title = "Pin Edit", onClick = { expanded = false coroutineScope.launch { - snackbarState.showSnackbar("고정 편집") + snackbarState.showSnackbar("Pin Edit") } }, icon = rememberVectorPainter(CupertinoIcons.Default.Pin) ), CupertinoMenuItemData( - title = "이름 및 사진 설정", + title = "Name and Photo Setting", onClick = { expanded = false coroutineScope.launch { - snackbarState.showSnackbar("이름 및 사진 설정") + snackbarState.showSnackbar("Name and Photo Setting") } }, icon = rememberVectorPainter(CupertinoIcons.Default.PersonCropCircle) @@ -283,7 +283,7 @@ fun TestScreen( ) } ) { - CupertinoText("메시지 선택") + CupertinoText("Message Select") } MenuAction( onClick = { expanded = false }, @@ -294,7 +294,7 @@ fun TestScreen( ) } ) { - CupertinoText("고정 편집") + CupertinoText("Pin Edit") } MenuAction( onClick = { expanded = false }, @@ -305,7 +305,7 @@ fun TestScreen( ) } ) { - CupertinoText("이름 및 사진 설정") + CupertinoText("Name and Photo Setting") } } // MenuDivider() diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt index f2b2bc7f..cd3ee956 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveCheckbox.kt @@ -156,9 +156,9 @@ class MaterialCheckBoxAdaptation( @Stable /** - * Cupertino 체크박스에 대한 적응형 어댑테이션 클래스로, 체크박스 색상 속성을 관리합니다. + * Cupertino checkbox adaptation class that manages checkbox color properties. * - * @param colors [CupertinoCheckboxColors] - 체크박스의 색상 설정 + * @param colors [CupertinoCheckboxColors] - Checkbox color configuration * @see CupertinoCheckboxColors * @see CupertinoCheckboxDefaults */ @@ -177,7 +177,7 @@ class CupertinoCheckBoxAdaptation( @OptIn(ExperimentalAdaptiveApi::class) @Stable /** - * 체크박스 컴포넌트에 대한 적응형 어댑테이션을 구현하는 클래스입니다. + * Checkbox component adaptive adaptation class. * * @see Adaptation * @see CupertinoCheckBoxAdaptation diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveDivider.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveDivider.kt index c0b41fe2..12049d11 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveDivider.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveDivider.kt @@ -35,12 +35,12 @@ import zone.ien.hig.CupertinoHorizontalDivider import zone.ien.hig.CupertinoVerticalDivider /** - * 적응형 구분선 컴포넌트로, Material Design과 Cupertino 테마에 따라 다른 구분선 스타일을 제공합니다. + * Adaptive divider component that provides different divider styles based on Material Design and Cupertino themes. * - * 이 함수는 [AdaptiveHorizontalDivider] 함수로 대체되었습니다. + * This function is deprecated and replaced by [AdaptiveHorizontalDivider] function. * - * @param modifier 요소에 적용할 Modifier - * @param adaptation [DividerAdaptation]에 대한 사용자 정의 설정 함수 + * @param modifier Modifier to be applied to the element + * @param adaptation Custom configuration function for [DividerAdaptation] * @see AdaptiveHorizontalDivider * @see HorizontalDivider * @see CupertinoHorizontalDivider @@ -60,13 +60,13 @@ fun AdaptiveDivider( /** - * 적응형 수평 구분선 컴포넌트로, Material Design과 Cupertino 테마에 따라 다른 구분선 스타일을 제공합니다. + * Adaptive horizontal divider component that provides different divider styles based on Material Design and Cupertino themes. * - * [AdaptiveHorizontalDivider]는 Material Design의 [HorizontalDivider] 컴포넌트와 Cupertino의 [CupertinoHorizontalDivider] 컴포넌트를 사용하여 - * 운영체제별로 적절한 수평 구분선을 제공합니다. + * [AdaptiveHorizontalDivider] uses the Material Design [HorizontalDivider] component and the Cupertino [CupertinoHorizontalDivider] component to + * provide an appropriate horizontal divider for each operating system. * - * @param modifier 요소에 적용할 Modifier - * @param adaptation [DividerAdaptation]에 대한 사용자 정의 설정 함수 + * @param modifier Modifier to be applied to the element + * @param adaptation Custom configuration function for [DividerAdaptation] * @see AdaptiveWidget * @see HorizontalDivider * @see CupertinoHorizontalDivider @@ -100,13 +100,13 @@ fun AdaptiveHorizontalDivider( } /** - * 적응형 수직 구분선 컴포넌트로, Material Design과 Cupertino 테마에 따라 다른 구분선 스타일을 제공합니다. + * Adaptive vertical divider component that provides different divider styles based on Material Design and Cupertino themes. * - * [AdaptiveVerticalDivider]는 Material Design의 [VerticalDivider] 컴포넌트와 Cupertino의 [CupertinoVerticalDivider] 컴포넌트를 사용하여 - * 운영체제별로 적절한 수직 구분선을 제공합니다. + * [AdaptiveVerticalDivider] uses the Material Design [VerticalDivider] component and the Cupertino [CupertinoVerticalDivider] component to + * provide an appropriate vertical divider for each operating system. * - * @param modifier 요소에 적용할 Modifier - * @param adaptation [DividerAdaptation]에 대한 사용자 정의 설정 함수 + * @param modifier Modifier to be applied to the element + * @param adaptation Custom configuration function for [DividerAdaptation] * @see AdaptiveWidget * @see VerticalDivider * @see CupertinoVerticalDivider @@ -141,7 +141,7 @@ fun AdaptiveVerticalDivider( @ExperimentalAdaptiveApi /** - * 구분선 컴포넌트에 대한 적응형 어댑테이션을 구현하는 클래스입니다. + * Implementation class for divider component adaptation. * * @see Adaptation * @see DividerAdaptation @@ -184,12 +184,13 @@ private class DividerAdaptationScope: Adaptation() { diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt index 4d107bfd..2a22800c 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveSwitch.kt @@ -99,10 +99,10 @@ fun AdaptiveSwitch( /** * Cupertino switch adaptation. * - * Cupertino 스위치에 대한 적응형 어댑테이션 클래스로, 스위치의 다양한 속성을 관리합니다. + * Adaptive adaptation class for Cupertino switch that manages various switch properties. * - * @param colors [CupertinoSwitchColors] - 스위치의 색상 설정 - * @param backdrop [Backdrop] - 스위치의 배경 + * @param colors [CupertinoSwitchColors] - Switch color configuration + * @param backdrop [Backdrop] - Switch backdrop * @see CupertinoSwitchColors * @see Backdrop */ @@ -124,9 +124,9 @@ class CupertinoSwitchAdaptation internal constructor( */ @Stable /** - * Material 스위치에 대한 적응형 어댑테이션 클래스로, 스위치의 다양한 속성을 관리합니다. + * Material switch adaptation class that manages various switch properties. * - * @param colors [SwitchColors] - 스위치의 색상 설정 + * @param colors [SwitchColors] - Switch color configuration * @see SwitchColors * @see SwitchDefaults */ @@ -144,7 +144,7 @@ class MaterialSwitchAdaptation internal constructor( @OptIn(ExperimentalAdaptiveApi::class) @Stable /** - * 스위치 유형에 따라 적응형 어댑테이션을 제공하는 클래스입니다. + * Class that provides adaptive adaptation for switch type. * * @see Adaptation * @see CupertinoSwitchAdaptation diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt index b774303b..943e5a45 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTheme.kt @@ -239,11 +239,11 @@ class MaterialThemeSpec( @Immutable @ExperimentalAdaptiveApi /** - * Cupertino 테마 사양 클래스로, Cupertino 테마의 컬러, 모양, 타이포그래피를 정의합니다. + * Cupertino theme specification class that defines color, shapes, and typography for Cupertino theme. * - * @param colorScheme [CupertinoColorScheme] - 테마의 컬러 스키마 - * @param shapes [CupertinoShapes] - 테마의 모양 - * @param typography [CupertinoTypography] - 테마의 타이포그래피 + * @param colorScheme [CupertinoColorScheme] - Theme color scheme + * @param shapes [CupertinoShapes] - Theme shapes + * @param typography [CupertinoTypography] - Theme typography */ class CupertinoThemeSpec( val colorScheme: CupertinoColorScheme = cupertinoLightColorScheme(), @@ -251,12 +251,12 @@ class CupertinoThemeSpec( val typography: CupertinoTypography = CupertinoTypography() ) { /** - * CupertinoThemeSpec 인스턴스를 복사하여 새로운 인스턴스를 생성합니다. + * Creates a copy of this CupertinoThemeSpec with the specified values replaced. * - * @param colorScheme 새로운 컬러 스키마 - * @param shapes 새로운 모양 - * @param typography 새로운 타이포그래피 - * @return 복사된 CupertinoThemeSpec 인스턴스 + * @param colorScheme the new color scheme + * @param shapes the new shapes + * @param typography the new typography + * @return a copy of this CupertinoThemeSpec */ fun copy( colorScheme: CupertinoColorScheme = this.colorScheme, diff --git a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTopAppBar.kt b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTopAppBar.kt index 3dac5322..78ba17fa 100644 --- a/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTopAppBar.kt +++ b/hig-adaptive/src/commonMain/kotlin/zone/ien/hig/adaptive/AdaptiveTopAppBar.kt @@ -158,11 +158,11 @@ private fun SingleRowTopAppBar( @Stable @OptIn(ExperimentalMaterial3Api::class) /** - * Material 상단 앱바에 대한 적응형 어댑테이션 클래스로, 상단 앱바의 다양한 속성을 관리합니다. + * Adaptive adaptation class for Material top app bar that manages various top app bar properties. * - * @param colors [TopAppBarColors] - 상단 앱바의 색상 설정 - * @param isCenterAligned 제목이 중앙 정렬되는지 여부 - * @param scrollBehavior 스크롤 행동 설정 + * @param colors [TopAppBarColors] - Top app bar color configuration + * @param isCenterAligned Whether the title is centered + * @param scrollBehavior Scroll behavior configuration * @see TopAppBarColors * @see TopAppBarDefaults * @see TopAppBarScrollBehavior @@ -187,10 +187,10 @@ class MaterialTopAppBarAdaptation internal constructor( */ @Stable /** - * Cupertino 상단 앱바에 대한 적응형 어댑테이션 클래스로, 상단 앱바의 다양한 속성을 관리합니다. + * Adaptive adaptation class for Cupertino top app bar that manages various top app bar properties. * - * @param colors [CupertinoTopAppBarColors] - 상단 앱바의 색상 설정 - * @param backdrop [LayerBackdrop] - 상단 앱바의 레이어 배경 + * @param colors [CupertinoTopAppBarColors] - Top app bar color configuration + * @param backdrop [LayerBackdrop] - Top app bar layer backdrop * @see CupertinoTopAppBarColors * @see LayerBackdrop */ @@ -210,7 +210,7 @@ class CupertinoTopAppBarAdaptation internal constructor( @OptIn(ExperimentalAdaptiveApi::class) @Stable /** - * 상단 앱바 유형에 따라 적응형 어댑테이션을 제공하는 클래스입니다. + * Class that provides adaptive adaptation based on top app bar type. * * @see Adaptation * @see CupertinoTopAppBarAdaptation diff --git a/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt index a8b1bbc5..1a321792 100644 --- a/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt +++ b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt @@ -80,7 +80,7 @@ actual fun CupertinoDropdownMenuNative( delegate.sections = sections delegate.onDismissRequest = onDismissRequest - // UIButton을 뷰 계층 최하단에 삽입 + // Insert UIButton at the bottom of the view hierarchy DisposableEffect(viewController) { val button = UIButton.buttonWithType(UIButtonTypePlain).apply { backgroundColor = UIColor.clearColor @@ -98,17 +98,17 @@ actual fun CupertinoDropdownMenuNative( } } - // 아이템/섹션 변경 시 메뉴 갱신 + // Refresh menu when items/sections change LaunchedEffect(items, sections) { delegate.button?.menu = delegate.buildMenu() } - // expanded 변경 → 메뉴 표시 + // Show menu when expanded changes LaunchedEffect(expanded) { val button = delegate.button ?: return@LaunchedEffect if (!expanded) return@LaunchedEffect - delegate.isMenuVisible = true // 메뉴 표시 중 프레임 업데이트 방지 + delegate.isMenuVisible = true // Prevent frame updates while menu is visible if (isPerformPrimaryActionAvailable()) { button.performPrimaryAction() } else { diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt index 8270093f..1286d0ed 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoDropdownMenu.kt @@ -437,14 +437,14 @@ private fun CupertinoMenuScope.ActionWithoutPadding( modifier = Modifier.padding(it) ) { when { - // 아이콘 있음 → 아이콘 표시 + // Has icon → display icon leadingIcon != null -> { Box( contentAlignment = Alignment.Center, modifier = Modifier.size(MinItemHeight / 2), ) { leadingIcon() } } - // 아이콘 없지만 다른 아이템에 아이콘 있음 → 공백 확보 + // No icon but other items have icons → reserve space hasIcon -> { Spacer(modifier = Modifier.size(MinItemHeight / 2)) } @@ -709,7 +709,7 @@ internal data class DropdownMenuPositionProvider( } } -// ✅ hasIcon 추가 +// ✅ Added hasIcon internal class CupertinoMenuScopeImpl : CupertinoMenuScope { var hasPicker: Boolean by mutableStateOf(false) var hasIcon: Boolean by mutableStateOf(false) diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoLiquidButton.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoLiquidButton.kt index 9dadc5d5..dbb1bd95 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoLiquidButton.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoLiquidButton.kt @@ -145,7 +145,7 @@ fun CupertinoLiquidButton( tween(300) ) } catch (e: RuntimeException) { - // layer가 dispose된 경우 → 루프 종료 + // When layer is disposed → exit loop break } } diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt index 72998ca8..9532ef5f 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoNavigationBar.kt @@ -254,11 +254,11 @@ fun CupertinoNavigationBar( ) } - // CompositionLocal로 동적 itemWidthDp 전달 + // Pass dynamic itemWidthDp via CompositionLocal CompositionLocalProvider( LocalCupertinoNavItemWidth provides itemWidthDp, ) { - // ── 배경 Row ────────────────────────────────────────────────────────── + // ── Background Row ─────────────────────────────────────────────────────────────── Row( modifier = Modifier .graphicsLayer { @@ -291,7 +291,7 @@ fun CupertinoNavigationBar( content = content ) - // ── 액센트 컬러 오버레이 Row ────────────────────────────────────────── + // ── Accent color overlay Row ────────────────────────────────────────────────────── CompositionLocalProvider( LocalLiquidBottomTabScale provides { lerp(1f, 1.2f, dampedDragAnimation.pressProgress) @@ -334,7 +334,7 @@ fun CupertinoNavigationBar( ) } - // ── 슬라이딩 선택 indicator Box ─────────────────────────────────────── + // ── Sliding selection indicator Box ─────────────────────────────────────────── Box( modifier = Modifier .graphicsLayer { @@ -392,7 +392,7 @@ fun CupertinoNavigationBar( ) .align(Alignment.CenterStart) .height(56.dp) - .width(itemWidthDp) // ★ 동적 너비 적용 + .width(itemWidthDp) // ★ Apply dynamic width ) } } @@ -409,7 +409,7 @@ fun RowScope.CupertinoNavigationBarItem( interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, ) { val scale = LocalLiquidBottomTabScale.current - val itemWidth = LocalCupertinoNavItemWidth.current // ★ CompositionLocal에서 동적 너비 읽기 + val itemWidth = LocalCupertinoNavItemWidth.current // ★ Read dynamic width from CompositionLocal Column( verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterVertically), @@ -424,7 +424,7 @@ fun RowScope.CupertinoNavigationBarItem( onClick = onClick ) .fillMaxHeight() - .width(itemWidth) // ★ 고정 90.dp 대신 동적 너비 사용 + .width(itemWidth) // ★ Use dynamic width instead of fixed 90.dp .graphicsLayer { val s = scale() scaleX = s @@ -530,4 +530,4 @@ object CupertinoNavigationBarDefaults { } internal val LocalLiquidBottomTabScale = staticCompositionLocalOf { { 1f } } -internal val LocalCupertinoNavItemWidth = staticCompositionLocalOf { 90.dp } // ★ 동적 너비 전달용 \ No newline at end of file +internal val LocalCupertinoNavItemWidth = staticCompositionLocalOf { 90.dp } // ★ Dynamic width transmission \ No newline at end of file diff --git a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoTopBars.kt b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoTopBars.kt index bc1c91a7..df56fe4a 100644 --- a/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoTopBars.kt +++ b/hig/src/commonMain/kotlin/zone/ien/hig/CupertinoTopBars.kt @@ -302,20 +302,20 @@ fun CupertinoNavigationTitle( val titleAlpha by remember { derivedStateOf { - if (!topAppBarExists) { - 1f // TopBar 없으면 항상 보임 - } else { - val d = offsetDifference - actualTopBarHeight + 50 - // d가 음수면 Large Title 영역 → alpha 1 - // d가 양수면 TopBar로 들어감 → alpha 0 - if (d <= 0) { - 1f + if (!topAppBarExists) { + 1f // TopBar does not exist, always visible } else { - // fadeDistance만큼 부드럽게 0으로 - val fadeDistance = 100f // 조절 가능 (dp → px 변환 권장) - (1f - (d / fadeDistance)).coerceIn(0f, 1f) + val d = offsetDifference - actualTopBarHeight + 50 + // If d is negative, it's in Large Title area → alpha 1 + // If d is positive, it's entering TopBar → alpha 0 + if (d <= 0) { + 1f + } else { + // Fade smoothly to 0 over fadeDistance + val fadeDistance = 100f // Adjustable (recommended: dp → px conversion) + (1f - (d / fadeDistance)).coerceIn(0f, 1f) + } } - } } } @@ -471,7 +471,7 @@ private fun InlineTopAppBar( tween(300) ) } catch (e: RuntimeException) { - // layer가 dispose된 경우 → 루프 종료 + // When layer is disposed → exit loop break } } @@ -642,7 +642,7 @@ private fun TopAppBarLayout( if (constraints.maxWidth == Constraints.Infinity) { constraints.maxWidth } else { - val actionReservedWidth = actionIconsPlaceable.width + TopAppBarHorizontalPadding.roundToPx() * 2 // 또는 고정값 72.dp 등 + val actionReservedWidth = actionIconsPlaceable.width + TopAppBarHorizontalPadding.roundToPx() * 2 // or fixed value 72.dp, etc. (constraints.maxWidth - navigationIconPlaceable.width - actionReservedWidth) .coerceAtLeast(0) } diff --git a/hig/src/skikoMain/kotlin/zone/ien/hig/Luminance.skiko.kt b/hig/src/skikoMain/kotlin/zone/ien/hig/Luminance.skiko.kt index 1b1e8280..5b78f51a 100644 --- a/hig/src/skikoMain/kotlin/zone/ien/hig/Luminance.skiko.kt +++ b/hig/src/skikoMain/kotlin/zone/ien/hig/Luminance.skiko.kt @@ -37,8 +37,8 @@ actual fun ImageBitmap.crop(x: Int, y: Int, width: Int, height: Int): ImageBitma Canvas(dstBitmap).use { canvas -> val srcRect = Rect.makeXYWH(x.toFloat(), y.toFloat(), cropWidth.toFloat(), cropHeight.toFloat()) - val dstRect = Rect.makeXYWH(0f, 0f, cropWidth.toFloat(), cropHeight.toFloat()) // ← 추가 - canvas.drawImageRect(Image.makeFromBitmap(srcBitmap), srcRect, dstRect) // ← dst 명시 + val dstRect = Rect.makeXYWH(0f, 0f, cropWidth.toFloat(), cropHeight.toFloat()) // ← added + canvas.drawImageRect(Image.makeFromBitmap(srcBitmap), srcRect, dstRect) // ← dst specified } return dstBitmap.asComposeImageBitmap() From a818f4542f64548858da9e7167c130d48d94056a Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 13 May 2026 21:49:02 +0900 Subject: [PATCH 105/109] Update hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoNavigationBarNative.ios.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../kotlin/zone/ien/hig/CupertinoNavigationBarNative.ios.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoNavigationBarNative.ios.kt b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoNavigationBarNative.ios.kt index 4edb9b97..f0e5c655 100644 --- a/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoNavigationBarNative.ios.kt +++ b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoNavigationBarNative.ios.kt @@ -54,7 +54,7 @@ actual fun CupertinoNavigationBarNative( translatesAutoresizingMaskIntoConstraints = false } } - val isLiquidGlassEnabled = true + val isLiquidGlassEnabled = true // TODO: 파라미터화 고려 val onItemSelectedState by rememberUpdatedState(onTabSelected) From ce0080513874fa4823458b4a404ea8e8397764b5 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 13 May 2026 21:49:36 +0900 Subject: [PATCH 106/109] Update hig-native/src/iosMain/kotlin/zone/ien/hig/util.ios.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- hig-native/src/iosMain/kotlin/zone/ien/hig/util.ios.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hig-native/src/iosMain/kotlin/zone/ien/hig/util.ios.kt b/hig-native/src/iosMain/kotlin/zone/ien/hig/util.ios.kt index 7b5dd93d..7ed882e5 100644 --- a/hig-native/src/iosMain/kotlin/zone/ien/hig/util.ios.kt +++ b/hig-native/src/iosMain/kotlin/zone/ien/hig/util.ios.kt @@ -74,7 +74,7 @@ internal fun UIView.applyTheme(dark: Boolean) { fun ImageBitmap.toUIImage(): UIImage { val skiaImage = Image.makeFromBitmap(this.asSkiaBitmap()) val pngData = skiaImage.encodeToData(EncodedImageFormat.PNG) - ?: error("Failed to encode ImageBitmap to PNG") + ?: return UIImage() val pngBytes = pngData.bytes val nsData = pngBytes.usePinned { pinned -> NSData.dataWithBytes(pinned.addressOf(0), pngBytes.size.toULong()) From 2708f553e1eae4e7be2ebf307ca2511279d12b78 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 13 May 2026 21:50:26 +0900 Subject: [PATCH 107/109] Update example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt index b5390244..2b70d73b 100644 --- a/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt +++ b/example/composeApp/src/commonMain/kotlin/cupertino/CupertinoWidgetsScreen.kt @@ -1833,7 +1833,6 @@ private fun DropdownExample( CupertinoDropdownMenuNative( expanded = dropdownVisible, onDismissRequest = { - println("onDismissRequest on Screen"); dropdownVisible = false }, backdrop = backdrop, From eba64efeb6c5b4531945d534c3bb993f14e60d41 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 13 May 2026 21:51:09 +0900 Subject: [PATCH 108/109] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(core):=20?= =?UTF-8?q?iOS=20=EB=84=A4=EC=9D=B4=ED=8B=B0=EB=B8=8C=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CupertinoDropdownMenuNative에서 iOS 시스템 버전 체크 로직 개선 - CupertinoNavigationBarNative에서 탭바 높이 안정화 로직 추가 및 구조 개선 - 탭바 높이 변경 시 패딩 업데이트 로직 최적화 --- .../hig/CupertinoDropdownMenuNative.ios.kt | 10 +++----- .../hig/CupertinoNavigationBarNative.ios.kt | 25 ++++--------------- 2 files changed, 9 insertions(+), 26 deletions(-) diff --git a/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt index 1a321792..b33adcf6 100644 --- a/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt +++ b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt @@ -206,9 +206,7 @@ private fun CupertinoMenuItemData.toUIAction(): UIAction { return action } -private fun isPerformPrimaryActionAvailable(): Boolean { - val systemVersion = UIDevice.currentDevice.systemVersion - val major = systemVersion.split(".").firstOrNull()?.toIntOrNull() ?: 0 - val minor = systemVersion.split(".").getOrNull(1)?.toIntOrNull() ?: 0 - return major > 17 || (major == 17 && minor >= 4) -} +@OptIn(ExperimentalForeignApi::class) +private fun isPerformPrimaryActionAvailable(): Boolean = platform.Foundation.NSProcessInfo.processInfo.operatingSystemVersion.useContents { + majorVersion > 17 || (majorVersion == 17L && minorVersion >= 4L) +} \ No newline at end of file diff --git a/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoNavigationBarNative.ios.kt b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoNavigationBarNative.ios.kt index f0e5c655..bfe720e4 100644 --- a/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoNavigationBarNative.ios.kt +++ b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoNavigationBarNative.ios.kt @@ -54,7 +54,7 @@ actual fun CupertinoNavigationBarNative( translatesAutoresizingMaskIntoConstraints = false } } - val isLiquidGlassEnabled = true // TODO: 파라미터화 고려 + val isLiquidGlassEnabled = true // TODO: consider parameterize val onItemSelectedState by rememberUpdatedState(onTabSelected) @@ -105,39 +105,24 @@ actual fun CupertinoNavigationBarNative( } LaunchedEffect(Unit) { - var tabBarHeightConsistencyCounter = 0 - - while (true) { + repeat(15) { // 최대 15프레임 동안 안정화 시도 tabBarView.frame.useContents { - topLeft = DpOffset( - x = origin.x.dp, - y = origin.y.dp, - ) + topLeft = DpOffset(x = origin.x.dp, y = origin.y.dp) tabBarWidth = size.width.dp - val safeAreaBottom = - if (isLiquidGlassEnabled) - 0.dp - else - viewController.view.safeAreaInsets.useContents { bottom.dp } + + val safeAreaBottom = if (isLiquidGlassEnabled) 0.dp else viewController.view.safeAreaInsets.useContents { bottom.dp } val newTabBarHeight = size.height.dp + safeAreaBottom IosBarHeightCache.updateTabBarHeight(size.height.dp) if (tabBarHeight != newTabBarHeight) { tabBarHeight = newTabBarHeight - tabBarHeightConsistencyCounter = 0 if (newTabBarHeight.value > 0f) { iosTabBarPaddingState.value = PaddingValues(bottom = newTabBarHeight) } - } else { - tabBarHeightConsistencyCounter++ } } - - if (tabBarHeight.value > 0f && tabBarHeightConsistencyCounter > 6) - break - withFrameMillis { } } } From 83f092d0d5f2d985f2811ec6c1cb99d6c7881951 Mon Sep 17 00:00:00 2001 From: IENGROUND Date: Wed, 13 May 2026 21:51:34 +0900 Subject: [PATCH 109/109] =?UTF-8?q?=20fix(core):=20CupertinoDropdownMenuNa?= =?UTF-8?q?tive=20iOS=20=EC=86=8D=EC=84=B1=20=EC=B0=B8=EC=A1=B0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UIMenuElementAttributesDestructive 및 UIMenuElementAttributesDisabled 참조 경로 수정 - UIKit 관련 속성 참조 정확성 개선 - iOS 플랫폼에서 메뉴 요소 속성 적용 오류 해결 --- .../kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt index b33adcf6..b8fb5f81 100644 --- a/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt +++ b/hig-native/src/iosMain/kotlin/zone/ien/hig/CupertinoDropdownMenuNative.ios.kt @@ -200,8 +200,8 @@ private fun CupertinoMenuItemData.toUIAction(): UIAction { ) { _ -> onClick() } var attributes: ULong = 0u - if (isDestructive) attributes = attributes or platform.UIKit.UIMenuElementAttributesDestructive - if (!enabled) attributes = attributes or platform.UIKit.UIMenuElementAttributesDisabled + if (isDestructive) attributes = attributes or UIMenuElementAttributesDestructive + if (!enabled) attributes = attributes or UIMenuElementAttributesDisabled action.attributes = attributes return action }