Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -35,70 +37,114 @@ fun MemorySpaceHeader(
onBackClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Row(
SubcomposeLayout(
modifier =
modifier
.fillMaxWidth()
// 이 화면은 Scaffold 없이 전체 화면 Box 위에 직접 얹히므로 시스템 인셋이 적용되지
// 않는다. 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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading