diff --git a/feature/mindrecord/presentation/src/main/kotlin/com/afternote/feature/mindrecord/presentation/component/memoryspace/MemorySpaceHeader.kt b/feature/mindrecord/presentation/src/main/kotlin/com/afternote/feature/mindrecord/presentation/component/memoryspace/MemorySpaceHeader.kt index ba7fe94499..d1da59737e 100644 --- a/feature/mindrecord/presentation/src/main/kotlin/com/afternote/feature/mindrecord/presentation/component/memoryspace/MemorySpaceHeader.kt +++ b/feature/mindrecord/presentation/src/main/kotlin/com/afternote/feature/mindrecord/presentation/component/memoryspace/MemorySpaceHeader.kt @@ -20,8 +20,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.shadow +import androidx.compose.ui.layout.SubcomposeLayout import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -35,7 +37,7 @@ fun MemorySpaceHeader( onBackClick: () -> Unit, modifier: Modifier = Modifier, ) { - Row( + SubcomposeLayout( modifier = modifier .fillMaxWidth() @@ -43,62 +45,106 @@ fun MemorySpaceHeader( // 않는다. statusBarsPadding 을 빼면 "돌아가기" 가 상태바 아래로 들어가 터치가 // 상태바에 먹혀 실제로 눌리지 않는다 (#559) — 아래 24.dp 는 그 위에 얹는 여백이다. .statusBarsPadding() - .padding(top = 24.dp, start = 24.dp), - horizontalArrangement = Arrangement.spacedBy(13.dp), - ) { - // 1. 뒤로가기 버튼 (Box로 변경) - Box( - modifier = - Modifier - .shadow(2.dp, CircleShape) // 그림자 수동 적용 - .background(AfternoteDesign.colors.white, CircleShape) - .clip(CircleShape) - .clickable( - onClick = onBackClick, - // 리플 효과가 원형 밖으로 나가지 않도록 설정 - ), - ) { - Row( - modifier = - Modifier - .padding(start = 20.dp, end = 15.dp) - .padding(vertical = 9.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(12.dp), - ) { - Icon( - painter = painterResource(CoreUiR.drawable.core_ui_arrow_left), - contentDescription = stringResource(R.string.mindrecord_memory_space_back), - modifier = Modifier.size(width = 4.dp, height = 7.dp), - tint = AfternoteDesign.colors.gray7, - ) - Text( - text = stringResource(R.string.mindrecord_memory_space_back), - style = AfternoteDesign.typography.inter, - color = AfternoteDesign.colors.gray7, - ) + // 좌우를 같이 준다. 종전에는 start 만 있어서 제목 블록이 오른쪽 화면 끝까지 + // 밀고 나갔다 — 360dp + 글자 1.5배 실측에서 우측 여백이 1.5dp 였다 (#1153). + .padding(top = 24.dp, start = 24.dp, end = 24.dp), + ) { constraints -> + val gap = HEADER_GAP.roundToPx() + val width = constraints.maxWidth + val loose = constraints.copy(minWidth = 0, minHeight = 0) + + val back = subcompose(HeaderSlot.Back) { BackPill(onBackClick) }.first().measure(loose) + + // **먼저 시안대로 놓아 본다** — 정본(4327:67706)은 제목 블록을 화면 중앙에 둔다. + // 폭이 넉넉하면(기본 배율·기본 화면) 그대로 중앙이고 develop 과 달라지지 않는다. + val centered = subcompose(HeaderSlot.TitleCentered) { HeaderTitle() }.first().measure(loose) + val centeredX = (width - centered.width) / 2 + + // 중앙에 두면 「돌아가기」와 겹치는 경우에만 남은 폭으로 접는다. 좁은 화면과 큰 글자가 + // 곱해지는 조건에서만 걸리고, 그때는 «겹치지 않는 것» 이 «중앙» 보다 우선한다 (#1153). + val fitsCentered = centeredX >= back.width + gap + val slotStart = back.width + gap + val remaining = (width - slotStart).coerceAtLeast(0) + val title = + if (fitsCentered) { + centered + } else { + subcompose(HeaderSlot.TitleConstrained) { HeaderTitle() } + .first() + .measure(loose.copy(maxWidth = remaining)) } + // 겹칠 때는 **필요한 만큼만** 오른쪽으로 민다. 남은 영역의 가운데로 옮기면 겹침은 + // 똑같이 풀리면서 화면 중심에서 더 멀어진다(360dp 실측 236dp vs 199dp) — 시안이 요구하는 + // 것은 중앙이므로, 중앙에서의 이탈을 최소로 두는 쪽이 맞다. + val titleX = maxOf(centeredX, slotStart) + + layout(width, maxOf(back.height, title.height)) { + back.place(0, 0) + title.place(titleX, 0) } + } +} - // 2. 타이틀 영역 - Column( - horizontalAlignment = Alignment.CenterHorizontally, +/** 서브컴포즈 슬롯 키. 같은 내용을 두 제약으로 재어 보므로 키가 갈려 있어야 한다. */ +private enum class HeaderSlot { Back, TitleCentered, TitleConstrained } + +/** 「돌아가기」와 제목 블록 사이 최소 간격. */ +private val HEADER_GAP = 13.dp + +@Composable +private fun BackPill(onBackClick: () -> Unit) { + Box( + modifier = + Modifier + .shadow(2.dp, CircleShape) + .background(AfternoteDesign.colors.white, CircleShape) + .clip(CircleShape) + .clickable(onClick = onBackClick), + ) { + Row( + modifier = + Modifier + .padding(start = 20.dp, end = 15.dp) + .padding(vertical = 9.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp), ) { - Text( - text = stringResource(R.string.mindrecord_memory_space_title), - style = AfternoteDesign.typography.inter, - color = AfternoteDesign.colors.gray6, + Icon( + painter = painterResource(CoreUiR.drawable.core_ui_arrow_left), + contentDescription = stringResource(R.string.mindrecord_memory_space_back), + modifier = Modifier.size(width = 4.dp, height = 7.dp), + tint = AfternoteDesign.colors.gray7, ) - Spacer(modifier = Modifier.height(4.dp)) Text( - text = stringResource(R.string.mindrecord_memory_space_subtitle), - style = AfternoteDesign.typography.inter.copy(fontSize = 9.sp), - color = AfternoteDesign.colors.gray5, + text = stringResource(R.string.mindrecord_memory_space_back), + style = AfternoteDesign.typography.inter, + color = AfternoteDesign.colors.gray7, ) } } } +@Composable +private fun HeaderTitle() { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + text = stringResource(R.string.mindrecord_memory_space_title), + style = AfternoteDesign.typography.inter, + color = AfternoteDesign.colors.gray6, + textAlign = TextAlign.Center, + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = stringResource(R.string.mindrecord_memory_space_subtitle), + style = AfternoteDesign.typography.inter.copy(fontSize = 9.sp), + color = AfternoteDesign.colors.gray5, + // Column 의 CenterHorizontally 는 Text 상자를 가운데 놓을 뿐이고, 접힌 줄을 + // 서로 가운데로 맞추는 것은 textAlign 이다 — 없으면 둘째 줄이 왼쪽에 붙는다. + textAlign = TextAlign.Center, + ) + } +} + @Preview(showBackground = true) @Composable private fun MemorySpaceHeaderPreview() { diff --git a/feature/mindrecord/presentation/src/screenshotTest/kotlin/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceHeaderLargeFontScreenshotTest.kt b/feature/mindrecord/presentation/src/screenshotTest/kotlin/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceHeaderLargeFontScreenshotTest.kt new file mode 100644 index 0000000000..11ec427a1d --- /dev/null +++ b/feature/mindrecord/presentation/src/screenshotTest/kotlin/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceHeaderLargeFontScreenshotTest.kt @@ -0,0 +1,47 @@ +package com.afternote.feature.mindrecord.presentation.screen.memoryspace + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import com.afternote.core.ui.theme.AfternoteTheme +import com.afternote.feature.mindrecord.presentation.COMPACT_DEVICE_SPEC +import com.afternote.feature.mindrecord.presentation.R +import com.android.tools.screenshot.PreviewTest + +/** + * 좁은 화면 × 글자 확대 — 헤더가 화면 밖으로 뻗지 않는지 (#1153). + * + * 이 조합이 결함을 드러낸 조건이다. 360dp 기본 배율에서는 제목 블록의 내용 폭이 작아 우측 + * 여백이 남지만, 글자를 1.5배로 키우면 종전 헤더는 부제를 **화면 오른쪽 끝까지**(x=356.5dp / + * 폭 360dp) 밀어냈다 — `start` 만 있고 `end` 패딩이 없었고, 제목 Column 에 weight 가 없어 + * 남은 폭을 상한으로 받지 못했기 때문이다. + * + * 크기 축(#1131)과 배율 축(#1146)이 **곱해져야** 드러나는 자리라, 둘 중 하나만으로는 잡히지 + * 않는다. 그래서 이 조합을 따로 고정한다. + */ +@PreviewTest +@Preview( + showBackground = true, + backgroundColor = 0xFFF5F5F5, + device = COMPACT_DEVICE_SPEC, + fontScale = LARGE_FONT_SCALE, +) +@Composable +internal fun memorySpaceScreenCompactLargeFontScreenshot() { + AfternoteTheme { + MemorySpaceContent( + onBackClick = {}, + modifier = Modifier.fillMaxSize(), + memories = emptyList(), + statusText = stringResource(R.string.mindrecord_error_memory_space_failed), + onRetryClick = {}, + ) + } +} + +/** + * Android 접근성 설정의 "크게" 구간. 이 값에서 #1153 이 드러났고 1.3 에서는 드러나지 않았다. + */ +internal const val LARGE_FONT_SCALE = 1.5f diff --git a/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceHeaderLargeFontScreenshotTestKt/memorySpaceScreenCompactLargeFontScreenshot_4138e1bb_0.png b/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceHeaderLargeFontScreenshotTestKt/memorySpaceScreenCompactLargeFontScreenshot_4138e1bb_0.png new file mode 100644 index 0000000000..b1f846d1f1 Binary files /dev/null and b/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceHeaderLargeFontScreenshotTestKt/memorySpaceScreenCompactLargeFontScreenshot_4138e1bb_0.png differ diff --git a/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceScreenScreenshotTestKt/memorySpaceScreenCompactScreenshot_e7ca7c28_0.png b/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceScreenScreenshotTestKt/memorySpaceScreenCompactScreenshot_e7ca7c28_0.png index 6a11855573..00b812e3a2 100644 Binary files a/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceScreenScreenshotTestKt/memorySpaceScreenCompactScreenshot_e7ca7c28_0.png and b/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceScreenScreenshotTestKt/memorySpaceScreenCompactScreenshot_e7ca7c28_0.png differ diff --git a/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceScreenScreenshotTestKt/memorySpaceScreenEmptyCompactScreenshot_e7ca7c28_0.png b/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceScreenScreenshotTestKt/memorySpaceScreenEmptyCompactScreenshot_e7ca7c28_0.png index 09110adfe4..6649e7c813 100644 Binary files a/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceScreenScreenshotTestKt/memorySpaceScreenEmptyCompactScreenshot_e7ca7c28_0.png and b/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceScreenScreenshotTestKt/memorySpaceScreenEmptyCompactScreenshot_e7ca7c28_0.png differ diff --git a/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceScreenScreenshotTestKt/memorySpaceScreenEmptyScreenshot_90705b76_0.png b/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceScreenScreenshotTestKt/memorySpaceScreenEmptyScreenshot_90705b76_0.png index 748fb481f3..ba9f3fd7a1 100644 Binary files a/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceScreenScreenshotTestKt/memorySpaceScreenEmptyScreenshot_90705b76_0.png and b/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceScreenScreenshotTestKt/memorySpaceScreenEmptyScreenshot_90705b76_0.png differ diff --git a/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceScreenScreenshotTestKt/memorySpaceScreenErrorCompactScreenshot_e7ca7c28_0.png b/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceScreenScreenshotTestKt/memorySpaceScreenErrorCompactScreenshot_e7ca7c28_0.png index b172aedd17..fda66c3ee4 100644 Binary files a/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceScreenScreenshotTestKt/memorySpaceScreenErrorCompactScreenshot_e7ca7c28_0.png and b/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceScreenScreenshotTestKt/memorySpaceScreenErrorCompactScreenshot_e7ca7c28_0.png differ diff --git a/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceScreenScreenshotTestKt/memorySpaceScreenErrorScreenshot_90705b76_0.png b/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceScreenScreenshotTestKt/memorySpaceScreenErrorScreenshot_90705b76_0.png index 1a8648cd39..9a4b9c2b19 100644 Binary files a/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceScreenScreenshotTestKt/memorySpaceScreenErrorScreenshot_90705b76_0.png and b/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceScreenScreenshotTestKt/memorySpaceScreenErrorScreenshot_90705b76_0.png differ diff --git a/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceScreenScreenshotTestKt/memorySpaceScreenScreenshot_90705b76_0.png b/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceScreenScreenshotTestKt/memorySpaceScreenScreenshot_90705b76_0.png index d471a4b38f..5d45f97310 100644 Binary files a/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceScreenScreenshotTestKt/memorySpaceScreenScreenshot_90705b76_0.png and b/feature/mindrecord/presentation/src/screenshotTestDebug/reference/com/afternote/feature/mindrecord/presentation/screen/memoryspace/MemorySpaceScreenScreenshotTestKt/memorySpaceScreenScreenshot_90705b76_0.png differ