From cc4a7c9d7974b9f31bfc1db65e8a729f704a68c3 Mon Sep 17 00:00:00 2001 From: soyeonLee <109227292+soyeonLee126@users.noreply.github.com> Date: Mon, 17 Aug 2026 17:45:10 +0900 Subject: [PATCH 01/20] =?UTF-8?q?feat:=20=ED=99=88=20=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=EC=B0=BD=EC=9D=84=20=ED=8F=AC=EC=BB=A4=EC=8A=A4=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=9D=BC=20=ED=99=95=EC=9E=A5=EB=90=98=EB=8A=94=20?= =?UTF-8?q?=ED=98=95=ED=83=9C=EB=A1=9C=20=EB=B0=94=EA=BF=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 포커스가 들어가면 입력창이 64dp 에서 149dp 로 늘고, 텍스트는 위에 남고 감정 선택과 전송 버튼은 하단 행으로 내려간다. - Row/Column 을 갈아 끼우면 BasicTextField 가 다시 만들어져 포커스가 끊기므로, 자식은 한 번만 컴포즈하고 커스텀 Layout 으로 배치만 다시 계산한다 - 입력창 배경을 손그림 SVG 기반 VectorDrawable 로 옮기고 PNG 를 지운다. aapt2 가 32KB 넘는 pathData 를 STRING_TOO_LARGE 로 치환해 크래시가 나므로 독립 서브패스 단위로 나눠 담는다 - 두 에셋의 세로 비율이 달라 중간 높이에서는 테두리가 왜곡되므로, 애니메이션 동안에는 굵기가 일정한 손그림 테두리를 대신 그린다 - 감정 선택 패널도 같은 손그림 테두리를 쓰고, 팝업은 입력창 실측 높이에 맞춰 겹친다 - 입력창 바깥을 누르면 포커스를 내려놓는다 - 140자 초과 안내가 매 입력마다 반복되던 문제를 고친다 --- .../designsystem/component/GamssBottomBar.kt | 40 ++- .../component/GamssCharacterPicker.kt | 10 +- .../designsystem/component/GamssInputBar.kt | 287 ++++++++++++++---- .../component/GamssSketchyShape.kt | 119 ++++++++ .../main/res/drawable-nodpi/bg_tab_bar.png | Bin 5880 -> 0 bytes .../src/main/res/drawable/bg_input_box.xml | 21 ++ .../main/res/drawable/bg_input_box_focus.xml | 21 ++ .../gamss/android/feature/home/HomeScreen.kt | 143 ++++++--- .../android/feature/home/HomeViewModel.kt | 5 + 9 files changed, 509 insertions(+), 137 deletions(-) create mode 100644 core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyShape.kt delete mode 100644 core/designsystem/src/main/res/drawable-nodpi/bg_tab_bar.png create mode 100644 core/designsystem/src/main/res/drawable/bg_input_box.xml create mode 100644 core/designsystem/src/main/res/drawable/bg_input_box_focus.xml diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssBottomBar.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssBottomBar.kt index d1772b32..3bbac3c6 100644 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssBottomBar.kt +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssBottomBar.kt @@ -1,38 +1,36 @@ package com.gamss.android.core.designsystem.component -import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.interaction.MutableInteractionSource 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.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.selection.selectable import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp -import com.gamss.android.core.designsystem.R import com.gamss.android.core.designsystem.theme.GamssTheme -// 손그림 테두리는 path 조각이 500여 개라 VectorDrawable 로 넣으면 aapt 문자열 한도를 넘겨 STRING_TOO_LARGE 로 잘린다. 배경만 3배율 PNG 로 둔다. -// 높이는 디자인 62dp 에 에셋의 stroke 여백 2dp 를 더한 값. -private val BottomBarHeight = 64.dp -private val BottomBarHorizontalMargin = 20.dp -private val BottomBarBottomMargin = 8.dp -private val ItemRowHorizontalPadding = 53.dp -private val ItemIconSize = 24.dp +private val BottomBarHeight = 66.dp +private val ItemRowWidth = 255.dp +private val ItemIconSize = 26.dp private val ItemLabelGap = 4.dp +private val BottomBarStroke = Color(0xFFDCE0E6) +private val SelectedColor = Color(0xFF303136) +private val UnselectedColor = Color(0xFFAAAFBD) /** 화면이 edge-to-edge 라 시스템 내비게이션 영역은 이 컴포넌트가 직접 피한다. */ @Composable @@ -46,19 +44,15 @@ fun GamssBottomBar( modifier = modifier .fillMaxWidth() .navigationBarsPadding() - .padding(horizontal = BottomBarHorizontalMargin, vertical = BottomBarBottomMargin) - .height(BottomBarHeight), + .height(BottomBarHeight) + .background(Color.White) + .border(1.dp, BottomBarStroke), ) { - Image( - painter = painterResource(R.drawable.bg_tab_bar), - contentDescription = null, - contentScale = ContentScale.FillBounds, - modifier = Modifier.fillMaxSize(), - ) Row( modifier = Modifier - .fillMaxSize() - .padding(horizontal = ItemRowHorizontalPadding), + .width(ItemRowWidth) + .height(44.dp) + .align(Alignment.Center), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { @@ -80,7 +74,7 @@ private fun BottomBarItem( onClick: () -> Unit, modifier: Modifier = Modifier, ) { - val contentColor = if (selected) GamssTheme.colors.gray900 else GamssTheme.colors.gray300 + val contentColor = if (selected) SelectedColor else UnselectedColor val interactionSource = remember { MutableInteractionSource() } Column( diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssCharacterPicker.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssCharacterPicker.kt index 6b32c123..eaf624f3 100644 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssCharacterPicker.kt +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssCharacterPicker.kt @@ -2,7 +2,6 @@ package com.gamss.android.core.designsystem.component import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.border import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -12,16 +11,13 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.selection.toggleable -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp -import com.gamss.android.core.designsystem.theme.GamssRadius import com.gamss.android.core.designsystem.theme.GamssTheme // Figma 노드 3331:3950 의 auto layout 값. 패널 211x94(hug), 패딩 20, 3x2 그리드, 간격 18. @@ -47,9 +43,11 @@ fun GamssCharacterPicker( ) { Column( modifier = modifier - .clip(RoundedCornerShape(GamssRadius.radius050)) .background(GamssTheme.colors.white) - .border(PanelBorderWidth, GamssTheme.colors.gray200, RoundedCornerShape(GamssRadius.radius050)) + .gamssSketchyBorder( + color = GamssTheme.colors.black, + width = PanelBorderWidth, + ) .padding(PickerPadding), verticalArrangement = Arrangement.spacedBy(PickerGap), ) { diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt index f51a62cd..e908d1e1 100644 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt @@ -1,47 +1,81 @@ package com.gamss.android.core.designsystem.component +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.tween import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.draw.paint +import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.Layout import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.dp +import com.gamss.android.core.designsystem.R import com.gamss.android.core.designsystem.theme.GamssTheme +import kotlin.math.abs -/** 입력바 아래에 무언가를 띄우는 화면이 있어 공개한다. */ -val GamssInputBarHeight = 53.dp +// 실제 높이는 collapsed 64dp <-> expanded 149dp+ 로 변하므로, 바깥에서는 측정값을 써야 한다. +private val GamssInputBarHeight = 64.dp -private val InputBarHeight = GamssInputBarHeight -private val InputBarBorderWidth = 1.5.dp -private val InputBarStartPadding = 21.dp +private val InputBarStartPadding = 16.dp +private val InputBarEndPadding = 16.dp +private val ControlsRowGap = 4.dp /** 전송 버튼은 배경까지 담긴 32x32 에셋이라 tint 하지 않고 그대로 그린다. */ private val SendButtonSize = 32.dp -// 버튼을 48dp 터치 영역으로 감싸면 좌우로 8dp 씩 남는다. 디자인값 18dp 에서 그만큼 빼야 -// 버튼의 보이는 가장자리가 18dp 자리에 온다. -private val SendButtonTouchSize = 48.dp -private val InputBarEndPadding = 10.dp - -// 140자 상한을 이 폭에서 담으려면 6줄이면 넉넉하다. 넘치면 입력칸 안에서 스크롤된다. +// expanded 컨트롤 행이 32dp 고정이라 Android 권장 터치 영역(48dp)을 못 주고 아이콘 크기에 맞춘다. +private val SendButtonTouchSize = 32.dp private const val INPUT_MAX_LINES = 6 +private val InputLineHeight = 20.dp + +// Figma(node 3483:16376): 포커스가 들어가면 입력창이 366x149로 커지고, 텍스트는 위(16dp 여백)에 +// 남고 감정 선택·전송 버튼은 하단(16dp 여백)의 별도 행으로 내려간다. +private val ExpandedInputBarHeight = 149.dp +private val ExpandedTopPadding = 16.dp +private val ExpandedBottomPadding = 16.dp +private val ControlsRowHeight = 32.dp +// 149 - 위16 - 아래16 - 컨트롤행32 = 85dp: 컨트롤 행과 겹치지 않고 텍스트가 늘어날 수 있는 최대 높이. +private val ExpandedTextAreaMaxHeight = + ExpandedInputBarHeight - ExpandedTopPadding - ExpandedBottomPadding - ControlsRowHeight + +// 기본 스프링(약 300ms)은 순간적으로 튀는 느낌이라, 손그림 톤에 맞게 좀 더 여유 있게 늘어나도록 늦춘다. +private const val HEIGHT_ANIMATION_DURATION_MS = 350 + +// 애니메이션 도중에만 쓰는 프로시저럴 테두리 굵기. 4배 에셋에서 실측한 6px = 1.5dp 에 맞춘다. +private val InputBarBorderWidth = 1.5.dp + +/** + * 포커스 여부로 collapsed(한 줄) <-> expanded(텍스트 위 / 컨트롤 아래) 모양이 바뀐다. + * + * Row/Column 을 if-else 로 갈아 끼우면 BasicTextField 가 매번 새 노드로 다시 만들어져 포커스를 + * 잡자마자 키보드가 닫힌다. 그래서 자식은 한 번만 컴포즈하고 배치만 커스텀 [Layout] 로 매 프레임 + * 다시 계산한다. + */ @Composable fun GamssInputBar( value: String, @@ -55,59 +89,184 @@ fun GamssInputBar( trailingAction: @Composable (() -> Unit)? = null, ) { val canSubmit = enabled && value.isNotBlank() + var lineCount by remember { mutableIntStateOf(1) } + var isExpanded by remember { mutableStateOf(false) } + + val collapsedHeight = GamssInputBarHeight + InputLineHeight * (lineCount - 1) + val expandedHeight = ExpandedInputBarHeight + + (InputLineHeight * lineCount - ExpandedTextAreaMaxHeight).coerceAtLeast(0.dp) + val targetHeight = if (isExpanded) expandedHeight else collapsedHeight + val animatedHeight by animateDpAsState( + targetValue = targetHeight, + animationSpec = tween(durationMillis = HEIGHT_ANIMATION_DURATION_MS), + label = "GamssInputBarHeight", + ) + + // collapsed/expanded 에셋은 세로 비율이 달라 중간 높이로 늘리면 테두리 굵기가 왜곡된다 + // (손그림이라 9-patch도 못 쓴다). 정착했을 때만 실제 에셋을 쓴다. + val isAnimating = abs((animatedHeight - targetHeight).value) > 0.01f + val useFocusArt = animatedHeight > (collapsedHeight + expandedHeight) / 2 - Row( + Layout( modifier = modifier - // 140자를 채우면 여러 줄이 된다. 한 줄일 때의 높이를 최소치로만 잡고 아래로 늘어나게 둔다. - .heightIn(min = InputBarHeight) - .background(GamssTheme.colors.white, RectangleShape) - .border(InputBarBorderWidth, GamssTheme.colors.black, RectangleShape) - .padding(start = InputBarStartPadding, end = InputBarEndPadding), - verticalAlignment = Alignment.CenterVertically, - ) { - BasicTextField( - value = value, - onValueChange = onValueChange, - modifier = Modifier.weight(1f), - enabled = enabled, - // 한 줄로 묶으면 140자 상한에 걸려도 가로로 스크롤만 되어 잘린 게 보이지 않는다. - // 상한을 안 걸면 남은 세로 공간을 전부 채워 버리므로 줄 수로 묶는다. - singleLine = false, - minLines = 1, - maxLines = INPUT_MAX_LINES, - textStyle = GamssTheme.typography.body4Medium.copy(color = GamssTheme.colors.gray900), - cursorBrush = SolidColor(GamssTheme.colors.gray900), - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Send), - keyboardActions = KeyboardActions(onSend = { if (canSubmit) onTrailingClick() }), - decorationBox = { innerTextField -> - // 자리표시자와 입력칸을 겹쳐 놓아야 한다. 컨테이너 없이 나란히 두면 입력칸이 밀려난다. - Box( - modifier = Modifier.fillMaxWidth(), - contentAlignment = Alignment.CenterStart, - ) { - if (value.isEmpty()) { - GamssText( - text = placeholder, - style = GamssTheme.typography.body4Medium, - color = GamssTheme.colors.gray500, - ) - } - innerTextField() + .height(animatedHeight) + .inputBarBackground(useFocusArt = useFocusArt, isAnimating = isAnimating), + content = { + InputTextField( + value = value, + onValueChange = onValueChange, + enabled = enabled, + placeholder = placeholder, + canSubmit = canSubmit, + onSubmit = onTrailingClick, + alignment = if (isExpanded) Alignment.TopStart else Alignment.CenterStart, + onFocusChanged = { isExpanded = it }, + onLineCountChanged = { lineCount = it }, + ) + ControlsRow( + isExpanded = isExpanded, + canSubmit = canSubmit, + trailingContentDescription = trailingContentDescription, + onTrailingClick = onTrailingClick, + trailingAction = trailingAction, + ) + }, + ) { measurables, constraints -> + val (textFieldMeasurable, controlsMeasurable) = measurables + val startPad = InputBarStartPadding.roundToPx() + val endPad = InputBarEndPadding.roundToPx() + val availableWidth = (constraints.maxWidth - startPad - endPad).coerceAtLeast(0) + val totalHeight = constraints.maxHeight + + if (isExpanded) { + val topPad = ExpandedTopPadding.roundToPx() + val bottomPad = ExpandedBottomPadding.roundToPx() + val controlsHeight = ControlsRowHeight.roundToPx() + val controlsPlaceable = controlsMeasurable.measure( + Constraints.fixed(availableWidth, controlsHeight), + ) + val textHeight = (totalHeight - topPad - bottomPad - controlsHeight).coerceAtLeast(0) + val textPlaceable = textFieldMeasurable.measure( + Constraints.fixed(availableWidth, textHeight), + ) + layout(constraints.maxWidth, totalHeight) { + textPlaceable.place(startPad, topPad) + controlsPlaceable.place(startPad, topPad + textHeight) + } + } else { + val controlsPlaceable = controlsMeasurable.measure( + Constraints(maxWidth = availableWidth, maxHeight = totalHeight), + ) + val textWidth = (availableWidth - controlsPlaceable.width).coerceAtLeast(0) + val textPlaceable = textFieldMeasurable.measure( + Constraints.fixed(textWidth, totalHeight), + ) + layout(constraints.maxWidth, totalHeight) { + textPlaceable.place(startPad, 0) + controlsPlaceable.place(startPad + textPlaceable.width, (totalHeight - controlsPlaceable.height) / 2) + } + } + } +} + +@Composable +private fun InputTextField( + value: String, + onValueChange: (String) -> Unit, + enabled: Boolean, + placeholder: String, + canSubmit: Boolean, + onSubmit: () -> Unit, + alignment: Alignment, + onFocusChanged: (Boolean) -> Unit, + onLineCountChanged: (Int) -> Unit, +) { + // 입력바 SVG는 라이트 배경이므로 시스템 다크 모드와 무관하게 전경을 검정으로 고정한다. + val textStyle = GamssTheme.typography.body4Medium.copy(color = GamssTheme.colors.black) + BasicTextField( + value = value, + onValueChange = onValueChange, + modifier = Modifier.onFocusChanged { onFocusChanged(it.isFocused) }, + enabled = enabled, + singleLine = false, + minLines = 1, + maxLines = INPUT_MAX_LINES, + onTextLayout = { onLineCountChanged(it.lineCount.coerceIn(1, INPUT_MAX_LINES)) }, + textStyle = textStyle, + cursorBrush = SolidColor(GamssTheme.colors.black), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Send), + keyboardActions = KeyboardActions(onSend = { if (canSubmit) onSubmit() }), + decorationBox = { innerTextField -> + // 자리표시자와 입력칸을 겹쳐 놓아야 한다. 컨테이너 없이 나란히 두면 입력칸이 밀려난다. + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = alignment, + ) { + if (value.isEmpty()) { + GamssText( + text = placeholder, + style = GamssTheme.typography.body4Medium, + color = GamssTheme.colors.gray500, + ) } - }, - ) + innerTextField() + } + }, + ) +} + +@Composable +private fun ControlsRow( + isExpanded: Boolean, + canSubmit: Boolean, + trailingContentDescription: String?, + onTrailingClick: () -> Unit, + trailingAction: @Composable (() -> Unit)?, +) { + Row(verticalAlignment = Alignment.CenterVertically) { trailingAction?.invoke() - Box( - modifier = Modifier - .size(SendButtonTouchSize) - .clickable(enabled = canSubmit, role = Role.Button, onClick = onTrailingClick), - contentAlignment = Alignment.Center, - ) { - Image( - painter = painterResource(if (canSubmit) GamssIcons.SendButtonOn else GamssIcons.SendButtonOff), - contentDescription = trailingContentDescription, - modifier = Modifier.size(SendButtonSize), - ) + if (trailingAction != null) { + // collapsed 에서는 전송 버튼 바로 앞에 좁은 간격만, expanded 에서는 양 끝으로 벌어진다. + if (isExpanded) { + Spacer(modifier = Modifier.weight(1f)) + } else { + Spacer(modifier = Modifier.width(ControlsRowGap)) + } } + SendButton(canSubmit = canSubmit, contentDescription = trailingContentDescription, onClick = onTrailingClick) } } + +@Composable +private fun SendButton( + canSubmit: Boolean, + contentDescription: String?, + onClick: () -> Unit, +) { + Box( + modifier = Modifier + .size(SendButtonTouchSize) + .clickable(enabled = canSubmit, role = Role.Button, onClick = onClick), + contentAlignment = Alignment.Center, + ) { + Image( + painter = painterResource(if (canSubmit) GamssIcons.SendButtonOn else GamssIcons.SendButtonOff), + contentDescription = contentDescription, + modifier = Modifier.size(SendButtonSize), + ) + } +} + +/** 4배 해상도로 export한 손그림 테두리(collapsed 366x64 / expanded 368x152). 애니메이션 중엔 + * [gamssSketchyBorder] 로 대신 그리고, 정착한 뒤에만 실제 에셋을 쓴다. */ +@Composable +private fun Modifier.inputBarBackground(useFocusArt: Boolean, isAnimating: Boolean): Modifier = + if (isAnimating) { + this.background(GamssTheme.colors.white) + .gamssSketchyBorder(color = GamssTheme.colors.black, width = InputBarBorderWidth) + } else { + this.paint( + painter = painterResource(if (useFocusArt) R.drawable.bg_input_box_focus else R.drawable.bg_input_box), + contentScale = ContentScale.FillBounds, + ) + } diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyShape.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyShape.kt new file mode 100644 index 00000000..7b8e9c21 --- /dev/null +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyShape.kt @@ -0,0 +1,119 @@ +package com.gamss.android.core.designsystem.component + +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import kotlin.math.sqrt +import kotlin.random.Random + +// Figma 목업의 입력바/감정 패널 테두리는 완전한 직선 사각형이 아니라 손으로 그은 듯 아주 +// 은은하게 굽이친다. 네 변을 각각 여러 점으로 쪼개 양방향으로 아주 작게 흔든 다음, 그 점들을 +// 매끄러운 곡선으로 잇는다. 매 recomposition 마다 다시 흔들리면 타이핑할 때마다 테두리가 +// 꿈틀거려 보이므로, 고정 시드로 항상 같은 흔들림을 재현한다. +// 각 점의 흔들림이 서로 독립적인 난수라 Catmull-Rom 접선이 이웃 점 사이의 편차를 그대로 +// 증폭시킨다 - 점 단위 진폭보다 눈에 보이는 물결이 훨씬 커지므로, Figma의 은은한 흔들림에 +// 맞추려면 진폭 자체를 훨씬 작게 잡아야 한다. +private val EdgeJitter = 0.35.dp +private const val EDGE_MID_POINTS = 2 +private const val DEFAULT_SEED = 20240521 + +/** + * Figma의 손그림 선은 자유 경로를 직접 stroke 한다. 선의 중심과 모든 굴곡을 컴포넌트 안에 + * 두어 draw canvas 가장자리에서 잘리지 않도록 한다. + */ +fun Modifier.gamssSketchyBorder( + color: Color, + width: Dp, + edgeJitter: Dp = EdgeJitter, + seed: Int = DEFAULT_SEED, +): Modifier = drawWithCache { + val strokeWidth = width.toPx() + val path = buildSketchyRectPath( + size = size, + edgeJitterPx = edgeJitter.toPx(), + random = Random(seed), + insetPx = strokeWidth / 2f, + ) + onDrawWithContent { + drawContent() + drawPath(path = path, color = color, style = Stroke(width = strokeWidth)) + } +} + +private fun buildSketchyRectPath( + size: Size, + edgeJitterPx: Float, + random: Random, + insetPx: Float = 0f, +): Path { + val strokeInset = insetPx.coerceAtLeast(0f) + // 선의 반 폭과 진폭을 모두 확보해야 양방향 굴곡도 캔버스 밖으로 나가지 않는다. + val safeInset = strokeInset + edgeJitterPx + fun jitter(amount: Float) = (random.nextFloat() - 0.5f) * 2f * amount + val corners = listOf( + Offset(safeInset, safeInset), + Offset(size.width - safeInset, safeInset), + Offset(size.width - safeInset, size.height - safeInset), + Offset(safeInset, size.height - safeInset), + ) + + // 코너 사이를 곡선으로 부드럽게 잇기 위해, 각 변의 점들을 먼저 하나의 목록으로 모은다. + val points = mutableListOf() + corners.indices.forEach { i -> + val start = corners[i] + val end = corners[(i + 1) % corners.size] + points += start + val dx = end.x - start.x + val dy = end.y - start.y + val len = sqrt(dx * dx + dy * dy).let { if (it == 0f) 1f else it } + // 안전 여백 안에서 양방향으로 흔들어 Figma의 손그림 선처럼 보이게 한다. + val nx = -dy / len + val ny = dx / len + for (m in 1..EDGE_MID_POINTS) { + val t = m / (EDGE_MID_POINTS + 1f) + val amount = jitter(edgeJitterPx) + points += Offset(start.x + dx * t + nx * amount, start.y + dy * t + ny * amount) + } + } + + val path = Path() + path.moveTo(points[0].x, points[0].y) + fun Offset.clampToOutline() = Offset( + x = x.coerceIn(strokeInset, size.width - strokeInset), + y = y.coerceIn(strokeInset, size.height - strokeInset), + ) + + // Catmull-Rom 곡선을 cubic Bézier로 바꾼다. 제어점만 이용하는 방식과 달리 모든 생성점을 + // 실제로 지나가므로, Figma의 굴곡 강도가 Compose에서 약해지지 않는다. + for (i in 0 until points.size) { + val previous = points[(i - 1 + points.size) % points.size] + val current = points[i] + val next = points[(i + 1) % points.size] + val following = points[(i + 2) % points.size] + val firstControl = Offset( + current.x + (next.x - previous.x) / 6f, + current.y + (next.y - previous.y) / 6f, + ).clampToOutline() + val secondControl = Offset( + next.x - (following.x - current.x) / 6f, + next.y - (following.y - current.y) / 6f, + ).clampToOutline() + path.cubicTo( + firstControl.x, + firstControl.y, + secondControl.x, + secondControl.y, + next.x, + next.y, + ) + } + path.close() + + return path +} diff --git a/core/designsystem/src/main/res/drawable-nodpi/bg_tab_bar.png b/core/designsystem/src/main/res/drawable-nodpi/bg_tab_bar.png deleted file mode 100644 index e30d3a0ae033b0087353d20e3593bf095bca8e32..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5880 zcmZu#2~<+s`nS4W>o(YMZ&_C{ZLl&;vs4_w%Cf=XmNQNn+2EKWDk|BnS6ZoL8kmOm zzm}#5gbApK)+?h#p>n`9v>b4tBveFX@EySZYrVA|Yr#2t@9*sW4Zq*_eTQ3rdAP3n zZS!wBIy$S4A9Fdaqw|XkaQ|}IufXpcdUzmkS$_Fg076H{*iQXjbOdX42AEugIPH2! zhr}@$0|vibIOu**M~8x0p$JW?oceD6NK?!J8NMc_-0x25+|>&i3z zH`dJt1^@l~0VCQ{CoZ^pTaL@(ZOabvbY@?P(*1vtN~LmFi9*pZ`{U~?6%u8EVK5kb z+5b#_n%vn>CbQuTK1jjriL#N8NK%yHd}M$kRW)I`GPIao&5xm93?TaI3Z3rH6iTc6 zsyX%e0x89j=c)`s<)_{M-WAPKxDA`_CCkYIx&;O$pA`>6V!`QFp?Z3HuB=C8)@6*_ zV-*osU~)nEL>h+lLkR%8zA|Q)ZabHSN(1si_->=t2^uc5m z5sd>fyzoXraayD2aO&=0nZ(=BkM`-rNp{7YX>3!M14b!geOQ+7H14b zem$6Lg!`wsU2IS#i#04NZ1UyX`$9P2H>jqZPBV%s8ZRV+S0i%58+SBMJ^fY$GcEQ4 zl?G+7;MT~Pwl=@2lOtVxL>nx5zBKD_nw91-f0}yTVyjA%6~vL6IxCml&OB%g&ffRMa>P7M)3{PK`*XljPz; zGzZ)j5yqxYV^j@;ZI4{f6!pZ~N5|64%` z13&u^w^5eiVKyoTSx^n3f&z*7utOjU;_Gg-a4dOwb_Gm>@sMPuYnblYX`(!ma6M$=@cF zPS~%tdQCoaFr;{m;E%eaC`g4b%7O0IsdTidaaB%+!oTxP^wO)jjZA+MtH(x6Z%L(z zLblSbPhM`BwbwIF5yf{`+6uayP>tBL5zErnW}LvMy3ff*N~DW&CuhA>@tZD8vG+Em z9Y1Ec0%lvBuJDdC5C~qszGhAomk}i!;e%GJm0^dfDwbQZOTXpwvcmrCO+@3H)3TYx z47@$bNI$ZM>$@Me2nwaPt{pd&9s}ZlXAT#h+*&e%L=#i&Hk;uPLvV1Gn+X`{ zUWq)5PoN4)k&fd+|BRtTZY0Lt3`3pBk=vI=SADQ^%i?i)|K0`W1~AKpBI5||b~DN@ zVHJ{TVD0AC!mFPc;dXp)c6F4(c%!uwl)9BSbHO)nQ)WVAUPYZ({AIVDwscYg7D%56 zF*k3`#PS8*s;Evgc>g=eDd7|qlo?LB()jJpX+1aPvzGOzXgz_5;cDB*Gb8?}Dl)v$ zqqCGa?T()(?q6x$^T**i&xzTRuVTeJUUNlSN#wVBYaiV=e*12y}uWt3H6OlCy zk^?(Jn?mE=bL3w-AS9#*KM?|nHSUU)NW67#?8}G%W71~_OZwq^bD{Gx?j7=n$X@_r zd|`Hc;r0VGxvq13`CCUPCv)ryVEQU2O2rc@W7FlmFDHBQE<9kBu~J)?04qV&`_}C+ zI~<86#wBZrb+PSbwaD0H%VJA!H-ezaCxk_xHt#$V_n8Eg2QsnlkX6j9r|w}Tkb8{VzhZZQLR@{ zA*Fx?%w)W+@+D~ZXIo)X;1AHw+c4!H4y!#e`U$dS^2d+M&n!Kw)m_5-yy0$TvPRR+ zm6@Z)8vCN^fEAtEeTz+ii!*&ZIK1+EA|ri+#-jTBBgcte8OyX5ZC;gGGqKZp(FC+Xel<4M`Z)|w4qh6h`oJqV9XcKfOQrd638?_504`oQ|5jhb^_bOt~i z9Swf^jh2>@Vh>ABN}>ZIfLorM`gA&IENH&ye2$~5Q8|8pMdj6H!20_;ZRZg+Raw{o z3$?gC)*<~{Mr+LmjX7tjFvEua?}^$VpxO9?AR{Bp5)R@Nm}HdGX{@v)zo7%L(VlZadZuzdNel(&AL4>h4}TpUGr8%?<(|t>=e5jEtaHK0`%I zOzJY;CNhH6vO)Eq$4Wg62WSjn@S?M}uI`gs007s-J~hmz-So65)<)Csg3%h(l`9Z` ze^Y(6e@An*&LtWQK25@Z(sh}PPMnI?pvB@8li)_v%LKS&xTiyF+{*~6eR5Op_ZhDI z>f2YZUcEH>f8mMgQ{}4y`uh8g(EeI$Ujk9ozgeo+&*z&1N9kDb1Dsqm&Fa-+!=)}K zEMGsa1g4$2SY%@7Zf>IntVP3sy)Uoz_G;+k%$?T6i^QqskYEp>8ZYwWHKYmGZWWWA zofRN3-X~j##{Mn$Oi#7tRcUY`xcIiUy}ez1|40{5`-?J#*$3Sl8yjm1)qtufJUh!< zpv46JbCiwliko5nYGtRth{fVBXv1BI-`DK=}h4-UYO11JyX}(B=q*uFzx^-cR$`X+`|c+3L7H zvUU9lk3MsH@AsG2Bq>bwVWSVg|I5fo;@F{_#U=|m^!@YjVi$=_hEikW{T;yh*QLX& z88=(iI|Q@wP{8gDY#@a^yR3gxow@+nZQp%wlZ}QXQFSn4&W7JKsyR7q-sh#SogbK) z0WdQ-SgP^RWi)o!*ZP6hTXz;hpt9X^@4CqXglK!_hi>^KG58A@(4^@50IlLNce{uS zw0dB@Z8zffwC33|&{m6H+y&I5)jHYp_t(BAV?^>^Ya-%m3)JttI$4@C=Zp2pMm2 zSC4^grWPvOXeVS#JOc4ty*k=9baxYQy$Z_ayJ;CS+I7x(8+X&kIc?&5urhOq(NYuB ztj@ugFCATCQUXnf%)R3CHQoa%jXgU5mM@O@$yW>Vkf9J0ImiTfd2}^eD7_RIadn?4 zkueA|(K1T}1a;l;Q*{A#j>XEb3_6{z=>v+*r_7TiTB6^B8wn*Tlvbk%euqPFvt*2CxCn3w`GK!^m#y?_R6XTWSmIb0BgK{&!=K zsL^z}0_Jr+pJNQb`GnuPDJ`hQPbrt)L-`p7`j z^ZdVo@Lwqc=xQ>;>|nK@qBf|F!&Ut;^;FdXn*|!%>EY@<7JCoo)mEjE8Z5W~zvdmO zU*r4xQ$+o>9GpMVSB|_d!nvZ*NlCVIvcieNH*<}SCwiXSFNqFLzv5I@CD*)C+YzDz zLfzpZp6*r_gOX63@beLYM<7^P+FN0EFI#RGBMLge9To%qjrYy+$)J)*@KYHkJvN#Q zV(ji(E>3%gGGZqwuPEdd=(TK5pQcbwDP^Al`*;;0zHq#L0A)pO^zVboAtW-A)SS?X z%*L;`!UudFiDQV7!tOunJZ|^4AYT{2HXscB+I=u>=Z~T{;q7tH)65HDegp_HzP`Mr z%YSo30AVB?O!6ALlYVFTaf3?zAH%AV$8a}c&${9G&BBZdN0$9gQsj6oEAk*1T0Pfdl-)=Nqrr;l4rM;?0XdYst2{2Cl?O(itH`QXqwsNdeZcE$F8vg+nMn7Rv`CW1xmWOK@NpO!ePnE%}=no(E z2r7i*-pt?{5+bhue*OMpGq0w#eTSS(BmSfc%4zEdk+h4{O9Ysxxql_ud_55jlJZWH zz*M|r*BeaqP!K6llJ1x>C_T%xy^pP!iEDg=Z;5Rv!;KVVc#U18mI_t+au1sesjptg z{#~0>5nW<7kw>xdAWe-#Iq-yHFpY3COusGmGpE~X=eSDoRSL~d{XyGo{pvyXN0EEg zBlvqbXQ^;|?^BOIT12NT9wFNM=MEaOx!%T#5PZaR18=5mH(_?L&X0Io<$c3e!Vl9A zX=s9Fzdz|v-hV=M>V3UVF?{^&EE6lD7?DKC;7jjVVQwDnfscvzrHC9Kv*aH{>SE^) z-tal*&?E85Mkul>gdrN7>yN;rm{v*&D@keP_;|~*cG&l7tJr`pR?da|^fOmhjQrwl zI|db2&ut~r+8cct;_ZS7k-zJkGe;%Vsn7S8Y$9JOYB-NB%ANmwg^Ql_ZR$jZsu=K3oeTHRd@{p>rLL|XmK!#*%_qjME!vi6b5zzgjcZa2k8b|4eaq=4h5D5~ z;P~o@p%XhS9RAReZ5}A-w<{w$4*0*Z+Y!@Vu%vW5=SFS`D=`H>U49_I^Wo0uV|k{{ zHB5=Bbt+}$*1@YI8)~|wQxMFJXAnW=SgXgASdybh+Qlq6fpr!k7(%{!J9GwBGMq-c zc7x)Uzdt`LhWswIPhhz=%YdL;Rx<;y@;kd3HcYAPjdtJR+qB+vNAwd1&ya_IZkD0+ z%Sh1r*~U~=2Cy0~Dc{Ag>i{#b)?#2$Y|>*pXM>((^Fkycax_ z%com0>l$P6IB`QJhF%NmLYJ^-FAqTV;sY?3ZgYugBl|3A%(mAqmym{Hdp~@=4JTKa zQ^9pZ+k*P5u%cwrn;L;I5zUdCuvu2RX_IqhzWs~c;1ou*9-t~BKUFXCt$%$a@ z!^n@8A5?F7O|wg$s`PVM+dOkn6DWvlBdDh5*AJf&R}g18wEz21oN}wS7%`sJ-39i+ zvN+{o|2VCuKM=OIolgrX;oWXpbu+9WIQ=yb4jmO@YWywOV56;rycwGxTfg#5>lC6m zim0oIJa>|?vSz%^H`sAT+0f?8xAYa1`WDB3iVikI%#GBP4^(@hQdj6|n%Aq5Kj!bv Wbamed6j9VfIex^$g>>l5wf_M8HWbDH diff --git a/core/designsystem/src/main/res/drawable/bg_input_box.xml b/core/designsystem/src/main/res/drawable/bg_input_box.xml new file mode 100644 index 00000000..67e7d42a --- /dev/null +++ b/core/designsystem/src/main/res/drawable/bg_input_box.xml @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/core/designsystem/src/main/res/drawable/bg_input_box_focus.xml b/core/designsystem/src/main/res/drawable/bg_input_box_focus.xml new file mode 100644 index 00000000..508cb707 --- /dev/null +++ b/core/designsystem/src/main/res/drawable/bg_input_box_focus.xml @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt b/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt index 022aa648..7576ca0a 100644 --- a/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt +++ b/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt @@ -2,6 +2,7 @@ package com.gamss.android.feature.home import android.widget.Toast import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope @@ -12,6 +13,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -19,14 +21,19 @@ import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role @@ -41,7 +48,6 @@ import com.gamss.android.core.designsystem.component.GamssCharacterPickerItem import com.gamss.android.core.designsystem.component.GamssIconButton import com.gamss.android.core.designsystem.component.GamssIcons import com.gamss.android.core.designsystem.component.GamssInputBar -import com.gamss.android.core.designsystem.component.GamssInputBarHeight import com.gamss.android.core.designsystem.component.GamssLogo import com.gamss.android.core.designsystem.component.GamssMarkerHighlight import com.gamss.android.core.designsystem.component.GamssPaperSlip @@ -50,6 +56,7 @@ import com.gamss.android.core.designsystem.component.GamssTape import com.gamss.android.core.designsystem.component.GamssText import com.gamss.android.core.designsystem.component.GamssTopBar import com.gamss.android.core.designsystem.theme.GamssTheme +import com.gamss.android.domain.conversation.takeWithinMessageLimit import com.gamss.android.domain.emotion.EmotionCharacter import org.orbitmvi.orbit.compose.collectAsState import org.orbitmvi.orbit.compose.collectSideEffect @@ -77,6 +84,7 @@ fun HomeScreen( HomeActions( onSettingClick = viewModel::navigateToSetting, onInputChange = viewModel::onInputChange, + onMessageLengthExceeded = viewModel::onMessageLengthExceeded, onSubmit = viewModel::onSubmit, onEmotionPickerToggle = viewModel::onEmotionPickerToggle, onEmotionToggle = viewModel::onEmotionToggle, @@ -90,6 +98,7 @@ fun HomeScreen( private data class HomeActions( val onSettingClick: () -> Unit, val onInputChange: (String) -> Unit, + val onMessageLengthExceeded: () -> Unit, val onSubmit: () -> Unit, val onEmotionPickerToggle: () -> Unit, val onEmotionToggle: (EmotionCharacter) -> Unit, @@ -101,9 +110,7 @@ private fun HomeContent( actions: HomeActions, modifier: Modifier = Modifier, ) { - val pickerItems = remember(state.selectedCharacters) { state.selectedCharacters.toPickerItems() } - - Box(modifier = modifier.fillMaxSize()) { + Box(modifier = modifier.fillMaxSize().dismissKeyboardOnTapOutside()) { HomeDecorations() Column(modifier = Modifier.fillMaxSize()) { @@ -131,55 +138,96 @@ private fun HomeContent( Spacer(modifier = Modifier.height(GreetingToInputGap)) - // 패널은 Popup 으로 띄운다. 흐름에 넣으면 펼칠 때마다 인사말과 입력바가 위로 밀린다. - Box(modifier = Modifier.fillMaxWidth().padding(horizontal = InputBarHorizontalPadding)) { - GamssInputBar( - value = state.input, - onValueChange = actions.onInputChange, - onTrailingClick = actions.onSubmit, - placeholder = stringResource(R.string.home_input_placeholder), - trailingContentDescription = stringResource(R.string.home_input_submit_description), - enabled = !state.isSending, - trailingAction = { - CharacterPickerToggle( - expanded = state.isEmotionPickerExpanded, - onClick = actions.onEmotionPickerToggle, - ) - }, - modifier = Modifier.fillMaxWidth(), - ) + HomeInputSection(state = state, actions = actions) - if (state.isEmotionPickerExpanded) { - val pickerOffset = with(LocalDensity.current) { - IntOffset(x = 0, y = (GamssInputBarHeight + PickerTopGap).roundToPx()) - } - Popup( - alignment = Alignment.TopEnd, - offset = pickerOffset, - onDismissRequest = actions.onEmotionPickerToggle, - ) { - GamssCharacterPicker( - items = pickerItems, - onToggle = actions.onEmotionToggle, - ) + Spacer(modifier = Modifier.weight(GREETING_BOTTOM_WEIGHT)) + } + } +} + +@Composable +private fun HomeInputSection( + state: HomeState, + actions: HomeActions, +) { + val pickerItems = remember(state.selectedCharacters) { state.selectedCharacters.toPickerItems() } + var inputBarHeightPx by remember { mutableIntStateOf(0) } + + // 한 번 알린 뒤에는 계속 이어 쳐도 다시 알리지 않고, 길이를 줄였다 다시 넘길 때만 알린다. + var lengthWarned by remember { mutableStateOf(false) } + + // 패널은 Popup 으로 띄운다. 흐름에 넣으면 펼칠 때마다 인사말과 입력바가 위로 밀린다. + Box(modifier = Modifier.fillMaxWidth().padding(horizontal = InputBarHorizontalPadding)) { + GamssInputBar( + value = state.input, + // ViewModel도 같은 도메인 정책으로 방어하지만, IME 입력 순간에도 140자를 넘겨 + // 화면에 보이지 않도록 UI 경계에서 먼저 제한한다. + onValueChange = { + val limited = it.takeWithinMessageLimit() + if (limited != it) { + if (!lengthWarned) { + actions.onMessageLengthExceeded() + lengthWarned = true } + } else { + lengthWarned = false } - } + actions.onInputChange(limited) + }, + onTrailingClick = actions.onSubmit, + placeholder = stringResource(R.string.home_input_placeholder), + trailingContentDescription = stringResource(R.string.home_input_submit_description), + enabled = !state.isSending, + trailingAction = { + CharacterPickerToggle( + expanded = state.isEmotionPickerExpanded, + onClick = actions.onEmotionPickerToggle, + ) + }, + modifier = Modifier + .fillMaxWidth() + .onSizeChanged { inputBarHeightPx = it.height }, + ) - Spacer(modifier = Modifier.weight(GREETING_BOTTOM_WEIGHT)) + if (state.isEmotionPickerExpanded) { + val pickerOffset = with(LocalDensity.current) { + // 입력창 높이가 64dp/149dp+ 로 달라지므로 겹침 폭만 고정해 실측 높이 기준으로 잡는다. + IntOffset( + x = PickerHorizontalInset.roundToPx(), + y = (inputBarHeightPx.toDp() - PickerOverlapHeight).roundToPx(), + ) + } + Popup( + alignment = Alignment.TopEnd, + offset = pickerOffset, + onDismissRequest = actions.onEmotionPickerToggle, + ) { + GamssCharacterPicker(items = pickerItems, onToggle = actions.onEmotionToggle) + } } } } +/** 입력창 바깥을 누르면 포커스를 내려놓아야 collapsed 로 돌아가고 키보드도 닫힌다. */ +@Composable +private fun Modifier.dismissKeyboardOnTapOutside(): Modifier { + val focusManager = LocalFocusManager.current + return clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = { focusManager.clearFocus() }, + ) +} + @Composable private fun CharacterPickerToggle( expanded: Boolean, onClick: () -> Unit, ) { Row( - // fillMaxHeight 를 쓰면 안 된다. 입력바 높이가 내용 기반이라 자식이 최대 제약을 먹고 화면을 채운다. - // 터치 영역은 세로 여백으로 벌린다. + // collapsed 에서만 48dp 까지 넓어진다. expanded 컨트롤 행은 32dp 로 고정 측정돼 그만큼만 잡힌다. modifier = Modifier + .heightIn(min = 48.dp) .clickable(role = Role.DropdownList, onClick = onClick) .padding(horizontal = PickerToggleHitPadding, vertical = PickerToggleVerticalPadding), verticalAlignment = Alignment.CenterVertically, @@ -188,13 +236,14 @@ private fun CharacterPickerToggle( GamssText( text = stringResource(R.string.home_character_picker), style = GamssTheme.typography.body4Medium, - color = GamssTheme.colors.gray900, + // 흰 입력바 위에서 다크 테마의 흰 전경색이 사라지지 않게 Figma 전경색을 유지한다. + color = GamssTheme.colors.black, maxLines = 1, ) Icon( painter = painterResource(GamssIcons.RightChevron), contentDescription = null, - tint = GamssTheme.colors.gray900, + tint = GamssTheme.colors.black, // 아래위 셰브론 에셋이 없어 오른쪽 셰브론을 돌려 쓴다. 글리프가 뷰포트 중심에서 벗어나 있어 // 회전축을 글리프 자신의 중심으로 옮긴다. 그러지 않으면 펼칠 때마다 위아래로 튄다. modifier = Modifier @@ -281,14 +330,19 @@ private val HeaderEndPadding = 8.dp private val GreetingStartPadding = 27.dp private val GreetingWordGap = 6.dp private val GreetingToInputGap = 22.dp -private val InputBarHorizontalPadding: Dp = 20.dp -private val PickerTopGap = 4.dp +// Figma 입력창은 402dp 화면에서 366dp 폭이므로 좌우 여백은 각각 18dp다. +private val InputBarHorizontalPadding: Dp = 18.dp + +private val PickerHorizontalInset = (-61).dp + +// 64dp/149dp 두 입력창 높이 모두에서 Figma가 보여 준 9~10dp 겹침 폭. +private val PickerOverlapHeight = 9.dp private val PickerToggleGap = 4.dp private val PickerToggleHitPadding = 4.dp -// 라벨 줄높이가 20dp 라 위아래 14dp 씩 더하면 터치 영역이 48dp 가 된다. -private val PickerToggleVerticalPadding = 14.dp +// SVG 입력바 내부 높이에 맞춰 라벨의 시각 높이만 사용한다. +private val PickerToggleVerticalPadding = 0.dp private val PickerToggleChevronSize = 16.dp private const val CHEVRON_ROTATION = 90f @@ -331,6 +385,7 @@ private fun previewState() = HomeState(isLoading = false, nickname = "이소연" private fun previewActions() = HomeActions( onSettingClick = {}, onInputChange = {}, + onMessageLengthExceeded = {}, onSubmit = {}, onEmotionPickerToggle = {}, onEmotionToggle = {}, diff --git a/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt b/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt index ee8ab696..2d4c2765 100644 --- a/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt +++ b/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt @@ -17,6 +17,7 @@ import javax.inject.Inject internal const val LAST_CHARACTER_BLOCKED = "한 명은 남겨 주세요" internal const val SEND_FAILED = "보내지 못했어요. 잠시 후 다시 시도해 주세요." +internal const val MESSAGE_LENGTH_EXCEEDED = "메시지는 140자까지 입력할 수 있어요." @HiltViewModel class HomeViewModel @Inject constructor( @@ -35,6 +36,10 @@ class HomeViewModel @Inject constructor( reduce { state.copy(input = text.takeWithinMessageLimit()) } } + fun onMessageLengthExceeded() = intent { + postSideEffect(HomeSideEffect.ShowToast(MESSAGE_LENGTH_EXCEEDED)) + } + fun onEmotionPickerToggle() = intent { reduce { state.copy(isEmotionPickerExpanded = !state.isEmotionPickerExpanded) } } From 0f421b8a591678cce90a9c9163a50dde7c5d5c3d Mon Sep 17 00:00:00 2001 From: soyeonLee <109227292+soyeonLee126@users.noreply.github.com> Date: Mon, 17 Aug 2026 17:52:48 +0900 Subject: [PATCH 02/20] =?UTF-8?q?fix:=20=ED=95=98=EB=8B=A8=20=EB=B0=94=20?= =?UTF-8?q?=EC=9D=B8=EC=85=8B=EA=B3=BC=20=EC=9E=85=EB=A0=A5=EC=B0=BD=20?= =?UTF-8?q?=ED=99=95=EC=9E=A5=20=EC=A0=84=ED=99=98=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=96=B4=EA=B8=8B=EB=82=98=EB=8D=98=20=EB=B6=80=EB=B6=84?= =?UTF-8?q?=EC=9D=84=20=EA=B3=A0=EC=B9=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 하단 바 배경을 인셋 바깥에 둬 내비게이션 영역까지 흰색이 깔리게 하고, 네 변에 그려지던 테두리를 위쪽 구분선만 남긴다 - 입력창 확장 첫 프레임에 텍스트 자리가 0dp 로 잡혀 컨트롤 행 위로 넘치던 것을 잘라 낸다 - expanded 컨트롤 행은 폭이 고정 측정되므로, trailingAction 이 없어도 전송 버튼이 오른쪽 끝에 붙도록 남는 자리를 밀어낸다 --- .../designsystem/component/GamssBottomBar.kt | 15 +++++++++++---- .../designsystem/component/GamssInputBar.kt | 18 ++++++++++-------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssBottomBar.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssBottomBar.kt index 3bbac3c6..7e7250f4 100644 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssBottomBar.kt +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssBottomBar.kt @@ -1,7 +1,6 @@ package com.gamss.android.core.designsystem.component import androidx.compose.foundation.background -import androidx.compose.foundation.border import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -18,6 +17,8 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role @@ -29,6 +30,7 @@ private val ItemRowWidth = 255.dp private val ItemIconSize = 26.dp private val ItemLabelGap = 4.dp private val BottomBarStroke = Color(0xFFDCE0E6) +private val BottomBarStrokeWidth = 1.dp private val SelectedColor = Color(0xFF303136) private val UnselectedColor = Color(0xFFAAAFBD) @@ -43,10 +45,15 @@ fun GamssBottomBar( Box( modifier = modifier .fillMaxWidth() - .navigationBarsPadding() - .height(BottomBarHeight) + // 배경과 구분선을 인셋보다 바깥에 둬야 내비게이션 영역까지 흰색이 깔린다. .background(Color.White) - .border(1.dp, BottomBarStroke), + .drawBehind { + // 화면 폭을 꽉 채우는 바라 좌우·아래 선은 화면 테두리에 붙어 버린다. 위쪽만 긋는다. + val stroke = BottomBarStrokeWidth.toPx() + drawRect(color = BottomBarStroke, size = Size(size.width, stroke)) + } + .navigationBarsPadding() + .height(BottomBarHeight), ) { Row( modifier = Modifier diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt index e908d1e1..dba76496 100644 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt @@ -23,6 +23,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.draw.paint import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.SolidColor @@ -110,7 +111,10 @@ fun GamssInputBar( Layout( modifier = modifier .height(animatedHeight) - .inputBarBackground(useFocusArt = useFocusArt, isAnimating = isAnimating), + .inputBarBackground(useFocusArt = useFocusArt, isAnimating = isAnimating) + // 확장 첫 프레임엔 높이가 아직 collapsed 라 텍스트 자리가 0dp 로 잡힌다. + // 그대로 두면 넘친 텍스트가 컨트롤 행 위로 비어져 나온다. + .clipToBounds(), content = { InputTextField( value = value, @@ -225,13 +229,11 @@ private fun ControlsRow( ) { Row(verticalAlignment = Alignment.CenterVertically) { trailingAction?.invoke() - if (trailingAction != null) { - // collapsed 에서는 전송 버튼 바로 앞에 좁은 간격만, expanded 에서는 양 끝으로 벌어진다. - if (isExpanded) { - Spacer(modifier = Modifier.weight(1f)) - } else { - Spacer(modifier = Modifier.width(ControlsRowGap)) - } + // expanded 행은 폭이 고정 측정돼 남는 자리가 생긴다. trailingAction 이 없어도 밀어내지 않으면 + // 전송 버튼이 왼쪽 끝에 붙는다. collapsed 에서는 버튼 앞 간격만 둔다. + when { + isExpanded -> Spacer(modifier = Modifier.weight(1f)) + trailingAction != null -> Spacer(modifier = Modifier.width(ControlsRowGap)) } SendButton(canSubmit = canSubmit, contentDescription = trailingContentDescription, onClick = onTrailingClick) } From e2379595270b73216a87f2362a7babc7e3629fc2 Mon Sep 17 00:00:00 2001 From: soyeonLee <109227292+soyeonLee126@users.noreply.github.com> Date: Mon, 17 Aug 2026 18:40:59 +0900 Subject: [PATCH 03/20] =?UTF-8?q?refactor:=20=EC=9E=85=EB=A0=A5=EC=B0=BD?= =?UTF-8?q?=20=EC=A3=BC=EB=B3=80=20=EA=B5=AC=EC=84=B1=EC=9A=94=EC=86=8C?= =?UTF-8?q?=EB=A5=BC=20=EB=94=94=EC=9E=90=EC=9D=B8=EC=8B=9C=EC=8A=A4?= =?UTF-8?q?=ED=85=9C=EC=9C=BC=EB=A1=9C=20=EB=AA=A8=EC=9C=BC=EA=B3=A0=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=EC=9D=84=20=EA=B1=B7=EC=96=B4=EB=83=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 라벨 + 회전 셰브론 토글을 GamssDisclosureToggle 로 디자인시스템에 옮긴다. 셰브론 회전축 좌표는 아이콘 에셋 옆에 둬야 에셋이 바뀔 때 함께 보인다 - 하단 바가 리터럴로 갖고 있던 색을 팔레트 토큰(gray200/400/900, white)으로 되돌린다 - 길이 제한 정책을 ViewModel 로 일원화한다. 화면에서 한 번 더 자르던 중복과 경고 래치를 없애고, 안내 문구는 MAX_MESSAGE_LENGTH 를 쓴다 - Layout 의 collapsed/expanded 분기가 측정·배치 뼈대를 두 벌 갖고 있던 것을 한 벌로 합친다 - 이름이 동작을 가리키도록 onTrailingClick -> onSend, trailingAction -> beforeSendSlot, GamssInputBarHeight -> CollapsedInputBarHeight 로 바꾼다 - 값이 같던 SendButtonTouchSize 를 없애고 래퍼 Box 도 걷어낸다 - 화면 전체 clickable 이 레이블 없는 클릭 노드를 시맨틱 트리에 넣던 것을 탭 제스처로 바꾼다 - Shape 가 없어진 GamssSketchyShape.kt 를 GamssSketchyBorder.kt 로 옮긴다 --- .../designsystem/component/GamssBottomBar.kt | 15 +- .../component/GamssComponentPreviews.kt | 4 +- .../component/GamssDisclosureToggle.kt | 70 ++++++++++ .../designsystem/component/GamssInputBar.kt | 130 ++++++++---------- ...sSketchyShape.kt => GamssSketchyBorder.kt} | 24 +--- .../src/main/res/drawable/bg_input_box.xml | 3 +- .../main/res/drawable/bg_input_box_focus.xml | 3 +- .../gamss/android/feature/home/HomeScreen.kt | 99 ++----------- .../android/feature/home/HomeViewModel.kt | 20 ++- 9 files changed, 171 insertions(+), 197 deletions(-) create mode 100644 core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssDisclosureToggle.kt rename core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/{GamssSketchyShape.kt => GamssSketchyBorder.kt} (69%) diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssBottomBar.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssBottomBar.kt index 7e7250f4..87d3af55 100644 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssBottomBar.kt +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssBottomBar.kt @@ -19,7 +19,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp @@ -27,12 +26,10 @@ import com.gamss.android.core.designsystem.theme.GamssTheme private val BottomBarHeight = 66.dp private val ItemRowWidth = 255.dp +private val ItemRowHeight = 44.dp private val ItemIconSize = 26.dp private val ItemLabelGap = 4.dp -private val BottomBarStroke = Color(0xFFDCE0E6) private val BottomBarStrokeWidth = 1.dp -private val SelectedColor = Color(0xFF303136) -private val UnselectedColor = Color(0xFFAAAFBD) /** 화면이 edge-to-edge 라 시스템 내비게이션 영역은 이 컴포넌트가 직접 피한다. */ @Composable @@ -42,15 +39,15 @@ fun GamssBottomBar( onItemClick: (T) -> Unit, modifier: Modifier = Modifier, ) { + val strokeColor = GamssTheme.colors.gray200 Box( modifier = modifier .fillMaxWidth() // 배경과 구분선을 인셋보다 바깥에 둬야 내비게이션 영역까지 흰색이 깔린다. - .background(Color.White) + .background(GamssTheme.colors.white) .drawBehind { // 화면 폭을 꽉 채우는 바라 좌우·아래 선은 화면 테두리에 붙어 버린다. 위쪽만 긋는다. - val stroke = BottomBarStrokeWidth.toPx() - drawRect(color = BottomBarStroke, size = Size(size.width, stroke)) + drawRect(color = strokeColor, size = Size(size.width, BottomBarStrokeWidth.toPx())) } .navigationBarsPadding() .height(BottomBarHeight), @@ -58,7 +55,7 @@ fun GamssBottomBar( Row( modifier = Modifier .width(ItemRowWidth) - .height(44.dp) + .height(ItemRowHeight) .align(Alignment.Center), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, @@ -81,7 +78,7 @@ private fun BottomBarItem( onClick: () -> Unit, modifier: Modifier = Modifier, ) { - val contentColor = if (selected) SelectedColor else UnselectedColor + val contentColor = if (selected) GamssTheme.colors.gray900 else GamssTheme.colors.gray400 val interactionSource = remember { MutableInteractionSource() } Column( diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssComponentPreviews.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssComponentPreviews.kt index cb75fae1..fb814ef4 100644 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssComponentPreviews.kt +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssComponentPreviews.kt @@ -38,7 +38,7 @@ private fun GamssInputBarEmptyPreview() { GamssInputBar( value = "", onValueChange = {}, - onTrailingClick = {}, + onSend = {}, placeholder = "무슨 이야기를 버려볼까요?", modifier = Modifier.fillMaxWidth(), ) @@ -55,7 +55,7 @@ private fun GamssInputBarFilledPreview() { GamssInputBar( value = "오늘 발표가 너무 떨려요", onValueChange = {}, - onTrailingClick = {}, + onSend = {}, modifier = Modifier.fillMaxWidth(), ) } diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssDisclosureToggle.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssDisclosureToggle.kt new file mode 100644 index 00000000..26e9792a --- /dev/null +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssDisclosureToggle.kt @@ -0,0 +1,70 @@ +package com.gamss.android.core.designsystem.component + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.TransformOrigin +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.unit.dp +import com.gamss.android.core.designsystem.theme.GamssTheme + +private val ToggleMinTouchHeight = 48.dp +private val ToggleHitPadding = 4.dp +private val LabelChevronGap = 4.dp +private val ChevronSize = 16.dp +private const val CHEVRON_ROTATION = 90f + +// ic_right_chevron 글리프의 실제 중심. 24 뷰포트에서 stroke 포함 x 12.4~21.3, y 3.9~20.2 다. +// 회전축을 글리프 중심으로 옮기지 않으면 펼칠 때마다 위아래로 튄다. +private const val CHEVRON_CENTER_X = 16.85f / 24f +private const val CHEVRON_CENTER_Y = 12.03f / 24f + +/** + * 라벨과 펼침 상태를 가리키는 셰브론을 나란히 둔 토글. 아래위 셰브론 에셋이 없어 오른쪽 셰브론을 돌려 쓴다. + * + * 터치 영역은 [ToggleMinTouchHeight] 까지 넓히지만, 높이가 고정 측정되는 곳에 놓이면 그만큼만 잡힌다. + */ +@Composable +fun GamssDisclosureToggle( + label: String, + expanded: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier + .heightIn(min = ToggleMinTouchHeight) + .clickable(role = Role.DropdownList, onClick = onClick) + .padding(horizontal = ToggleHitPadding), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(LabelChevronGap), + ) { + GamssText( + text = label, + style = GamssTheme.typography.body4Medium, + // 흰 입력바 위에서 다크 테마의 흰 전경색이 사라지지 않게 Figma 전경색을 유지한다. + color = GamssTheme.colors.black, + maxLines = 1, + ) + Icon( + painter = painterResource(GamssIcons.RightChevron), + contentDescription = null, + tint = GamssTheme.colors.black, + modifier = Modifier + .size(ChevronSize) + .graphicsLayer { + rotationZ = if (expanded) -CHEVRON_ROTATION else CHEVRON_ROTATION + transformOrigin = TransformOrigin(CHEVRON_CENTER_X, CHEVRON_CENTER_Y) + }, + ) + } +} diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt index dba76496..9a7a84a1 100644 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt @@ -38,18 +38,15 @@ import com.gamss.android.core.designsystem.R import com.gamss.android.core.designsystem.theme.GamssTheme import kotlin.math.abs -// 실제 높이는 collapsed 64dp <-> expanded 149dp+ 로 변하므로, 바깥에서는 측정값을 써야 한다. -private val GamssInputBarHeight = 64.dp +private val CollapsedInputBarHeight = 64.dp private val InputBarStartPadding = 16.dp private val InputBarEndPadding = 16.dp private val ControlsRowGap = 4.dp -/** 전송 버튼은 배경까지 담긴 32x32 에셋이라 tint 하지 않고 그대로 그린다. */ +// 배경까지 담긴 에셋이라 tint 하지 않는다. expanded 컨트롤 행이 32dp 고정이라 +// Android 권장 터치 영역(48dp)은 주지 못하고 아이콘 크기가 곧 터치 영역이다. private val SendButtonSize = 32.dp - -// expanded 컨트롤 행이 32dp 고정이라 Android 권장 터치 영역(48dp)을 못 주고 아이콘 크기에 맞춘다. -private val SendButtonTouchSize = 32.dp private const val INPUT_MAX_LINES = 6 private val InputLineHeight = 20.dp @@ -64,36 +61,33 @@ private val ControlsRowHeight = 32.dp private val ExpandedTextAreaMaxHeight = ExpandedInputBarHeight - ExpandedTopPadding - ExpandedBottomPadding - ControlsRowHeight -// 기본 스프링(약 300ms)은 순간적으로 튀는 느낌이라, 손그림 톤에 맞게 좀 더 여유 있게 늘어나도록 늦춘다. private const val HEIGHT_ANIMATION_DURATION_MS = 350 -// 애니메이션 도중에만 쓰는 프로시저럴 테두리 굵기. 4배 에셋에서 실측한 6px = 1.5dp 에 맞춘다. +// 4배 에셋에서 실측한 6px. private val InputBarBorderWidth = 1.5.dp /** * 포커스 여부로 collapsed(한 줄) <-> expanded(텍스트 위 / 컨트롤 아래) 모양이 바뀐다. * - * Row/Column 을 if-else 로 갈아 끼우면 BasicTextField 가 매번 새 노드로 다시 만들어져 포커스를 - * 잡자마자 키보드가 닫힌다. 그래서 자식은 한 번만 컴포즈하고 배치만 커스텀 [Layout] 로 매 프레임 - * 다시 계산한다. + * Row/Column 을 갈아 끼우면 BasicTextField 가 새 노드로 다시 만들어져 포커스를 잡자마자 키보드가 + * 닫힌다. 자식은 한 번만 컴포즈하고 배치만 [Layout] 으로 다시 계산하는 이유다. */ @Composable fun GamssInputBar( value: String, onValueChange: (String) -> Unit, - onTrailingClick: () -> Unit, + onSend: () -> Unit, modifier: Modifier = Modifier, placeholder: String = "", - trailingContentDescription: String? = null, + sendContentDescription: String? = null, enabled: Boolean = true, - /** 후행 아이콘 앞에 놓이는 자리. 홈은 여기에 감정 선택 토글을 단다. */ - trailingAction: @Composable (() -> Unit)? = null, + beforeSendSlot: @Composable (() -> Unit)? = null, ) { val canSubmit = enabled && value.isNotBlank() var lineCount by remember { mutableIntStateOf(1) } var isExpanded by remember { mutableStateOf(false) } - val collapsedHeight = GamssInputBarHeight + InputLineHeight * (lineCount - 1) + val collapsedHeight = CollapsedInputBarHeight + InputLineHeight * (lineCount - 1) val expandedHeight = ExpandedInputBarHeight + (InputLineHeight * lineCount - ExpandedTextAreaMaxHeight).coerceAtLeast(0.dp) val targetHeight = if (isExpanded) expandedHeight else collapsedHeight @@ -103,8 +97,7 @@ fun GamssInputBar( label = "GamssInputBarHeight", ) - // collapsed/expanded 에셋은 세로 비율이 달라 중간 높이로 늘리면 테두리 굵기가 왜곡된다 - // (손그림이라 9-patch도 못 쓴다). 정착했을 때만 실제 에셋을 쓴다. + // 두 에셋은 세로 비율이 달라 중간 높이로 늘리면 테두리가 왜곡된다(손그림이라 9-patch 도 못 쓴다). val isAnimating = abs((animatedHeight - targetHeight).value) > 0.01f val useFocusArt = animatedHeight > (collapsedHeight + expandedHeight) / 2 @@ -112,8 +105,7 @@ fun GamssInputBar( modifier = modifier .height(animatedHeight) .inputBarBackground(useFocusArt = useFocusArt, isAnimating = isAnimating) - // 확장 첫 프레임엔 높이가 아직 collapsed 라 텍스트 자리가 0dp 로 잡힌다. - // 그대로 두면 넘친 텍스트가 컨트롤 행 위로 비어져 나온다. + // 확장 첫 프레임엔 텍스트 자리가 0dp 로 잡혀 컨트롤 행 위로 넘친다. .clipToBounds(), content = { InputTextField( @@ -122,7 +114,7 @@ fun GamssInputBar( enabled = enabled, placeholder = placeholder, canSubmit = canSubmit, - onSubmit = onTrailingClick, + onSubmit = onSend, alignment = if (isExpanded) Alignment.TopStart else Alignment.CenterStart, onFocusChanged = { isExpanded = it }, onLineCountChanged = { lineCount = it }, @@ -130,44 +122,45 @@ fun GamssInputBar( ControlsRow( isExpanded = isExpanded, canSubmit = canSubmit, - trailingContentDescription = trailingContentDescription, - onTrailingClick = onTrailingClick, - trailingAction = trailingAction, + sendContentDescription = sendContentDescription, + onSend = onSend, + beforeSendSlot = beforeSendSlot, ) }, ) { measurables, constraints -> val (textFieldMeasurable, controlsMeasurable) = measurables val startPad = InputBarStartPadding.roundToPx() - val endPad = InputBarEndPadding.roundToPx() - val availableWidth = (constraints.maxWidth - startPad - endPad).coerceAtLeast(0) + val availableWidth = + (constraints.maxWidth - startPad - InputBarEndPadding.roundToPx()).coerceAtLeast(0) val totalHeight = constraints.maxHeight + val topPad = ExpandedTopPadding.roundToPx() + val controlsHeight = ControlsRowHeight.roundToPx() - if (isExpanded) { - val topPad = ExpandedTopPadding.roundToPx() - val bottomPad = ExpandedBottomPadding.roundToPx() - val controlsHeight = ControlsRowHeight.roundToPx() - val controlsPlaceable = controlsMeasurable.measure( - Constraints.fixed(availableWidth, controlsHeight), - ) - val textHeight = (totalHeight - topPad - bottomPad - controlsHeight).coerceAtLeast(0) - val textPlaceable = textFieldMeasurable.measure( - Constraints.fixed(availableWidth, textHeight), - ) - layout(constraints.maxWidth, totalHeight) { - textPlaceable.place(startPad, topPad) - controlsPlaceable.place(startPad, topPad + textHeight) - } - } else { - val controlsPlaceable = controlsMeasurable.measure( - Constraints(maxWidth = availableWidth, maxHeight = totalHeight), - ) - val textWidth = (availableWidth - controlsPlaceable.width).coerceAtLeast(0) - val textPlaceable = textFieldMeasurable.measure( - Constraints.fixed(textWidth, totalHeight), - ) - layout(constraints.maxWidth, totalHeight) { - textPlaceable.place(startPad, 0) - controlsPlaceable.place(startPad + textPlaceable.width, (totalHeight - controlsPlaceable.height) / 2) + // expanded 는 컨트롤 행을 한 줄로 눕히고, collapsed 는 내용만큼만 차지하게 둔 뒤 남는 폭을 텍스트에 준다. + val controls = controlsMeasurable.measure( + if (isExpanded) { + Constraints.fixed(availableWidth, controlsHeight) + } else { + Constraints(maxWidth = availableWidth, maxHeight = totalHeight) + }, + ) + val textHeight = + (totalHeight - topPad - ExpandedBottomPadding.roundToPx() - controlsHeight).coerceAtLeast(0) + val text = textFieldMeasurable.measure( + if (isExpanded) { + Constraints.fixed(availableWidth, textHeight) + } else { + Constraints.fixed((availableWidth - controls.width).coerceAtLeast(0), totalHeight) + }, + ) + + layout(constraints.maxWidth, totalHeight) { + if (isExpanded) { + text.place(startPad, topPad) + controls.place(startPad, topPad + textHeight) + } else { + text.place(startPad, 0) + controls.place(startPad + text.width, (totalHeight - controls.height) / 2) } } } @@ -223,19 +216,18 @@ private fun InputTextField( private fun ControlsRow( isExpanded: Boolean, canSubmit: Boolean, - trailingContentDescription: String?, - onTrailingClick: () -> Unit, - trailingAction: @Composable (() -> Unit)?, + sendContentDescription: String?, + onSend: () -> Unit, + beforeSendSlot: @Composable (() -> Unit)?, ) { Row(verticalAlignment = Alignment.CenterVertically) { - trailingAction?.invoke() - // expanded 행은 폭이 고정 측정돼 남는 자리가 생긴다. trailingAction 이 없어도 밀어내지 않으면 - // 전송 버튼이 왼쪽 끝에 붙는다. collapsed 에서는 버튼 앞 간격만 둔다. + beforeSendSlot?.invoke() + // expanded 행은 폭이 고정 측정돼, 밀어내지 않으면 전송 버튼이 왼쪽 끝에 붙는다. when { isExpanded -> Spacer(modifier = Modifier.weight(1f)) - trailingAction != null -> Spacer(modifier = Modifier.width(ControlsRowGap)) + beforeSendSlot != null -> Spacer(modifier = Modifier.width(ControlsRowGap)) } - SendButton(canSubmit = canSubmit, contentDescription = trailingContentDescription, onClick = onTrailingClick) + SendButton(canSubmit = canSubmit, contentDescription = sendContentDescription, onClick = onSend) } } @@ -245,22 +237,16 @@ private fun SendButton( contentDescription: String?, onClick: () -> Unit, ) { - Box( + Image( + painter = painterResource(if (canSubmit) GamssIcons.SendButtonOn else GamssIcons.SendButtonOff), + contentDescription = contentDescription, modifier = Modifier - .size(SendButtonTouchSize) + .size(SendButtonSize) .clickable(enabled = canSubmit, role = Role.Button, onClick = onClick), - contentAlignment = Alignment.Center, - ) { - Image( - painter = painterResource(if (canSubmit) GamssIcons.SendButtonOn else GamssIcons.SendButtonOff), - contentDescription = contentDescription, - modifier = Modifier.size(SendButtonSize), - ) - } + ) } -/** 4배 해상도로 export한 손그림 테두리(collapsed 366x64 / expanded 368x152). 애니메이션 중엔 - * [gamssSketchyBorder] 로 대신 그리고, 정착한 뒤에만 실제 에셋을 쓴다. */ +/** 애니메이션 중엔 굵기가 일정한 [gamssSketchyBorder], 높이가 정착한 뒤엔 실제 손그림 에셋. */ @Composable private fun Modifier.inputBarBackground(useFocusArt: Boolean, isAnimating: Boolean): Modifier = if (isAnimating) { diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyShape.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBorder.kt similarity index 69% rename from core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyShape.kt rename to core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBorder.kt index 7b8e9c21..473ecc3d 100644 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyShape.kt +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBorder.kt @@ -12,21 +12,14 @@ import androidx.compose.ui.unit.dp import kotlin.math.sqrt import kotlin.random.Random -// Figma 목업의 입력바/감정 패널 테두리는 완전한 직선 사각형이 아니라 손으로 그은 듯 아주 -// 은은하게 굽이친다. 네 변을 각각 여러 점으로 쪼개 양방향으로 아주 작게 흔든 다음, 그 점들을 -// 매끄러운 곡선으로 잇는다. 매 recomposition 마다 다시 흔들리면 타이핑할 때마다 테두리가 -// 꿈틀거려 보이므로, 고정 시드로 항상 같은 흔들림을 재현한다. -// 각 점의 흔들림이 서로 독립적인 난수라 Catmull-Rom 접선이 이웃 점 사이의 편차를 그대로 -// 증폭시킨다 - 점 단위 진폭보다 눈에 보이는 물결이 훨씬 커지므로, Figma의 은은한 흔들림에 -// 맞추려면 진폭 자체를 훨씬 작게 잡아야 한다. +// Catmull-Rom 접선이 이웃 점 사이 편차를 증폭시켜, 보이는 물결은 점 단위 진폭보다 훨씬 커진다. private val EdgeJitter = 0.35.dp private const val EDGE_MID_POINTS = 2 + +// 매 recomposition 마다 다시 흔들리면 타이핑할 때마다 테두리가 꿈틀거린다. private const val DEFAULT_SEED = 20240521 -/** - * Figma의 손그림 선은 자유 경로를 직접 stroke 한다. 선의 중심과 모든 굴곡을 컴포넌트 안에 - * 두어 draw canvas 가장자리에서 잘리지 않도록 한다. - */ +/** 손그림 느낌의 사각 테두리. 선 굵기와 굴곡을 모두 컴포넌트 안에 두어 가장자리에서 잘리지 않는다. */ fun Modifier.gamssSketchyBorder( color: Color, width: Dp, @@ -50,7 +43,7 @@ private fun buildSketchyRectPath( size: Size, edgeJitterPx: Float, random: Random, - insetPx: Float = 0f, + insetPx: Float, ): Path { val strokeInset = insetPx.coerceAtLeast(0f) // 선의 반 폭과 진폭을 모두 확보해야 양방향 굴곡도 캔버스 밖으로 나가지 않는다. @@ -63,7 +56,6 @@ private fun buildSketchyRectPath( Offset(safeInset, size.height - safeInset), ) - // 코너 사이를 곡선으로 부드럽게 잇기 위해, 각 변의 점들을 먼저 하나의 목록으로 모은다. val points = mutableListOf() corners.indices.forEach { i -> val start = corners[i] @@ -72,7 +64,6 @@ private fun buildSketchyRectPath( val dx = end.x - start.x val dy = end.y - start.y val len = sqrt(dx * dx + dy * dy).let { if (it == 0f) 1f else it } - // 안전 여백 안에서 양방향으로 흔들어 Figma의 손그림 선처럼 보이게 한다. val nx = -dy / len val ny = dx / len for (m in 1..EDGE_MID_POINTS) { @@ -89,9 +80,8 @@ private fun buildSketchyRectPath( y = y.coerceIn(strokeInset, size.height - strokeInset), ) - // Catmull-Rom 곡선을 cubic Bézier로 바꾼다. 제어점만 이용하는 방식과 달리 모든 생성점을 - // 실제로 지나가므로, Figma의 굴곡 강도가 Compose에서 약해지지 않는다. - for (i in 0 until points.size) { + // Catmull-Rom: 생성점을 모두 지나가므로 굴곡 강도가 약해지지 않는다. + for (i in points.indices) { val previous = points[(i - 1 + points.size) % points.size] val current = points[i] val next = points[(i + 1) % points.size] diff --git a/core/designsystem/src/main/res/drawable/bg_input_box.xml b/core/designsystem/src/main/res/drawable/bg_input_box.xml index 67e7d42a..ef98eef9 100644 --- a/core/designsystem/src/main/res/drawable/bg_input_box.xml +++ b/core/designsystem/src/main/res/drawable/bg_input_box.xml @@ -1,6 +1,5 @@ - + - + Unit, val onInputChange: (String) -> Unit, - val onMessageLengthExceeded: () -> Unit, val onSubmit: () -> Unit, val onEmotionPickerToggle: () -> Unit, val onEmotionToggle: (EmotionCharacter) -> Unit, @@ -153,33 +143,18 @@ private fun HomeInputSection( val pickerItems = remember(state.selectedCharacters) { state.selectedCharacters.toPickerItems() } var inputBarHeightPx by remember { mutableIntStateOf(0) } - // 한 번 알린 뒤에는 계속 이어 쳐도 다시 알리지 않고, 길이를 줄였다 다시 넘길 때만 알린다. - var lengthWarned by remember { mutableStateOf(false) } - // 패널은 Popup 으로 띄운다. 흐름에 넣으면 펼칠 때마다 인사말과 입력바가 위로 밀린다. Box(modifier = Modifier.fillMaxWidth().padding(horizontal = InputBarHorizontalPadding)) { GamssInputBar( value = state.input, - // ViewModel도 같은 도메인 정책으로 방어하지만, IME 입력 순간에도 140자를 넘겨 - // 화면에 보이지 않도록 UI 경계에서 먼저 제한한다. - onValueChange = { - val limited = it.takeWithinMessageLimit() - if (limited != it) { - if (!lengthWarned) { - actions.onMessageLengthExceeded() - lengthWarned = true - } - } else { - lengthWarned = false - } - actions.onInputChange(limited) - }, - onTrailingClick = actions.onSubmit, + onValueChange = actions.onInputChange, + onSend = actions.onSubmit, placeholder = stringResource(R.string.home_input_placeholder), - trailingContentDescription = stringResource(R.string.home_input_submit_description), + sendContentDescription = stringResource(R.string.home_input_submit_description), enabled = !state.isSending, - trailingAction = { - CharacterPickerToggle( + beforeSendSlot = { + GamssDisclosureToggle( + label = stringResource(R.string.home_character_picker), expanded = state.isEmotionPickerExpanded, onClick = actions.onEmotionPickerToggle, ) @@ -212,48 +187,8 @@ private fun HomeInputSection( @Composable private fun Modifier.dismissKeyboardOnTapOutside(): Modifier { val focusManager = LocalFocusManager.current - return clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - onClick = { focusManager.clearFocus() }, - ) -} - -@Composable -private fun CharacterPickerToggle( - expanded: Boolean, - onClick: () -> Unit, -) { - Row( - // collapsed 에서만 48dp 까지 넓어진다. expanded 컨트롤 행은 32dp 로 고정 측정돼 그만큼만 잡힌다. - modifier = Modifier - .heightIn(min = 48.dp) - .clickable(role = Role.DropdownList, onClick = onClick) - .padding(horizontal = PickerToggleHitPadding, vertical = PickerToggleVerticalPadding), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(PickerToggleGap), - ) { - GamssText( - text = stringResource(R.string.home_character_picker), - style = GamssTheme.typography.body4Medium, - // 흰 입력바 위에서 다크 테마의 흰 전경색이 사라지지 않게 Figma 전경색을 유지한다. - color = GamssTheme.colors.black, - maxLines = 1, - ) - Icon( - painter = painterResource(GamssIcons.RightChevron), - contentDescription = null, - tint = GamssTheme.colors.black, - // 아래위 셰브론 에셋이 없어 오른쪽 셰브론을 돌려 쓴다. 글리프가 뷰포트 중심에서 벗어나 있어 - // 회전축을 글리프 자신의 중심으로 옮긴다. 그러지 않으면 펼칠 때마다 위아래로 튄다. - modifier = Modifier - .size(PickerToggleChevronSize) - .graphicsLayer { - rotationZ = if (expanded) -CHEVRON_ROTATION else CHEVRON_ROTATION - transformOrigin = TransformOrigin(CHEVRON_CENTER_X, CHEVRON_CENTER_Y) - }, - ) - } + // clickable 을 쓰면 레이블 없는 클릭 노드가 화면 전체 크기로 시맨틱 트리에 들어간다. + return pointerInput(Unit) { detectTapGestures { focusManager.clearFocus() } } } private fun Set.toPickerItems(): List> = @@ -338,17 +273,6 @@ private val PickerHorizontalInset = (-61).dp // 64dp/149dp 두 입력창 높이 모두에서 Figma가 보여 준 9~10dp 겹침 폭. private val PickerOverlapHeight = 9.dp -private val PickerToggleGap = 4.dp -private val PickerToggleHitPadding = 4.dp - -// SVG 입력바 내부 높이에 맞춰 라벨의 시각 높이만 사용한다. -private val PickerToggleVerticalPadding = 0.dp -private val PickerToggleChevronSize = 16.dp -private const val CHEVRON_ROTATION = 90f - -// ic_right_chevron 글리프의 실제 중심. 24 뷰포트에서 stroke 포함 x 12.4~21.3, y 3.9~20.2 다. -private const val CHEVRON_CENTER_X = 16.85f / 24f -private const val CHEVRON_CENTER_Y = 12.03f / 24f @Preview(name = "Home - Light", showBackground = true, widthDp = 402, heightDp = 720) @Composable @@ -385,7 +309,6 @@ private fun previewState() = HomeState(isLoading = false, nickname = "이소연" private fun previewActions() = HomeActions( onSettingClick = {}, onInputChange = {}, - onMessageLengthExceeded = {}, onSubmit = {}, onEmotionPickerToggle = {}, onEmotionToggle = {}, diff --git a/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt b/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt index 2d4c2765..d086463e 100644 --- a/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt +++ b/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.gamss.android.core.common.AppResult import com.gamss.android.domain.conversation.ConversationSession +import com.gamss.android.domain.conversation.MAX_MESSAGE_LENGTH import com.gamss.android.domain.conversation.takeWithinMessageLimit import com.gamss.android.domain.emotion.EmotionCharacter import com.gamss.android.domain.user.GetUserInfoUseCase @@ -17,7 +18,7 @@ import javax.inject.Inject internal const val LAST_CHARACTER_BLOCKED = "한 명은 남겨 주세요" internal const val SEND_FAILED = "보내지 못했어요. 잠시 후 다시 시도해 주세요." -internal const val MESSAGE_LENGTH_EXCEEDED = "메시지는 140자까지 입력할 수 있어요." +internal val MESSAGE_LENGTH_EXCEEDED = "메시지는 ${MAX_MESSAGE_LENGTH}자까지 입력할 수 있어요." @HiltViewModel class HomeViewModel @Inject constructor( @@ -32,12 +33,21 @@ class HomeViewModel @Inject constructor( postSideEffect(HomeSideEffect.NavigateToSetting) } + // 이어 치는 동안 토스트가 쌓이지 않게, 넘긴 상태에서 벗어날 때까지 한 번만 알린다. + private var lengthWarned = false + fun onInputChange(text: String) = blockingIntent { - reduce { state.copy(input = text.takeWithinMessageLimit()) } - } + val limited = text.takeWithinMessageLimit() + reduce { state.copy(input = limited) } - fun onMessageLengthExceeded() = intent { - postSideEffect(HomeSideEffect.ShowToast(MESSAGE_LENGTH_EXCEEDED)) + if (limited != text) { + if (!lengthWarned) { + lengthWarned = true + postSideEffect(HomeSideEffect.ShowToast(MESSAGE_LENGTH_EXCEEDED)) + } + } else { + lengthWarned = false + } } fun onEmotionPickerToggle() = intent { From 257be7cb87786c66e84aa783c09c745015498d3c Mon Sep 17 00:00:00 2001 From: soyeonLee <109227292+soyeonLee126@users.noreply.github.com> Date: Mon, 17 Aug 2026 19:52:26 +0900 Subject: [PATCH 04/20] =?UTF-8?q?test:=20=EA=B8=B8=EC=9D=B4=20=EC=A0=9C?= =?UTF-8?q?=ED=95=9C=20=EC=95=88=EB=82=B4=EB=A5=BC=20ViewModel=20=EB=A1=9C?= =?UTF-8?q?=20=EC=98=AE=EA=B8=B4=20=EB=8F=99=EC=9E=91=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EC=B6=B0=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EB=A5=BC=20=EA=B3=A0?= =?UTF-8?q?=EC=B9=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 자르기와 함께 토스트가 나가므로 기존 테스트가 사이드이펙트를 소비하지 않아 실패했다. 한 번만 알리는 래치와, 상한 아래로 줄였다 다시 넘길 때 다시 알리는 동작도 함께 검증한다. --- .../android/feature/home/HomeViewModelTest.kt | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/feature/home/src/test/java/com/gamss/android/feature/home/HomeViewModelTest.kt b/feature/home/src/test/java/com/gamss/android/feature/home/HomeViewModelTest.kt index 68e029ae..8c393b50 100644 --- a/feature/home/src/test/java/com/gamss/android/feature/home/HomeViewModelTest.kt +++ b/feature/home/src/test/java/com/gamss/android/feature/home/HomeViewModelTest.kt @@ -137,10 +137,42 @@ class HomeViewModelTest { } @Test - fun `상한을 넘긴 입력은 140자까지만 남는다`() = runTest { + fun `상한을 넘긴 입력은 140자까지만 남고 한 번 알린다`() = runTest { viewModel().test(this) { containerHost.onInputChange("가".repeat(MAX_MESSAGE_LENGTH + 20)) expectState { copy(input = "가".repeat(MAX_MESSAGE_LENGTH)) } + expectSideEffect(HomeSideEffect.ShowToast(MESSAGE_LENGTH_EXCEEDED)) + } + } + + @Test + fun `상한을 넘긴 채로 이어 쳐도 다시 알리지 않는다`() = runTest { + viewModel().test(this) { + val filled = "가".repeat(MAX_MESSAGE_LENGTH) + containerHost.onInputChange(filled + "가") + expectState { copy(input = filled) } + expectSideEffect(HomeSideEffect.ShowToast(MESSAGE_LENGTH_EXCEEDED)) + + // 상태가 그대로라 expectState 는 없고, 안내도 다시 오지 않는다. + containerHost.onInputChange(filled + "나") + } + } + + @Test + fun `상한 아래로 줄였다 다시 넘기면 또 알린다`() = runTest { + viewModel().test(this) { + val filled = "가".repeat(MAX_MESSAGE_LENGTH) + containerHost.onInputChange(filled + "가") + expectState { copy(input = filled) } + expectSideEffect(HomeSideEffect.ShowToast(MESSAGE_LENGTH_EXCEEDED)) + + val shorter = "가".repeat(MAX_MESSAGE_LENGTH - 1) + containerHost.onInputChange(shorter) + expectState { copy(input = shorter) } + + containerHost.onInputChange(shorter + "나다") + expectState { copy(input = shorter + "나") } + expectSideEffect(HomeSideEffect.ShowToast(MESSAGE_LENGTH_EXCEEDED)) } } From e7963c2212f6dd1f819407bc9a605dda9095cb07 Mon Sep 17 00:00:00 2001 From: soyeonLee <109227292+soyeonLee126@users.noreply.github.com> Date: Mon, 17 Aug 2026 21:33:11 +0900 Subject: [PATCH 05/20] =?UTF-8?q?feat:=20=EC=9E=85=EB=A0=A5=EC=B0=BD=20?= =?UTF-8?q?=EA=B8=B0=EB=B3=B8=20=EC=83=81=ED=83=9C=EB=8F=84=20=ED=85=8D?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=99=80=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?= =?UTF-8?q?=EC=9D=84=20=EB=82=98=EB=88=A0=20=EB=86=93=EA=B3=A0,=20?= =?UTF-8?q?=ED=85=8C=EB=91=90=EB=A6=AC=EB=A5=BC=209-slice=20=EB=A1=9C=20?= =?UTF-8?q?=EA=B7=B8=EB=A6=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Figma default(4001:8200) 가 focus 와 같은 2단 구성으로 바뀌었다. 기본 100dp, 포커스 149dp 로 높이만 다르고 위 16dp 텍스트 / 컨트롤 32dp / 아래 16dp 구성은 같다. - 상태별 분기가 높이와 좌우 여백 두 값으로 줄어, 측정·배치가 한 벌로 합쳐진다 - 손그림 테두리를 9-slice 로 그린다. 모서리는 원본 크기로 두고 직선 구간만 늘려 어떤 크기에서도 선 굵기가 변하지 않는다. 통째로 늘리면 늘린 방향 선만 두꺼워졌다 - 입력창과 감정 선택 패널이 같은 에셋 하나를 쓰게 되어, focus 전용 에셋과 코드로 그리던 테두리를 지운다 - 포커스 전환만 애니메이션하고 늘어난 줄은 즉시 반영한다. 애니메이션 높이로 텍스트 자리를 재면 방금 친 줄이 애니메이션 동안 잘려 보인다 - ripple 없는 noRippleClickable 을 modifier 에 두고 감정 선택 토글과 전송 버튼에 쓴다 - 보낸 뒤 입력을 비우면 길이 초과 안내 래치도 함께 푼다 --- .../component/GamssCharacterPicker.kt | 8 +- .../component/GamssDisclosureToggle.kt | 4 +- .../designsystem/component/GamssInputBar.kt | 135 ++++++------------ .../component/GamssSketchyBorder.kt | 109 -------------- .../designsystem/component/GamssSketchyBox.kt | 70 +++++++++ .../core/designsystem/modifier/Clickable.kt | 17 +++ .../src/main/res/drawable/bg_input_box.xml | 17 +-- .../main/res/drawable/bg_input_box_focus.xml | 20 --- .../gamss/android/feature/home/HomeScreen.kt | 4 +- .../android/feature/home/HomeViewModel.kt | 2 + 10 files changed, 147 insertions(+), 239 deletions(-) delete mode 100644 core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBorder.kt create mode 100644 core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBox.kt delete mode 100644 core/designsystem/src/main/res/drawable/bg_input_box_focus.xml diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssCharacterPicker.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssCharacterPicker.kt index eaf624f3..3e62b6db 100644 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssCharacterPicker.kt +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssCharacterPicker.kt @@ -1,7 +1,6 @@ package com.gamss.android.core.designsystem.component import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -31,7 +30,6 @@ private val ItemWidth = 45.dp private val ItemMinHeight = 18.dp private val IndicatorSize = 18.dp private val IndicatorLabelGap = 2.dp -private val PanelBorderWidth = 1.dp private const val COLUMNS = 3 /** 반응할 캐릭터를 고르는 패널. 한 줄에 [COLUMNS] 개씩 끊어 놓는다. */ @@ -43,11 +41,7 @@ fun GamssCharacterPicker( ) { Column( modifier = modifier - .background(GamssTheme.colors.white) - .gamssSketchyBorder( - color = GamssTheme.colors.black, - width = PanelBorderWidth, - ) + .gamssSketchyBox() .padding(PickerPadding), verticalArrangement = Arrangement.spacedBy(PickerGap), ) { diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssDisclosureToggle.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssDisclosureToggle.kt index 26e9792a..a5e6019a 100644 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssDisclosureToggle.kt +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssDisclosureToggle.kt @@ -1,6 +1,5 @@ package com.gamss.android.core.designsystem.component -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.heightIn @@ -15,6 +14,7 @@ import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp +import com.gamss.android.core.designsystem.modifier.noRippleClickable import com.gamss.android.core.designsystem.theme.GamssTheme private val ToggleMinTouchHeight = 48.dp @@ -43,7 +43,7 @@ fun GamssDisclosureToggle( Row( modifier = modifier .heightIn(min = ToggleMinTouchHeight) - .clickable(role = Role.DropdownList, onClick = onClick) + .noRippleClickable(role = Role.DropdownList, onClick = onClick) .padding(horizontal = ToggleHitPadding), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(LabelChevronGap), diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt index 9a7a84a1..1e1ca2f2 100644 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt @@ -3,15 +3,12 @@ package com.gamss.android.core.designsystem.component import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box 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.size -import androidx.compose.foundation.layout.width import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions @@ -24,50 +21,40 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds -import androidx.compose.ui.draw.paint import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.Layout import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.constrainWidth import androidx.compose.ui.unit.dp -import com.gamss.android.core.designsystem.R +import com.gamss.android.core.designsystem.modifier.noRippleClickable import com.gamss.android.core.designsystem.theme.GamssTheme -import kotlin.math.abs -private val CollapsedInputBarHeight = 64.dp +// Figma default(4001:8200) 366x100, focus(3489:4923) 366x149. 둘 다 위 16dp 텍스트 → +// 컨트롤 행 32dp → 아래 16dp 로 구성이 같고, 텍스트에 주는 높이만 다르다. +private val CollapsedInputBarHeight = 100.dp +private val ExpandedInputBarHeight = 149.dp +private val InputBarTopPadding = 16.dp +private val InputBarBottomPadding = 16.dp +private val ControlsRowHeight = 32.dp -private val InputBarStartPadding = 16.dp -private val InputBarEndPadding = 16.dp -private val ControlsRowGap = 4.dp +// 좌우 여백은 Figma 가 두 상태에서 다르게 잡혀 있다(default 20, focus 16). +private val CollapsedHorizontalPadding = 20.dp +private val ExpandedHorizontalPadding = 16.dp -// 배경까지 담긴 에셋이라 tint 하지 않는다. expanded 컨트롤 행이 32dp 고정이라 +// 배경까지 담긴 에셋이라 tint 하지 않는다. 컨트롤 행이 32dp 고정이라 // Android 권장 터치 영역(48dp)은 주지 못하고 아이콘 크기가 곧 터치 영역이다. private val SendButtonSize = 32.dp private const val INPUT_MAX_LINES = 6 private val InputLineHeight = 20.dp -// Figma(node 3483:16376): 포커스가 들어가면 입력창이 366x149로 커지고, 텍스트는 위(16dp 여백)에 -// 남고 감정 선택·전송 버튼은 하단(16dp 여백)의 별도 행으로 내려간다. -private val ExpandedInputBarHeight = 149.dp -private val ExpandedTopPadding = 16.dp -private val ExpandedBottomPadding = 16.dp -private val ControlsRowHeight = 32.dp - -// 149 - 위16 - 아래16 - 컨트롤행32 = 85dp: 컨트롤 행과 겹치지 않고 텍스트가 늘어날 수 있는 최대 높이. -private val ExpandedTextAreaMaxHeight = - ExpandedInputBarHeight - ExpandedTopPadding - ExpandedBottomPadding - ControlsRowHeight - private const val HEIGHT_ANIMATION_DURATION_MS = 350 -// 4배 에셋에서 실측한 6px. -private val InputBarBorderWidth = 1.5.dp - /** - * 포커스 여부로 collapsed(한 줄) <-> expanded(텍스트 위 / 컨트롤 아래) 모양이 바뀐다. + * 텍스트가 위, 감정 선택과 전송 버튼이 아래 행에 놓인다. 포커스가 들어가면 텍스트 자리만 넓어진다. * * Row/Column 을 갈아 끼우면 BasicTextField 가 새 노드로 다시 만들어져 포커스를 잡자마자 키보드가 * 닫힌다. 자식은 한 번만 컴포즈하고 배치만 [Layout] 으로 다시 계산하는 이유다. @@ -87,24 +74,25 @@ fun GamssInputBar( var lineCount by remember { mutableIntStateOf(1) } var isExpanded by remember { mutableStateOf(false) } - val collapsedHeight = CollapsedInputBarHeight + InputLineHeight * (lineCount - 1) - val expandedHeight = ExpandedInputBarHeight + - (InputLineHeight * lineCount - ExpandedTextAreaMaxHeight).coerceAtLeast(0.dp) - val targetHeight = if (isExpanded) expandedHeight else collapsedHeight - val animatedHeight by animateDpAsState( - targetValue = targetHeight, + val baseHeight = if (isExpanded) ExpandedInputBarHeight else CollapsedInputBarHeight + // 포커스로 기준 높이가 바뀔 때만 애니메이션한다. 줄이 늘어난 만큼은 곧바로 반영해야 + // 방금 친 줄이 애니메이션이 끝날 때까지 잘려 보이지 않는다. + val animatedBaseHeight by animateDpAsState( + targetValue = baseHeight, animationSpec = tween(durationMillis = HEIGHT_ANIMATION_DURATION_MS), label = "GamssInputBarHeight", ) + val textAreaHeight = + animatedBaseHeight - InputBarTopPadding - InputBarBottomPadding - ControlsRowHeight + val barHeight = + animatedBaseHeight + (InputLineHeight * lineCount - textAreaHeight).coerceAtLeast(0.dp) - // 두 에셋은 세로 비율이 달라 중간 높이로 늘리면 테두리가 왜곡된다(손그림이라 9-patch 도 못 쓴다). - val isAnimating = abs((animatedHeight - targetHeight).value) > 0.01f - val useFocusArt = animatedHeight > (collapsedHeight + expandedHeight) / 2 + val horizontalPadding = if (isExpanded) ExpandedHorizontalPadding else CollapsedHorizontalPadding Layout( modifier = modifier - .height(animatedHeight) - .inputBarBackground(useFocusArt = useFocusArt, isAnimating = isAnimating) + .height(barHeight) + .gamssSketchyBox() // 확장 첫 프레임엔 텍스트 자리가 0dp 로 잡혀 컨트롤 행 위로 넘친다. .clipToBounds(), content = { @@ -115,12 +103,10 @@ fun GamssInputBar( placeholder = placeholder, canSubmit = canSubmit, onSubmit = onSend, - alignment = if (isExpanded) Alignment.TopStart else Alignment.CenterStart, onFocusChanged = { isExpanded = it }, onLineCountChanged = { lineCount = it }, ) ControlsRow( - isExpanded = isExpanded, canSubmit = canSubmit, sendContentDescription = sendContentDescription, onSend = onSend, @@ -129,39 +115,24 @@ fun GamssInputBar( }, ) { measurables, constraints -> val (textFieldMeasurable, controlsMeasurable) = measurables - val startPad = InputBarStartPadding.roundToPx() - val availableWidth = - (constraints.maxWidth - startPad - InputBarEndPadding.roundToPx()).coerceAtLeast(0) + val sidePad = horizontalPadding.roundToPx() + // 폭이 열려 있는 부모(가로 스크롤 등) 아래에서는 maxWidth 가 Infinity 라 그대로 쓰면 터진다. + val barWidth = constraints.constrainWidth( + if (constraints.hasBoundedWidth) constraints.maxWidth else constraints.minWidth, + ) + val availableWidth = (barWidth - sidePad * 2).coerceAtLeast(0) val totalHeight = constraints.maxHeight - val topPad = ExpandedTopPadding.roundToPx() + val topPad = InputBarTopPadding.roundToPx() val controlsHeight = ControlsRowHeight.roundToPx() - - // expanded 는 컨트롤 행을 한 줄로 눕히고, collapsed 는 내용만큼만 차지하게 둔 뒤 남는 폭을 텍스트에 준다. - val controls = controlsMeasurable.measure( - if (isExpanded) { - Constraints.fixed(availableWidth, controlsHeight) - } else { - Constraints(maxWidth = availableWidth, maxHeight = totalHeight) - }, - ) val textHeight = - (totalHeight - topPad - ExpandedBottomPadding.roundToPx() - controlsHeight).coerceAtLeast(0) - val text = textFieldMeasurable.measure( - if (isExpanded) { - Constraints.fixed(availableWidth, textHeight) - } else { - Constraints.fixed((availableWidth - controls.width).coerceAtLeast(0), totalHeight) - }, - ) + (totalHeight - topPad - InputBarBottomPadding.roundToPx() - controlsHeight).coerceAtLeast(0) - layout(constraints.maxWidth, totalHeight) { - if (isExpanded) { - text.place(startPad, topPad) - controls.place(startPad, topPad + textHeight) - } else { - text.place(startPad, 0) - controls.place(startPad + text.width, (totalHeight - controls.height) / 2) - } + val controls = controlsMeasurable.measure(Constraints.fixed(availableWidth, controlsHeight)) + val text = textFieldMeasurable.measure(Constraints.fixed(availableWidth, textHeight)) + + layout(barWidth, totalHeight) { + text.place(sidePad, topPad) + controls.place(sidePad, topPad + textHeight) } } } @@ -174,7 +145,6 @@ private fun InputTextField( placeholder: String, canSubmit: Boolean, onSubmit: () -> Unit, - alignment: Alignment, onFocusChanged: (Boolean) -> Unit, onLineCountChanged: (Int) -> Unit, ) { @@ -197,7 +167,7 @@ private fun InputTextField( // 자리표시자와 입력칸을 겹쳐 놓아야 한다. 컨테이너 없이 나란히 두면 입력칸이 밀려난다. Box( modifier = Modifier.fillMaxWidth(), - contentAlignment = alignment, + contentAlignment = Alignment.TopStart, ) { if (value.isEmpty()) { GamssText( @@ -214,7 +184,6 @@ private fun InputTextField( @Composable private fun ControlsRow( - isExpanded: Boolean, canSubmit: Boolean, sendContentDescription: String?, onSend: () -> Unit, @@ -222,11 +191,8 @@ private fun ControlsRow( ) { Row(verticalAlignment = Alignment.CenterVertically) { beforeSendSlot?.invoke() - // expanded 행은 폭이 고정 측정돼, 밀어내지 않으면 전송 버튼이 왼쪽 끝에 붙는다. - when { - isExpanded -> Spacer(modifier = Modifier.weight(1f)) - beforeSendSlot != null -> Spacer(modifier = Modifier.width(ControlsRowGap)) - } + // 폭이 고정 측정되므로 밀어내지 않으면 전송 버튼이 왼쪽 끝에 붙는다. + Spacer(modifier = Modifier.weight(1f)) SendButton(canSubmit = canSubmit, contentDescription = sendContentDescription, onClick = onSend) } } @@ -242,19 +208,6 @@ private fun SendButton( contentDescription = contentDescription, modifier = Modifier .size(SendButtonSize) - .clickable(enabled = canSubmit, role = Role.Button, onClick = onClick), + .noRippleClickable(enabled = canSubmit, role = Role.Button, onClick = onClick), ) } - -/** 애니메이션 중엔 굵기가 일정한 [gamssSketchyBorder], 높이가 정착한 뒤엔 실제 손그림 에셋. */ -@Composable -private fun Modifier.inputBarBackground(useFocusArt: Boolean, isAnimating: Boolean): Modifier = - if (isAnimating) { - this.background(GamssTheme.colors.white) - .gamssSketchyBorder(color = GamssTheme.colors.black, width = InputBarBorderWidth) - } else { - this.paint( - painter = painterResource(if (useFocusArt) R.drawable.bg_input_box_focus else R.drawable.bg_input_box), - contentScale = ContentScale.FillBounds, - ) - } diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBorder.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBorder.kt deleted file mode 100644 index 473ecc3d..00000000 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBorder.kt +++ /dev/null @@ -1,109 +0,0 @@ -package com.gamss.android.core.designsystem.component - -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawWithCache -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Path -import androidx.compose.ui.graphics.drawscope.Stroke -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import kotlin.math.sqrt -import kotlin.random.Random - -// Catmull-Rom 접선이 이웃 점 사이 편차를 증폭시켜, 보이는 물결은 점 단위 진폭보다 훨씬 커진다. -private val EdgeJitter = 0.35.dp -private const val EDGE_MID_POINTS = 2 - -// 매 recomposition 마다 다시 흔들리면 타이핑할 때마다 테두리가 꿈틀거린다. -private const val DEFAULT_SEED = 20240521 - -/** 손그림 느낌의 사각 테두리. 선 굵기와 굴곡을 모두 컴포넌트 안에 두어 가장자리에서 잘리지 않는다. */ -fun Modifier.gamssSketchyBorder( - color: Color, - width: Dp, - edgeJitter: Dp = EdgeJitter, - seed: Int = DEFAULT_SEED, -): Modifier = drawWithCache { - val strokeWidth = width.toPx() - val path = buildSketchyRectPath( - size = size, - edgeJitterPx = edgeJitter.toPx(), - random = Random(seed), - insetPx = strokeWidth / 2f, - ) - onDrawWithContent { - drawContent() - drawPath(path = path, color = color, style = Stroke(width = strokeWidth)) - } -} - -private fun buildSketchyRectPath( - size: Size, - edgeJitterPx: Float, - random: Random, - insetPx: Float, -): Path { - val strokeInset = insetPx.coerceAtLeast(0f) - // 선의 반 폭과 진폭을 모두 확보해야 양방향 굴곡도 캔버스 밖으로 나가지 않는다. - val safeInset = strokeInset + edgeJitterPx - fun jitter(amount: Float) = (random.nextFloat() - 0.5f) * 2f * amount - val corners = listOf( - Offset(safeInset, safeInset), - Offset(size.width - safeInset, safeInset), - Offset(size.width - safeInset, size.height - safeInset), - Offset(safeInset, size.height - safeInset), - ) - - val points = mutableListOf() - corners.indices.forEach { i -> - val start = corners[i] - val end = corners[(i + 1) % corners.size] - points += start - val dx = end.x - start.x - val dy = end.y - start.y - val len = sqrt(dx * dx + dy * dy).let { if (it == 0f) 1f else it } - val nx = -dy / len - val ny = dx / len - for (m in 1..EDGE_MID_POINTS) { - val t = m / (EDGE_MID_POINTS + 1f) - val amount = jitter(edgeJitterPx) - points += Offset(start.x + dx * t + nx * amount, start.y + dy * t + ny * amount) - } - } - - val path = Path() - path.moveTo(points[0].x, points[0].y) - fun Offset.clampToOutline() = Offset( - x = x.coerceIn(strokeInset, size.width - strokeInset), - y = y.coerceIn(strokeInset, size.height - strokeInset), - ) - - // Catmull-Rom: 생성점을 모두 지나가므로 굴곡 강도가 약해지지 않는다. - for (i in points.indices) { - val previous = points[(i - 1 + points.size) % points.size] - val current = points[i] - val next = points[(i + 1) % points.size] - val following = points[(i + 2) % points.size] - val firstControl = Offset( - current.x + (next.x - previous.x) / 6f, - current.y + (next.y - previous.y) / 6f, - ).clampToOutline() - val secondControl = Offset( - next.x - (following.x - current.x) / 6f, - next.y - (following.y - current.y) / 6f, - ).clampToOutline() - path.cubicTo( - firstControl.x, - firstControl.y, - secondControl.x, - secondControl.y, - next.x, - next.y, - ) - } - path.close() - - return path -} diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBox.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBox.kt new file mode 100644 index 00000000..586e4f07 --- /dev/null +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBox.kt @@ -0,0 +1,70 @@ +package com.gamss.android.core.designsystem.component + +import androidx.compose.foundation.background +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.isUnspecified +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.clipRect +import androidx.compose.ui.graphics.drawscope.scale +import androidx.compose.ui.graphics.drawscope.translate +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import com.gamss.android.core.designsystem.R +import com.gamss.android.core.designsystem.theme.GamssTheme + +// 모서리는 늘리지 않고 원본 크기로 찍어 낸다. 굴곡이 가장 심한 구간이라 넉넉히 잡는다. +private val ArtCorner = 12.dp + +/** + * 손그림 사각 테두리를 9-slice 로 그린다. 모서리는 원본 크기로 두고 직선 구간만 늘리므로, + * 어떤 크기에 올려도 선 굵기가 변하지 않는다. 통째로 늘리면 늘린 방향의 선만 두꺼워진다. + */ +@Composable +fun Modifier.gamssSketchyBox(background: Color = GamssTheme.colors.white): Modifier { + val art = painterResource(R.drawable.bg_input_box) + return this + .background(background) + .drawBehind { drawNineSlice(art) } +} + +private fun DrawScope.drawNineSlice(art: Painter) { + // 에셋 크기를 상수로 복제하면 에셋을 갈아 끼울 때 조용히 어긋난다. + val artSize = art.intrinsicSize + if (artSize.isUnspecified) return + val corner = ArtCorner.toPx() + + // 세 구간의 경계: 원본과 대상 각각. 가운데 구간만 늘어난다. + val srcX = floatArrayOf(0f, corner, artSize.width - corner, artSize.width) + val srcY = floatArrayOf(0f, corner, artSize.height - corner, artSize.height) + val dstX = floatArrayOf(0f, corner, size.width - corner, size.width) + val dstY = floatArrayOf(0f, corner, size.height - corner, size.height) + + // 링 안쪽(가운데 칸)은 비어 있어 그릴 것이 없다. + val slices = (0..2).flatMap { col -> (0..2).map { row -> col to row } } - (1 to 1) + + slices.forEach { (col, row) -> + val scaleX = sliceScale(srcX, dstX, col) + val scaleY = sliceScale(srcY, dstY, row) + if (scaleX <= 0f || scaleY <= 0f) return@forEach + + clipRect(dstX[col], dstY[row], dstX[col + 1], dstY[row + 1]) { + translate(dstX[col] - srcX[col] * scaleX, dstY[row] - srcY[row] * scaleY) { + scale(scaleX, scaleY, pivot = Offset.Zero) { + with(art) { draw(artSize) } + } + } + } + } +} + +/** 대상이 원본보다 작아 구간이 뒤집히면 0 을 돌려 그리지 않게 한다. */ +private fun sliceScale(src: FloatArray, dst: FloatArray, index: Int): Float { + val srcLength = src[index + 1] - src[index] + val dstLength = dst[index + 1] - dst[index] + return if (srcLength <= 0f || dstLength <= 0f) 0f else dstLength / srcLength +} diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/modifier/Clickable.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/modifier/Clickable.kt index 98e4b57b..bc97b876 100644 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/modifier/Clickable.kt +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/modifier/Clickable.kt @@ -3,6 +3,23 @@ package com.gamss.android.core.designsystem.modifier import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.Role + +/** + * ripple 없이 클릭을 받습니다. 디자인에 눌림 표현이 없어, 기본 indication 을 두면 손그림 배경 위에 + * 회색 사각형이 덧그려집니다. + */ +fun Modifier.noRippleClickable( + enabled: Boolean = true, + role: Role? = null, + onClick: () -> Unit, +): Modifier = clickable( + enabled = enabled, + role = role, + onClick = onClick, + indication = null, + interactionSource = null, +) /** * [onClick]이 null이 아닐 때만, ripple 없이 [Modifier.clickable]을 적용합니다. diff --git a/core/designsystem/src/main/res/drawable/bg_input_box.xml b/core/designsystem/src/main/res/drawable/bg_input_box.xml index ef98eef9..a1acd766 100644 --- a/core/designsystem/src/main/res/drawable/bg_input_box.xml +++ b/core/designsystem/src/main/res/drawable/bg_input_box.xml @@ -1,20 +1,21 @@ - + + android:viewportHeight="61"> + android:fillColor="#FF1E1F22" + android:pathData="M21.0197 0.582495L20.9554 1.07834L21.0197 0.582495ZM69.3397 0.733111L69.3069 0.23419L69.3397 0.733111ZM90.4797 0.787421L90.4565 1.28688L90.4797 0.787421ZM98.0297 0.808693L98.0584 1.30787L98.0297 0.808693ZM116.15 0.597864L116.198 0.1002L116.15 0.597864ZM152.39 0.697461L152.415 1.19683L152.39 0.697461ZM153.9 0.587782L153.852 0.0900291L153.9 0.587782ZM185.61 0.705486L185.528 1.19874L185.61 0.705486ZM191.65 0.599519L191.59 1.09592L191.65 0.599519ZM236.95 0.651499L236.924 1.15082L236.95 0.651499ZM246.01 0.618184L245.955 1.11523L246.01 0.618184ZM288.29 0.655085L288.313 1.15453L288.29 0.655085ZM315.47 0.778584L315.522 0.28133L315.47 0.778584ZM327.55 0.713141L327.514 1.21189L327.55 0.713141ZM336.61 0.531272L336.595 1.03106L336.61 0.531272ZM363.79 0.592187L363.839 0.0946211L363.79 0.592187ZM366.437 1.18436L366.069 1.52249L366.437 1.18436ZM366.754 3.8659L367.254 3.84942L366.754 3.8659ZM366.606 8.3959L367.101 8.32476L366.606 8.3959ZM366.576 11.4159L366.082 11.3364L366.576 11.4159ZM366.663 15.9459L367.162 15.9724L366.663 15.9459ZM366.636 21.9859L366.14 21.9231L366.636 21.9859ZM366.628 23.4959L366.132 23.5538L366.628 23.4959ZM366.594 31.0459L366.094 31.0408L366.594 31.0459ZM366.541 32.5559L367.04 32.5858L366.541 32.5559ZM366.522 34.0659L366.023 34.0836L366.522 34.0659ZM366.584 43.1259L366.092 43.0403L366.584 43.1259ZM366.796 46.1459L366.296 46.1517L366.796 46.1459ZM366.643 50.6759L366.145 50.6313L366.643 50.6759ZM366.724 55.2059L367.221 55.1446L366.724 55.2059ZM366.742 56.7159L367.239 56.7658L366.742 56.7159ZM366.3 59.337L366.596 59.7401L366.3 59.337ZM360.53 59.5752L360.532 60.0752L360.53 59.5752ZM357.51 59.5887L357.557 60.0864L357.51 59.5887ZM356 59.6248L356.024 59.1254L356 59.6248ZM354.49 59.5475L354.463 60.0468L354.49 59.5475ZM352.98 59.619L353.053 60.1135L352.98 59.619ZM349.96 59.7395L349.923 59.2409L349.96 59.7395ZM348.45 59.7468L348.481 59.2478L348.45 59.7468ZM346.94 59.7795L346.887 59.2823L346.94 59.7795ZM345.43 59.8023L345.392 60.3009L345.43 59.8023ZM342.41 59.8343L342.393 60.334L342.41 59.8343ZM340.9 59.659L340.803 60.1496L340.9 59.659ZM339.39 59.5748L339.433 60.0729L339.39 59.5748ZM336.37 59.6794L336.451 59.186L336.37 59.6794ZM333.35 59.7721L333.374 59.2727L333.35 59.7721ZM330.33 59.627L330.241 60.1191L330.33 59.627ZM327.31 59.6898L327.403 60.181L327.31 59.6898ZM324.29 59.6535L324.285 60.1534L324.29 59.6535ZM321.27 59.7099L321.19 59.2162L321.27 59.7099ZM315.23 59.5924L315.25 60.0919L315.23 59.5924ZM313.72 59.6862L313.762 60.1845L313.72 59.6862ZM310.7 59.6503L310.622 60.1442L310.7 59.6503ZM297.11 59.7905L297.087 59.291L297.11 59.7905ZM291.07 59.7297L291.085 60.2294L291.07 59.7297ZM288.05 59.8116L288.067 60.3113L288.05 59.8116ZM283.52 59.7563L283.579 59.2598L283.52 59.7563ZM277.48 59.6101L277.519 60.1086L277.48 59.6101ZM269.93 59.6736L269.839 59.1819L269.93 59.6736ZM257.85 59.6598L257.838 60.1597L257.85 59.6598ZM254.83 59.666L254.825 59.166L254.83 59.666ZM253.32 59.7394L253.276 59.2413L253.32 59.7394ZM248.79 59.7094L248.819 60.2085L248.79 59.7094ZM247.28 59.6583L247.343 59.1623L247.28 59.6583ZM242.75 59.573L242.794 59.0749L242.75 59.573ZM241.24 59.5662L241.279 60.0646L241.24 59.5662ZM239.73 59.5867L239.704 60.086L239.73 59.5867ZM238.22 59.5285L238.207 60.0283L238.22 59.5285ZM233.69 59.704L233.752 59.2079L233.69 59.704ZM229.16 59.6499L229.088 59.155L229.16 59.6499ZM227.65 59.6453L227.724 59.1509L227.65 59.6453ZM226.14 59.6338L226.207 60.1292L226.14 59.6338ZM217.08 59.7035L217.067 60.2033L217.08 59.7035ZM211.04 59.709L210.954 60.2015L211.04 59.709ZM209.53 59.7146L209.44 59.2227L209.53 59.7146ZM203.49 59.7054L203.475 59.2056V59.2056L203.49 59.7054ZM201.98 59.7748L201.948 59.2758L201.98 59.7748ZM200.47 59.7949L200.452 60.2946L200.47 59.7949ZM192.92 59.5883L192.91 59.0884L192.92 59.5883ZM182.35 59.722L182.39 60.2203L182.35 59.722ZM180.84 59.6358L180.744 60.1265L180.84 59.6358ZM179.33 59.5346L179.299 59.0355L179.33 59.5346ZM174.8 59.5634L174.773 59.0641L174.8 59.5634ZM171.78 59.6521L171.804 59.1527L171.78 59.6521ZM168.76 59.6741L168.766 60.174L168.76 59.6741ZM161.21 59.6061L161.145 59.1103L161.21 59.6061ZM159.7 59.6334L159.747 59.1356L159.7 59.6334ZM153.66 59.6443L153.614 59.1465L153.66 59.6443ZM147.62 59.6925L147.67 59.195L147.62 59.6925ZM137.05 59.7714L137.093 60.2695L137.05 59.7714ZM134.03 59.6752L133.97 60.1716L134.03 59.6752ZM129.5 59.757L129.492 59.257L129.5 59.757ZM124.97 59.6059L125.029 59.1094L124.97 59.6059ZM123.46 59.5732L123.422 59.0747L123.46 59.5732ZM118.93 59.7432L118.856 59.2487L118.93 59.7432ZM108.36 59.6006L108.328 59.1017L108.36 59.6006ZM97.7897 59.6625L97.7567 60.1615L97.7897 59.6625ZM96.2797 59.6014L96.2871 59.1015L96.2797 59.6014ZM93.2597 59.5418L93.2859 60.0411L93.2597 59.5418ZM85.7097 59.6741L85.7289 60.1738L85.7097 59.6741ZM82.6897 59.675L82.7272 59.1764L82.6897 59.675ZM81.1797 59.6394L81.1658 59.1396L81.1797 59.6394ZM76.6498 59.7866L76.6756 60.286L76.6498 59.7866ZM69.0997 59.5442L69.0888 59.0444L69.0997 59.5442ZM63.0597 59.5721L63.1192 59.0756L63.0597 59.5721ZM58.5297 59.6482L58.5384 60.1481L58.5297 59.6482ZM47.9598 59.6646L47.878 60.1578L47.9598 59.6646ZM46.4497 59.6192L46.3972 59.1219L46.4497 59.6192ZM44.9397 59.7289L44.9199 59.2293L44.9397 59.7289ZM40.4097 59.6713L40.2962 60.1582L40.4097 59.6713ZM37.3897 59.6305L37.3443 59.1325L37.3897 59.6305ZM32.8598 59.8403L32.8667 59.3404L32.8598 59.8403ZM29.8397 59.6752L29.7877 60.1725L29.8397 59.6752ZM28.3297 59.6605L28.2873 59.1623L28.3297 59.6605ZM25.3097 59.8241L25.3316 60.3236L25.3097 59.8241ZM20.7797 59.7688L20.7305 59.2712L20.7797 59.7688ZM16.2497 59.5342L16.2351 60.034L16.2497 59.5342ZM14.7397 59.6236L14.8129 60.1183L14.7397 59.6236ZM11.7197 59.7576L11.7194 60.2576L11.7197 59.7576ZM10.2098 59.666L10.2696 59.1696L10.2098 59.666ZM5.67974 59.6202L5.75398 59.1257L5.67974 59.6202ZM4.16973 59.6787L4.05878 59.1911L4.16973 59.6787ZM0.608713 42.0659L0.109351 42.0912L0.608713 42.0659ZM0.548804 20.9259L0.0517725 20.9803L0.548804 20.9259ZM1.38974 0.621569L1.42233 1.12051C1.9133 1.08843 2.40421 1.07467 2.8951 1.07922L2.89974 0.579244L2.90437 0.0792655C2.38859 0.0744806 1.87284 0.0889386 1.35714 0.122633L1.38974 0.621569ZM2.89974 0.579244L2.8951 1.07922C3.37558 1.08368 3.85635 1.12096 4.33754 1.19117L4.40973 0.696411L4.48193 0.20165C3.95646 0.124978 3.43055 0.0841469 2.90437 0.0792655L2.89974 0.579244ZM4.40973 0.696411L4.33754 1.19117C4.86056 1.26749 5.38394 1.31205 5.90758 1.32478L5.91973 0.824933L5.93189 0.32508C5.44886 0.313335 4.96557 0.272219 4.48193 0.20165L4.40973 0.696411ZM5.91973 0.824933L5.90758 1.32478C6.45189 1.33802 6.99522 1.28493 7.53676 1.16627L7.42974 0.677855L7.32271 0.189443C6.85758 0.291363 6.39425 0.336323 5.93189 0.32508L5.91973 0.824933ZM7.42974 0.677855L7.53676 1.16627C7.97488 1.07027 8.41036 1.06366 8.84726 1.14587L8.93974 0.6545L9.03221 0.163125C8.46244 0.0559027 7.89126 0.0648627 7.32271 0.189443L7.42974 0.677855ZM8.93974 0.6545L8.84726 1.14587C9.40362 1.25057 9.96168 1.264 10.5193 1.18568L10.4497 0.690538L10.3802 0.195398C9.93113 0.258471 9.48251 0.247867 9.03221 0.163125L8.93974 0.6545ZM10.4497 0.690538L10.5193 1.18568C10.9713 1.1222 11.4227 1.12907 11.8754 1.20655L11.9597 0.71372L12.0441 0.220886C11.4901 0.126071 10.9349 0.11749 10.3802 0.195398L10.4497 0.690538ZM11.9597 0.71372L11.8754 1.20655C12.4285 1.30122 12.9829 1.31119 13.5368 1.23617L13.4697 0.74069L13.4026 0.245213C12.9499 0.30653 12.4976 0.298515 12.0441 0.220886L11.9597 0.71372ZM13.4697 0.74069L13.5368 1.23617C14.0081 1.17234 14.4787 1.15487 14.9493 1.1836L14.9797 0.684527L15.0102 0.185456C14.4741 0.152726 13.9381 0.172697 13.4026 0.245213L13.4697 0.74069ZM14.9797 0.684527L14.9493 1.1836C15.4649 1.21508 15.9806 1.22744 16.4963 1.22068L16.4897 0.720727L16.4832 0.220769C15.9922 0.227202 15.5012 0.215432 15.0102 0.185456L14.9797 0.684527ZM16.4897 0.720727L16.4963 1.22068C17.0111 1.21394 17.5258 1.1895 18.0405 1.14737L17.9997 0.649036L17.9589 0.150703C17.467 0.190972 16.9751 0.214325 16.4832 0.220769L16.4897 0.720727ZM17.9997 0.649036L18.0405 1.14737C18.5416 1.10636 19.0426 1.06881 19.5437 1.03472L19.5097 0.535872L19.4758 0.0370248C18.9702 0.0714244 18.4646 0.109317 17.9589 0.150703L17.9997 0.649036ZM19.5097 0.535872L19.5437 1.03472C20.014 1.00272 20.4844 1.01721 20.9554 1.07834L21.0197 0.582495L21.0841 0.0866534C20.5484 0.0171239 20.0121 0.000536859 19.4758 0.0370248L19.5097 0.535872ZM21.0197 0.582495L20.9554 1.07834C21.4613 1.144 21.9672 1.20565 22.4731 1.26329L22.5297 0.7665L22.5863 0.269714C22.0856 0.212665 21.5848 0.151645 21.0841 0.0866534L21.0197 0.582495ZM22.5297 0.7665L22.4731 1.26329C22.9974 1.32301 23.5219 1.34935 24.0465 1.34224L24.0397 0.84229L24.033 0.342336C23.5509 0.348865 23.0688 0.324676 22.5863 0.269714L22.5297 0.7665ZM24.0397 0.84229L24.0465 1.34224C24.571 1.33514 25.0953 1.29482 25.6192 1.22137L25.5497 0.726211L25.4803 0.231054C24.9975 0.29874 24.5151 0.335806 24.033 0.342336L24.0397 0.84229ZM25.5497 0.726211L25.6192 1.22137C26.0801 1.15674 26.5403 1.15176 27.0009 1.20633L27.0597 0.709803L27.1186 0.213275C26.5725 0.14858 26.026 0.154541 25.4803 0.231054L25.5497 0.726211ZM27.0597 0.709803L27.0009 1.20633C27.5219 1.26805 28.0431 1.30168 28.5645 1.30716L28.5697 0.807185L28.575 0.307213C28.0897 0.302112 27.6042 0.270814 27.1186 0.213275L27.0597 0.709803ZM28.5697 0.807185L28.5645 1.30716C29.0696 1.31247 29.5748 1.315 30.08 1.31475L30.0797 0.814749L30.0795 0.314749C29.578 0.314996 29.0765 0.312484 28.575 0.307213L28.5697 0.807185ZM30.0797 0.814749L30.08 1.31475C30.5934 1.3145 31.1067 1.29878 31.62 1.2676L31.5897 0.768518L31.5594 0.269438C31.0661 0.299403 30.5728 0.314506 30.0795 0.314749L30.0797 0.814749ZM31.5897 0.768518L31.62 1.2676C32.1356 1.23628 32.6511 1.18593 33.1664 1.11655L33.0997 0.621022L33.033 0.125492C32.5417 0.191635 32.0505 0.23961 31.5594 0.269438L31.5897 0.768518ZM33.0997 0.621022L33.1664 1.11655C33.6256 1.05474 34.0842 1.05461 34.5433 1.11616L34.6097 0.620596L34.6762 0.125029C34.1286 0.0516315 33.5805 0.0517867 33.033 0.125492L33.0997 0.621022ZM34.6097 0.620596L34.5433 1.11616C35.0569 1.18502 35.5707 1.2376 36.0845 1.27389L36.1197 0.775134L36.155 0.276377C35.6621 0.241568 35.1692 0.191123 34.6762 0.125029L34.6097 0.620596ZM36.1197 0.775134L36.0845 1.27389C36.6148 1.31134 37.1451 1.30587 37.6752 1.25748L37.6297 0.759552L37.5843 0.261622C37.1077 0.305117 36.6314 0.310027 36.155 0.276377L36.1197 0.775134ZM37.6297 0.759552L37.6752 1.25748C38.1692 1.2124 38.6631 1.18132 39.157 1.16426L39.1397 0.66456L39.1225 0.164858C38.6097 0.182573 38.097 0.21483 37.5843 0.261622L37.6297 0.759552ZM39.1397 0.66456L39.157 1.16426C39.6589 1.14692 40.1608 1.13176 40.6627 1.11878L40.6497 0.618951L40.6368 0.119118C40.132 0.132173 39.6272 0.14742 39.1225 0.164858L39.1397 0.66456ZM40.6497 0.618951L40.6627 1.11878C41.1489 1.10621 41.6352 1.1185 42.1216 1.15568L42.1597 0.657138L42.1978 0.158592C41.6776 0.118822 41.1572 0.105659 40.6368 0.119118L40.6497 0.618951ZM42.1597 0.657138L42.1216 1.15568C42.6311 1.19463 43.1407 1.22407 43.6502 1.244L43.6697 0.744378L43.6893 0.24476C43.1922 0.225318 42.695 0.196596 42.1978 0.158592L42.1597 0.657138ZM43.6697 0.744378L43.6502 1.244C44.1531 1.26366 44.656 1.28401 45.1588 1.30505L45.1797 0.805487L45.2006 0.305924C44.6968 0.28485 44.1931 0.264462 43.6893 0.24476L43.6697 0.744378ZM45.1797 0.805487L45.1588 1.30505C45.6696 1.32642 46.1804 1.33635 46.6912 1.33486L46.6897 0.834861L46.6883 0.334863C46.1924 0.336313 45.6965 0.326667 45.2006 0.305924L45.1797 0.805487ZM46.6897 0.834861L46.6912 1.33486C47.1993 1.33337 47.7074 1.32461 48.2155 1.30857L48.1997 0.808817L48.184 0.309066C47.6854 0.324806 47.1868 0.333405 46.6883 0.334863L46.6897 0.834861ZM48.1997 0.808817L48.2155 1.30857C48.7199 1.29265 49.2242 1.27518 49.7286 1.25617L49.7097 0.756523L49.6909 0.256878C49.1886 0.275812 48.6863 0.293209 48.184 0.309066L48.1997 0.808817ZM49.7097 0.756523L49.7286 1.25617C50.241 1.23685 50.7534 1.20348 51.2658 1.15605L51.2197 0.65818L51.1737 0.160308C50.6793 0.206061 50.1851 0.238248 49.6909 0.256878L49.7097 0.756523ZM51.2197 0.65818L51.2658 1.15605C51.7341 1.1127 52.2023 1.11875 52.6709 1.17426L52.7297 0.677734L52.7886 0.181205C52.2505 0.117467 51.712 0.110479 51.1737 0.160308L51.2197 0.65818ZM52.7297 0.677734L52.6709 1.17426C53.2238 1.23976 53.7769 1.22256 54.3285 1.12311L54.2397 0.631046L54.151 0.13898C53.6959 0.221032 53.2423 0.234956 52.7886 0.181205L52.7297 0.677734ZM54.2397 0.631046L54.3285 1.12311C54.8005 1.038 55.2716 0.999222 55.7421 1.00643L55.7497 0.506489L55.7574 0.00654781C55.2212 -0.00166595 54.6856 0.0425903 54.151 0.13898L54.2397 0.631046ZM55.7497 0.506489L55.7421 1.00643C56.2474 1.01417 56.7527 1.01891 57.258 1.02064L57.2597 0.520645L57.2615 0.0206476C56.7601 0.0189284 56.2587 0.0142285 55.7574 0.00654781L55.7497 0.506489ZM57.2597 0.520645L57.258 1.02064C57.7599 1.02236 58.2617 1.02634 58.7635 1.03256L58.7697 0.5326L58.7759 0.0326388C58.2711 0.0263758 57.7663 0.0223787 57.2615 0.0206476L57.2597 0.520645ZM58.7697 0.5326L58.7635 1.03256C59.266 1.0388 59.7685 1.0463 60.271 1.05507L60.2797 0.555141L60.2885 0.0552172C59.7843 0.0464197 59.2801 0.0388936 58.7759 0.0326388L58.7697 0.5326ZM60.2797 0.555141L60.271 1.05507C60.7534 1.06348 61.2361 1.10207 61.7192 1.17092L61.7897 0.675926L61.8603 0.180929C61.3367 0.106297 60.8127 0.0643647 60.2885 0.0552172L60.2797 0.555141ZM61.7897 0.675926L61.7192 1.17092C62.2428 1.24556 62.7668 1.28749 63.291 1.29663L63.2997 0.796706L63.3085 0.296782C62.826 0.288367 62.3433 0.249778 61.8603 0.180929L61.7897 0.675926ZM63.2997 0.796706L63.291 1.29663C63.8282 1.306 64.3647 1.26122 64.9002 1.16267L64.8097 0.67093L64.7192 0.179188C64.2481 0.265897 63.778 0.304971 63.3085 0.296782L63.2997 0.796706ZM64.8097 0.67093L64.9002 1.16267C65.379 1.07456 65.8571 1.02346 66.3347 1.00918L66.3197 0.5094L66.3048 0.00962323C65.7757 0.0254503 65.2471 0.0820423 64.7192 0.179188L64.8097 0.67093ZM66.3197 0.5094L66.3347 1.00918C66.8001 0.995255 67.2661 1.03384 67.7336 1.12546L67.8297 0.634799L67.9259 0.144135C67.3867 0.0384421 66.8461 -0.00656861 66.3048 0.00962323L66.3197 0.5094ZM67.8297 0.634799L67.7336 1.12546C68.2786 1.2323 68.8253 1.26804 69.3726 1.23203L69.3397 0.733111L69.3069 0.23419C68.8475 0.264416 68.3875 0.23461 67.9259 0.144135L67.8297 0.634799ZM69.3397 0.733111L69.3726 1.23203C69.8585 1.20006 70.3443 1.19358 70.8302 1.21258L70.8497 0.712958L70.8693 0.21334C70.3485 0.192978 69.8276 0.199931 69.3069 0.23419L69.3397 0.733111ZM70.8497 0.712958L70.8302 1.21258C71.3384 1.23244 71.8466 1.2449 72.3548 1.24995L72.3597 0.749977L72.3647 0.250002C71.8662 0.245049 71.3678 0.232829 70.8693 0.21334L70.8497 0.712958ZM72.3597 0.749977L72.3548 1.24995C72.8893 1.25526 73.4234 1.21078 73.9565 1.11678L73.8697 0.624374L73.7829 0.131968C73.3094 0.215445 72.8368 0.254692 72.3647 0.250002L72.3597 0.749977ZM73.8697 0.624374L73.9565 1.11678C74.3919 1.04003 74.8263 1.05308 75.2642 1.1571L75.3797 0.670635L75.4953 0.184174C74.9265 0.0490337 74.3543 0.0312439 73.7829 0.131968L73.8697 0.624374ZM75.3797 0.670635L75.2642 1.1571C75.8334 1.29233 76.406 1.30945 76.9777 1.20732L76.8897 0.715118L76.8018 0.222911C76.3668 0.300625 75.9327 0.288092 75.4953 0.184174L75.3797 0.670635ZM76.8897 0.715118L76.9777 1.20732C77.4254 1.12733 77.8717 1.12399 78.319 1.19714L78.3997 0.703689L78.4804 0.210242C77.9211 0.118781 77.3607 0.123059 76.8018 0.222911L76.8897 0.715118ZM78.3997 0.703689L78.319 1.19714C78.8572 1.28514 79.3964 1.3152 79.9358 1.28702L79.9097 0.787703L79.8837 0.288384C79.4164 0.31279 78.9489 0.286845 78.4804 0.210242L78.3997 0.703689ZM79.9097 0.787703L79.9358 1.28702C80.4318 1.26111 80.9279 1.24611 81.4239 1.24203L81.4197 0.742044L81.4156 0.242061C80.9049 0.246268 80.3943 0.26171 79.8837 0.288384L79.9097 0.787703ZM81.4197 0.742044L81.4239 1.24203C81.9183 1.23795 82.4127 1.24709 82.9072 1.26945L82.9297 0.769963L82.9523 0.270473C82.4401 0.247312 81.9279 0.237841 81.4156 0.242061L81.4197 0.742044ZM82.9297 0.769963L82.9072 1.26945C83.4158 1.29245 83.9245 1.30728 84.4332 1.31394L84.4397 0.813984L84.4463 0.314026C83.9483 0.307509 83.4503 0.292991 82.9523 0.270473L82.9297 0.769963ZM84.4397 0.813984L84.4332 1.31394C84.9732 1.32101 85.5124 1.2691 86.0502 1.15874L85.9497 0.668947L85.8492 0.179152C85.3804 0.275355 84.9129 0.320134 84.4463 0.314026L84.4397 0.813984ZM85.9497 0.668947L86.0502 1.15874C86.5109 1.06422 86.9697 1.03196 87.4279 1.06122L87.4597 0.562232L87.4916 0.0632484C86.9431 0.0282295 86.3953 0.067113 85.8492 0.179152L85.9497 0.668947ZM87.4597 0.562232L87.4279 1.06122C87.9262 1.09303 88.4246 1.13236 88.923 1.17918L88.9697 0.681377L89.0165 0.18357C88.5082 0.13581 87.9999 0.0957025 87.4916 0.0632484L87.4597 0.562232ZM88.9697 0.681377L88.923 1.17918C89.4341 1.22721 89.9453 1.26311 90.4565 1.28688L90.4797 0.787421L90.503 0.287961C90.0075 0.264926 89.512 0.23013 89.0165 0.18357L88.9697 0.681377ZM90.4797 0.787421L90.4565 1.28688C90.9674 1.31063 91.4783 1.3228 91.9892 1.32337L91.9897 0.823368L91.9903 0.323369C91.4945 0.322814 90.9987 0.311012 90.503 0.287961L90.4797 0.787421ZM91.9897 0.823368L91.9892 1.32337C92.5192 1.32396 93.0488 1.28232 93.5779 1.19863L93.4997 0.704771L93.4216 0.210913C92.944 0.286474 92.4669 0.323902 91.9903 0.323369L91.9897 0.823368ZM93.4997 0.704771L93.5779 1.19863C94.0622 1.12201 94.5461 1.07405 95.0298 1.05468L95.0097 0.555076L94.9897 0.0554769C94.4667 0.0764345 93.944 0.128276 93.4216 0.210913L93.4997 0.704771ZM95.0097 0.555076L95.0298 1.05468C95.4899 1.03624 95.9509 1.07677 96.4138 1.17709L96.5197 0.688426L96.6256 0.199768C96.0819 0.0819429 95.5363 0.0335771 94.9897 0.0554769L95.0097 0.555076ZM96.5197 0.688426L96.4138 1.17709C96.9604 1.29552 97.509 1.33941 98.0584 1.30787L98.0297 0.808693L98.0011 0.309514C97.5438 0.33576 97.0858 0.299474 96.6256 0.199768L96.5197 0.688426ZM98.0297 0.808693L98.0584 1.30787C98.572 1.27839 99.0855 1.23306 99.5989 1.1719L99.5397 0.67541L99.4806 0.178921C98.9873 0.237684 98.4942 0.281211 98.0011 0.309514L98.0297 0.808693ZM99.5397 0.67541L99.5989 1.1719C100.059 1.1171 100.519 1.12254 100.979 1.18834L101.05 0.693367L101.12 0.198396C100.574 0.120328 100.027 0.113797 99.4806 0.178921L99.5397 0.67541ZM101.05 0.693367L100.979 1.18834C101.529 1.26695 102.08 1.26724 102.63 1.18919L102.56 0.694149L102.49 0.199107C102.033 0.263885 101.577 0.263651 101.12 0.198396L101.05 0.693367ZM102.56 0.694149L102.63 1.18919C103.084 1.12483 103.537 1.12892 103.991 1.20162L104.07 0.707904L104.149 0.214191C103.596 0.125718 103.043 0.120644 102.49 0.199107L102.56 0.694149ZM104.07 0.707904L103.991 1.20162C104.556 1.29212 105.122 1.27614 105.685 1.15449L105.58 0.665757L105.474 0.17702C105.031 0.272735 104.59 0.284859 104.149 0.214191L104.07 0.707904ZM105.58 0.665757L105.685 1.15449C106.155 1.05301 106.624 1.00174 107.091 1.00012L107.09 0.500123L107.088 0.000126183C106.549 0.00199032 106.011 0.0611429 105.474 0.17702L105.58 0.665757ZM107.09 0.500123L107.091 1.00012C107.587 0.998405 108.083 1.00757 108.58 1.02763L108.6 0.528036L108.62 0.0284443C108.109 0.00779992 107.599 -0.00163972 107.088 0.000126183L107.09 0.500123ZM108.6 0.528036L108.58 1.02763C109.064 1.04722 109.549 1.09419 110.034 1.16864L110.11 0.674424L110.186 0.180209C109.664 0.100169 109.142 0.0495552 108.62 0.0284443L108.6 0.528036ZM110.11 0.674424L110.034 1.16864C110.595 1.25482 111.158 1.24158 111.718 1.12947L111.62 0.639191L111.522 0.148912C111.075 0.238268 110.631 0.248514 110.186 0.180209L110.11 0.674424ZM111.62 0.639191L111.718 1.12947C112.165 1.03991 112.611 1.0283 113.057 1.09404L113.13 0.599386L113.203 0.104731C112.642 0.0220819 112.081 0.0370093 111.522 0.148912L111.62 0.639191ZM113.13 0.599386L113.057 1.09404C113.604 1.17476 114.153 1.18142 114.701 1.1139L114.64 0.61765L114.579 0.121403C114.12 0.177914 113.662 0.1724 113.203 0.104731L113.13 0.599386ZM114.64 0.61765L114.701 1.1139C115.168 1.05634 115.635 1.05024 116.101 1.09553L116.15 0.597864L116.198 0.1002C115.658 0.0478299 115.118 0.0549237 114.579 0.121403L114.64 0.61765ZM116.15 0.597864L116.101 1.09553C116.633 1.1471 117.165 1.15309 117.697 1.11345L117.66 0.61483L117.623 0.116213C117.148 0.151604 116.673 0.146277 116.198 0.1002L116.15 0.597864ZM117.66 0.61483L117.697 1.11345C118.159 1.07903 118.621 1.10196 119.084 1.18266L119.17 0.690083L119.256 0.197502C118.712 0.102833 118.167 0.0755963 117.623 0.116213L117.66 0.61483ZM119.17 0.690083L119.084 1.18266C119.614 1.27495 120.144 1.32371 120.675 1.32872L120.68 0.828745L120.684 0.328768C120.209 0.324273 119.732 0.280592 119.256 0.197502L119.17 0.690083ZM120.68 0.828745L120.675 1.32872C121.181 1.3335 121.686 1.33473 122.192 1.33242L122.19 0.83243L122.187 0.332435C121.686 0.334724 121.185 0.333501 120.684 0.328768L120.68 0.828745ZM122.19 0.83243L122.192 1.33242C122.725 1.32999 123.258 1.27999 123.79 1.18273L123.7 0.69089L123.61 0.199049C123.135 0.285906 122.661 0.330272 122.187 0.332435L122.19 0.83243ZM123.7 0.69089L123.79 1.18273C124.241 1.10009 124.692 1.08961 125.142 1.15087L125.21 0.65543L125.277 0.159986C124.721 0.0844039 124.165 0.0975633 123.61 0.199049L123.7 0.69089ZM125.21 0.65543L125.142 1.15087C125.659 1.22106 126.175 1.27063 126.692 1.29955L126.72 0.800334L126.748 0.301116C126.258 0.273677 125.767 0.226643 125.277 0.159986L125.21 0.65543ZM126.72 0.800334L126.692 1.29955C127.204 1.32824 127.717 1.34291 128.229 1.34356L128.23 0.843556L128.23 0.343557C127.736 0.342935 127.242 0.328789 126.748 0.301116L126.72 0.800334ZM128.23 0.843556L128.229 1.34356C128.769 1.34424 129.307 1.28673 129.845 1.1716L129.74 0.682706L129.635 0.193806C129.165 0.294419 128.697 0.344145 128.23 0.343557L128.23 0.843556ZM129.74 0.682706L129.845 1.1716C130.316 1.07062 130.786 1.01812 131.255 1.0136L131.25 0.513621L131.245 0.0136442C130.707 0.0188313 130.17 0.0790567 129.635 0.193806L129.74 0.682706ZM131.25 0.513621L131.255 1.0136C131.721 1.0091 132.189 1.0554 132.658 1.15308L132.76 0.663583L132.862 0.174087C132.324 0.0621009 131.785 0.00843525 131.245 0.0136442L131.25 0.513621ZM132.76 0.663583L132.658 1.15308C133.213 1.26861 133.769 1.29499 134.326 1.23134L134.27 0.734576L134.213 0.237807C133.763 0.289169 133.314 0.268215 132.862 0.174087L132.76 0.663583ZM134.27 0.734576L134.326 1.23134C134.797 1.17758 135.267 1.17054 135.738 1.21018L135.78 0.711946L135.822 0.213712C135.285 0.168518 134.749 0.176571 134.213 0.237807L134.27 0.734576ZM135.78 0.711946L135.738 1.21018C136.266 1.25472 136.795 1.25901 137.324 1.22302L137.29 0.724177L137.256 0.225332C136.778 0.257869 136.3 0.254001 135.822 0.213712L135.78 0.711946ZM137.29 0.724177L137.324 1.22302C137.81 1.18995 138.295 1.18246 138.781 1.20054L138.8 0.700887L138.818 0.201233C138.297 0.181845 137.777 0.189881 137.256 0.225332L137.29 0.724177ZM138.8 0.700887L138.781 1.20054C139.278 1.21903 139.775 1.24731 140.272 1.28539L140.31 0.786853L140.348 0.288315C139.838 0.249239 139.328 0.220211 138.818 0.201233L138.8 0.700887ZM140.31 0.786853L140.272 1.28539C140.802 1.32608 141.333 1.32264 141.864 1.27509L141.82 0.777086L141.775 0.27908C141.299 0.321703 140.824 0.324776 140.348 0.288315L140.31 0.786853ZM141.82 0.777086L141.864 1.27509C142.355 1.23115 142.845 1.20623 143.336 1.20033L143.33 0.70037L143.324 0.200406C142.807 0.206617 142.291 0.232846 141.775 0.27908L141.82 0.777086ZM143.33 0.70037L143.336 1.20033C143.826 1.19443 144.316 1.20762 144.807 1.23991L144.84 0.740992L144.873 0.242072C144.356 0.208085 143.84 0.194194 143.324 0.200406L143.33 0.70037ZM144.84 0.740992L144.807 1.23991C145.33 1.27432 145.852 1.2783 146.375 1.25185L146.35 0.752492L146.324 0.253131C145.841 0.27762 145.357 0.273936 144.873 0.242072L144.84 0.740992ZM146.35 0.752492L146.375 1.25185C146.866 1.22702 147.357 1.22064 147.847 1.23272L147.86 0.732868L147.872 0.233019C147.356 0.220324 146.84 0.227029 146.324 0.253131L146.35 0.752492ZM147.86 0.732868L147.847 1.23272C148.345 1.24495 148.842 1.26642 149.339 1.29711L149.37 0.798061L149.401 0.299012C148.891 0.267557 148.382 0.245559 147.872 0.233019L147.86 0.732868ZM149.37 0.798061L149.339 1.29711C149.865 1.32956 150.39 1.32675 150.916 1.28869L150.88 0.789993L150.844 0.291299C150.363 0.326141 149.882 0.328709 149.401 0.299012L149.37 0.798061ZM150.88 0.789993L150.916 1.28869C151.415 1.2525 151.915 1.22188 152.415 1.19683L152.39 0.697461L152.365 0.198089C151.858 0.223508 151.351 0.254578 150.844 0.291299L150.88 0.789993ZM152.39 0.697461L152.415 1.19683C152.926 1.17123 153.436 1.13412 153.947 1.08553L153.9 0.587782L153.852 0.0900291C153.356 0.137211 152.861 0.173229 152.365 0.198089L152.39 0.697461ZM153.9 0.587782L153.947 1.08553C154.407 1.04181 154.866 1.0584 155.327 1.13566L155.41 0.642539L155.492 0.149422C154.947 0.0578969 154.399 0.0379788 153.852 0.0900291L153.9 0.587782ZM155.41 0.642539L155.327 1.13566C155.887 1.2295 156.448 1.22675 157.007 1.12753L156.92 0.63521L156.832 0.142894C156.385 0.222218 155.939 0.224358 155.492 0.149422L155.41 0.642539ZM156.92 0.63521L157.007 1.12753C157.482 1.0433 157.956 1.00144 158.429 1.00168L158.43 0.501677L158.43 0.00167668C157.897 0.00140727 157.364 0.0485701 156.832 0.142894L156.92 0.63521ZM158.43 0.501677L158.429 1.00168C158.902 1.00192 159.376 1.04507 159.85 1.13143L159.94 0.639511L160.029 0.147597C159.497 0.0506853 158.964 0.00194639 158.43 0.00167668L158.43 0.501677ZM159.94 0.639511L159.85 1.13143C160.386 1.22904 160.924 1.27175 161.461 1.25916L161.45 0.759299L161.438 0.259436C160.969 0.270406 160.5 0.233251 160.029 0.147597L159.94 0.639511ZM161.45 0.759299L161.461 1.25916C161.97 1.24727 162.478 1.22792 162.986 1.20111L162.96 0.701805L162.933 0.2025C162.435 0.228795 161.936 0.247774 161.438 0.259436L161.45 0.759299ZM162.96 0.701805L162.986 1.20111C163.477 1.17519 163.969 1.1671 164.46 1.17682L164.47 0.676916L164.48 0.177014C163.964 0.166814 163.449 0.175311 162.933 0.2025L162.96 0.701805ZM164.47 0.676916L164.46 1.17682C164.977 1.18704 165.493 1.17661 166.01 1.14554L165.98 0.646444L165.95 0.147346C165.46 0.176823 164.97 0.18671 164.48 0.177014L164.47 0.676916ZM165.98 0.646444L166.01 1.14554C166.482 1.11714 166.954 1.13265 167.427 1.19222L167.49 0.696139L167.552 0.200059C167.018 0.132814 166.484 0.115204 165.95 0.147346L165.98 0.646444ZM167.49 0.696139L167.427 1.19222C167.953 1.25839 168.478 1.28925 169.004 1.28472L169 0.784734L168.995 0.284753C168.515 0.288899 168.034 0.260694 167.552 0.200059L167.49 0.696139ZM169 0.784734L169.004 1.28472C169.499 1.28044 169.995 1.28807 170.49 1.30761L170.51 0.807996L170.529 0.308384C170.018 0.288221 169.507 0.280344 168.995 0.284753L169 0.784734ZM170.51 0.807996L170.49 1.30761C171.012 1.32821 171.535 1.31898 172.057 1.27996L172.02 0.78135L171.982 0.28274C171.498 0.318939 171.014 0.327482 170.529 0.308384L170.51 0.807996ZM172.02 0.78135L172.057 1.27996C172.539 1.24392 173.021 1.23854 173.504 1.2638L173.53 0.764487L173.556 0.265171C173.031 0.237693 172.507 0.243553 171.982 0.28274L172.02 0.78135ZM173.53 0.764487L173.504 1.2638C174.035 1.29162 174.566 1.27538 175.096 1.21514L175.04 0.718334L174.983 0.221525C174.507 0.275568 174.032 0.290091 173.556 0.265171L173.53 0.764487ZM175.04 0.718334L175.096 1.21514C175.585 1.15969 176.073 1.12632 176.561 1.11502L176.55 0.615151L176.538 0.115285C176.02 0.127284 175.501 0.162707 174.983 0.221525L175.04 0.718334ZM176.55 0.615151L176.561 1.11502C177.047 1.10377 177.533 1.11801 178.019 1.15776L178.06 0.659427L178.1 0.161091C177.58 0.118503 177.059 0.103228 176.538 0.115285L176.55 0.615151ZM178.06 0.659427L178.019 1.15776C178.561 1.20211 179.103 1.18313 179.645 1.10106L179.57 0.606714L179.495 0.112363C179.029 0.182916 178.565 0.199077 178.1 0.161091L178.06 0.659427ZM179.57 0.606714L179.645 1.10106C180.095 1.03286 180.544 1.03778 180.994 1.11604L181.08 0.623416L181.165 0.130795C180.609 0.0341629 180.052 0.0279496 179.495 0.112363L179.57 0.606714ZM181.08 0.623416L180.994 1.11604C181.52 1.20745 182.047 1.26138 182.574 1.27767L182.59 0.777906L182.605 0.278145C182.126 0.263333 181.646 0.214271 181.165 0.130795L181.08 0.623416ZM182.59 0.777906L182.574 1.27767C183.107 1.29413 183.64 1.26333 184.172 1.18544L184.1 0.690709L184.027 0.195982C183.553 0.265448 183.079 0.292776 182.605 0.278145L182.59 0.777906ZM184.1 0.690709L184.172 1.18544C184.624 1.11927 185.075 1.12365 185.528 1.19874L185.61 0.705486L185.692 0.212232C185.137 0.120257 184.582 0.114786 184.027 0.195982L184.1 0.690709ZM185.61 0.705486L185.528 1.19874C186.085 1.29112 186.642 1.2926 187.199 1.20313L187.12 0.709462L187.04 0.215795C186.591 0.288087 186.142 0.286916 185.692 0.212232L185.61 0.705486ZM187.12 0.709462L187.199 1.20313C187.678 1.12619 188.156 1.08583 188.634 1.08188L188.63 0.581901L188.626 0.0819181C188.097 0.08629 187.568 0.13097 187.04 0.215795L187.12 0.709462ZM188.63 0.581901L188.634 1.08188C189.143 1.07767 189.653 1.06424 190.162 1.04157L190.14 0.542068L190.118 0.0425624C189.62 0.0646886 189.123 0.0778069 188.626 0.0819181L188.63 0.581901ZM190.14 0.542068L190.162 1.04157C190.638 1.02041 191.114 1.03849 191.59 1.09592L191.65 0.599519L191.71 0.103115C191.179 0.0391575 190.648 0.0189416 190.118 0.0425624L190.14 0.542068ZM191.65 0.599519L191.59 1.09592C192.097 1.1571 192.605 1.21204 193.112 1.26073L193.16 0.763014L193.208 0.265301C192.708 0.217387 192.209 0.163325 191.71 0.103115L191.65 0.599519ZM193.16 0.763014L193.112 1.26073C193.663 1.31359 194.213 1.28798 194.762 1.18448L194.67 0.693139L194.577 0.201793C194.119 0.288072 193.663 0.309048 193.208 0.265301L193.16 0.763014ZM194.67 0.693139L194.762 1.18448C195.228 1.09669 195.692 1.06396 196.156 1.08579L196.18 0.586344L196.203 0.0868969C195.66 0.0613464 195.118 0.0998132 194.577 0.201793L194.67 0.693139ZM196.18 0.586344L196.156 1.08579C196.657 1.10936 197.158 1.1368 197.659 1.1681L197.69 0.669073L197.721 0.170047C197.215 0.138424 196.709 0.110708 196.203 0.0868969L196.18 0.586344ZM197.69 0.669073L197.659 1.1681C198.188 1.2012 198.718 1.19258 199.247 1.14228L199.2 0.644526L199.152 0.146768C198.675 0.192123 198.198 0.199871 197.721 0.170047L197.69 0.669073ZM199.2 0.644526L199.247 1.14228C199.732 1.09618 200.217 1.07687 200.702 1.08431L200.71 0.584364L200.717 0.0844229C200.196 0.0764202 199.674 0.0972118 199.152 0.146768L199.2 0.644526ZM200.71 0.584364L200.702 1.08431C201.182 1.09167 201.663 1.13234 202.144 1.20645L202.22 0.71228L202.296 0.218112C201.77 0.137103 201.244 0.0924988 200.717 0.0844229L200.71 0.584364Z" /> + android:pathData="M202.22 0.71228L202.144 1.20645C202.681 1.28923 203.219 1.31595 203.757 1.28637L203.73 0.787123L203.702 0.287876C203.234 0.313617 202.765 0.290445 202.296 0.218112L202.22 0.71228ZM203.73 0.787123L203.757 1.28637C204.279 1.25767 204.801 1.19941 205.323 1.11168L205.24 0.618609L205.157 0.125538C204.672 0.207165 204.187 0.261249 203.702 0.287876L203.73 0.787123ZM205.24 0.618609L205.323 1.11168C205.787 1.03352 206.251 1.01119 206.714 1.04434L206.75 0.545616L206.785 0.046891C206.242 0.00800472 205.699 0.0343426 205.157 0.125538L205.24 0.618609ZM206.75 0.545616L206.714 1.04434C207.23 1.08126 207.746 1.09848 208.262 1.09599L208.26 0.596001L208.257 0.0960065C207.767 0.0983728 207.276 0.0820038 206.785 0.046891L206.75 0.545616ZM208.26 0.596001L208.262 1.09599C208.747 1.09366 209.232 1.11796 209.717 1.16895L209.77 0.67169L209.822 0.174428C209.301 0.119643 208.779 0.0934901 208.257 0.0960065L208.26 0.596001ZM209.77 0.67169L209.717 1.16895C210.25 1.22495 210.784 1.23311 211.317 1.19339L211.28 0.694771L211.243 0.196153C210.769 0.231429 210.296 0.224204 209.822 0.174428L209.77 0.67169ZM211.28 0.694771L211.317 1.19339C211.807 1.15691 212.296 1.14068 212.786 1.14468L212.79 0.644697L212.794 0.144714C212.277 0.140483 211.76 0.157633 211.243 0.196153L211.28 0.694771ZM212.79 0.644697L212.786 1.14468C213.294 1.14884 213.803 1.14488 214.312 1.1328L214.3 0.63294L214.288 0.133081C213.79 0.144911 213.292 0.148788 212.794 0.144714L212.79 0.644697ZM214.3 0.63294L214.312 1.1328C214.798 1.12123 215.285 1.13375 215.772 1.17036L215.81 0.671767L215.847 0.173174C215.327 0.134102 214.808 0.120732 214.288 0.133081L214.3 0.63294ZM215.81 0.671767L215.772 1.17036C216.277 1.20834 216.783 1.24343 217.288 1.27561L217.32 0.776624L217.352 0.277635C216.85 0.245693 216.349 0.210873 215.847 0.173174L215.81 0.671767ZM217.32 0.776624L217.288 1.27561C217.825 1.30982 218.362 1.28987 218.898 1.21595L218.83 0.720632L218.761 0.225317C218.291 0.290171 217.821 0.307554 217.352 0.277635L217.32 0.776624ZM218.83 0.720632L218.898 1.21595C219.39 1.14808 219.882 1.09703 220.374 1.06276L220.34 0.56397L220.305 0.0651781C219.79 0.101016 219.276 0.154402 218.761 0.225317L218.83 0.720632ZM220.34 0.56397L220.374 1.06276C220.856 1.02921 221.338 1.02689 221.82 1.05581L221.85 0.556703L221.88 0.0576012C221.355 0.0261005 220.83 0.0286281 220.305 0.0651781L220.34 0.56397ZM221.85 0.556703L221.82 1.05581C222.329 1.08639 222.839 1.10739 223.349 1.1188L223.36 0.618929L223.371 0.119054C222.874 0.107918 222.377 0.0874337 221.88 0.0576012L221.85 0.556703ZM223.36 0.618929L223.349 1.1188C223.843 1.12989 224.338 1.15353 224.833 1.18973L224.87 0.691065L224.906 0.192397C224.394 0.15497 223.883 0.130521 223.371 0.119054L223.36 0.618929ZM224.87 0.691065L224.833 1.18973C225.378 1.22958 225.923 1.20172 226.466 1.10656L226.38 0.614057L226.293 0.121554C225.83 0.202702 225.368 0.226174 224.906 0.192397L224.87 0.691065ZM226.38 0.614057L226.466 1.10656C226.905 1.02964 227.343 1.0385 227.784 1.13381L227.89 0.64511L227.995 0.156408C227.429 0.0340084 226.861 0.0221685 226.293 0.121554L226.38 0.614057ZM227.89 0.64511L227.784 1.13381C228.332 1.25229 228.882 1.29391 229.432 1.25778L229.4 0.758855L229.367 0.259928C228.911 0.289845 228.454 0.255642 227.995 0.156408L227.89 0.64511ZM229.4 0.758855L229.432 1.25778C229.922 1.22565 230.412 1.2136 230.902 1.22161L230.91 0.72168L230.918 0.221747C230.401 0.213283 229.884 0.226012 229.367 0.259928L229.4 0.758855ZM230.91 0.72168L230.902 1.22161C231.397 1.22973 231.893 1.24891 232.389 1.27916L232.42 0.780091L232.45 0.281018C231.939 0.249866 231.429 0.230109 230.918 0.221747L230.91 0.72168ZM232.42 0.780091L232.389 1.27916C232.925 1.31185 233.461 1.29227 233.996 1.22058L233.93 0.725006L233.863 0.229433C233.392 0.292588 232.921 0.309733 232.45 0.281018L232.42 0.780091ZM233.93 0.725006L233.996 1.22058C234.479 1.15591 234.961 1.12173 235.444 1.11797L235.44 0.617986L235.436 0.118001C234.911 0.122089 234.387 0.159259 233.863 0.229433L233.93 0.725006ZM235.44 0.617986L235.444 1.11797C235.937 1.11413 236.43 1.12507 236.924 1.15082L236.95 0.651499L236.976 0.152178C236.462 0.125393 235.949 0.114 235.436 0.118001L235.44 0.617986ZM236.95 0.651499L236.924 1.15082C237.442 1.17784 237.959 1.18226 238.477 1.16406L238.46 0.664367L238.442 0.164676C237.953 0.181851 237.465 0.177686 236.976 0.152178L236.95 0.651499ZM238.46 0.664367L238.477 1.16406C238.965 1.14694 239.452 1.15332 239.939 1.18322L239.97 0.684155L240 0.185094C239.481 0.153232 238.962 0.146424 238.442 0.164676L238.46 0.664367ZM239.97 0.684155L239.939 1.18322C240.469 1.21571 240.998 1.20638 241.528 1.15527L241.48 0.65758L241.432 0.159894C240.954 0.205972 240.477 0.214359 240 0.185094L239.97 0.684155ZM241.48 0.65758L241.528 1.15527C242.022 1.10757 242.516 1.07391 243.01 1.05428L242.99 0.554677L242.97 0.0550715C242.457 0.0754532 241.944 0.110396 241.432 0.159894L241.48 0.65758ZM242.99 0.554677L243.01 1.05428C243.505 1.03459 244.001 1.02655 244.496 1.03016L244.5 0.530175L244.503 0.0301881C243.992 0.0264598 243.481 0.0347546 242.97 0.0550715L242.99 0.554677ZM244.5 0.530175L244.496 1.03016C244.982 1.03371 245.469 1.06205 245.955 1.11523L246.01 0.618184L246.064 0.121143C245.544 0.0643132 245.024 0.0339834 244.503 0.0301881L244.5 0.530175ZM246.01 0.618184L245.955 1.11523C246.48 1.17252 247.004 1.19632 247.529 1.18657L247.52 0.686655L247.51 0.186741C247.028 0.195699 246.546 0.173851 246.064 0.121143L246.01 0.618184ZM247.52 0.686655L247.529 1.18657C248.04 1.17707 248.551 1.156 249.062 1.12335L249.03 0.624365L248.998 0.125383C248.502 0.157075 248.006 0.177527 247.51 0.186741L247.52 0.686655ZM249.03 0.624365L249.062 1.12335C249.529 1.09348 249.996 1.11392 250.465 1.18492L250.54 0.690568L250.615 0.196217C250.076 0.114601 249.537 0.090909 248.998 0.125383L249.03 0.624365ZM250.54 0.690568L250.465 1.18492C251.005 1.26675 251.545 1.28817 252.086 1.24893L252.05 0.750245L252.014 0.251556C251.548 0.285364 251.082 0.267 250.615 0.196217L250.54 0.690568ZM252.05 0.750245L252.086 1.24893C252.561 1.21443 253.037 1.21977 253.512 1.26497L253.56 0.767216L253.607 0.269459C253.076 0.218988 252.545 0.213011 252.014 0.251556L252.05 0.750245ZM253.56 0.767216L253.512 1.26497C254.045 1.31557 254.577 1.31941 255.11 1.27647L255.07 0.778084L255.03 0.279702C254.555 0.317942 254.081 0.314534 253.607 0.269459L253.56 0.767216ZM255.07 0.778084L255.11 1.27647C255.603 1.23674 256.095 1.21294 256.588 1.20507L256.58 0.705138L256.572 0.205202C256.058 0.213413 255.544 0.238248 255.03 0.279702L255.07 0.778084ZM256.58 0.705138L256.588 1.20507C257.111 1.19671 257.635 1.15665 258.158 1.08496L258.09 0.58959L258.022 0.0942224C257.538 0.160519 257.055 0.197487 256.572 0.205202L256.58 0.705138ZM258.09 0.58959L258.158 1.08496C258.624 1.02102 259.09 1.00976 259.556 1.05102L259.6 0.552968L259.644 0.0549181C259.103 0.00701553 258.562 0.0201682 258.022 0.0942224L258.09 0.58959ZM259.6 0.552968L259.556 1.05102C260.087 1.09804 260.618 1.10082 261.149 1.05934L261.11 0.56086L261.071 0.0623785C260.595 0.0995352 260.12 0.0970529 259.644 0.0549181L259.6 0.552968ZM261.11 0.56086L261.149 1.05934C261.638 1.02114 262.127 1.00399 262.616 1.00788L262.62 0.507899L262.624 0.00791514C262.106 0.00378942 261.588 0.021948 261.071 0.0623785L261.11 0.56086ZM262.62 0.507899L262.616 1.00788C263.117 1.01188 263.618 1.019 264.12 1.02925L264.13 0.529353L264.14 0.0294574C263.635 0.0191237 263.129 0.011943 262.624 0.00791514L262.62 0.507899ZM264.13 0.529353L264.12 1.02925C264.632 1.03972 265.144 1.03666 265.656 1.02007L265.64 0.520332L265.624 0.0205939C265.129 0.0366142 264.634 0.0395686 264.14 0.0294574L264.13 0.529353ZM265.64 0.520332L265.656 1.02007C266.132 1.00465 266.608 1.02833 267.084 1.09121L267.15 0.595505L267.215 0.099803C266.685 0.0298418 266.154 0.00339806 265.624 0.0205939L265.64 0.520332ZM267.15 0.595505L267.084 1.09121C267.609 1.16051 268.135 1.19466 268.661 1.19357L268.66 0.69357L268.659 0.193571C268.178 0.194569 267.697 0.163342 267.215 0.099803L267.15 0.595505ZM268.66 0.69357L268.661 1.19357C269.152 1.19255 269.644 1.20883 270.136 1.24241L270.17 0.743572L270.204 0.244735C269.689 0.209558 269.174 0.192502 268.659 0.193571L268.66 0.69357ZM270.17 0.743572L270.136 1.24241C270.671 1.279 271.207 1.26359 271.742 1.19629L271.68 0.700201L271.617 0.204109C271.146 0.263402 270.675 0.276905 270.204 0.244735L270.17 0.743572ZM271.68 0.700201L271.742 1.19629C272.201 1.13861 272.659 1.14295 273.118 1.20941L273.19 0.714572L273.261 0.219731C272.714 0.140443 272.165 0.135201 271.617 0.204109L271.68 0.700201ZM273.19 0.714572L273.118 1.20941C273.667 1.28889 274.217 1.29201 274.766 1.2187L274.7 0.723092L274.634 0.227489C274.176 0.288565 273.719 0.286002 273.261 0.219731L273.19 0.714572ZM274.7 0.723092L274.766 1.2187C275.248 1.15432 275.73 1.12129 276.212 1.11953L276.21 0.619529L276.208 0.119532C275.683 0.12146 275.158 0.157474 274.634 0.227489L274.7 0.723092ZM276.21 0.619529L276.212 1.11953C276.722 1.11765 277.233 1.10466 277.743 1.08055L277.72 0.581107L277.696 0.0816631C277.2 0.105087 276.704 0.11771 276.208 0.119532L276.21 0.619529ZM277.72 0.581107L277.743 1.08055C278.216 1.05822 278.689 1.0788 279.163 1.14245L279.23 0.646901L279.296 0.151354C278.763 0.0797428 278.23 0.0564644 277.696 0.0816631L277.72 0.581107ZM279.23 0.646901L279.163 1.14245C279.694 1.21372 280.225 1.24092 280.756 1.22391L280.74 0.72417L280.724 0.224426C280.248 0.239651 279.773 0.215337 279.296 0.151354L279.23 0.646901ZM280.74 0.72417L280.756 1.22391C281.265 1.2076 281.775 1.18161 282.285 1.14596L282.25 0.647182L282.215 0.148401C281.718 0.183172 281.221 0.208513 280.724 0.224426L280.74 0.72417ZM282.25 0.647182L282.285 1.14596C282.756 1.11297 283.228 1.12489 283.7 1.18181L283.76 0.685408L283.82 0.189004C283.285 0.124537 282.75 0.11097 282.215 0.148401L282.25 0.647182ZM283.76 0.685408L283.7 1.18181C284.222 1.24481 284.745 1.27734 285.268 1.27935L285.27 0.77935L285.272 0.279354C284.788 0.277495 284.304 0.247396 283.82 0.189004L283.76 0.685408ZM285.27 0.77935L285.268 1.27935C285.782 1.28132 286.296 1.26675 286.81 1.23562L286.78 0.736534L286.75 0.237448C286.257 0.26728 285.764 0.281247 285.272 0.279354L285.27 0.77935ZM286.78 0.736534L286.81 1.23562C287.311 1.20527 287.812 1.17824 288.313 1.15453L288.29 0.655085L288.266 0.155644C287.761 0.179569 287.255 0.206837 286.75 0.237448L286.78 0.736534ZM288.29 0.655085L288.313 1.15453C288.819 1.13061 289.324 1.10379 289.829 1.07405L289.8 0.574915L289.77 0.0757789C289.269 0.105291 288.768 0.131913 288.266 0.155644L288.29 0.655085ZM289.8 0.574915L289.829 1.07405C290.319 1.04524 290.808 1.03692 291.297 1.04907L291.31 0.549226L291.322 0.0493796C290.805 0.0365344 290.288 0.045336 289.77 0.0757789L289.8 0.574915ZM291.31 0.549226L291.297 1.04907C291.808 1.06176 292.319 1.06285 292.83 1.05235L292.82 0.552451L292.809 0.0525565C292.314 0.0627495 291.818 0.0616906 291.322 0.0493796L291.31 0.549226ZM292.82 0.552451L292.83 1.05235C293.318 1.04232 293.806 1.05505 294.293 1.09056L294.33 0.591881L294.366 0.0932007C293.847 0.0554397 293.328 0.0418877 292.809 0.0525565L292.82 0.552451ZM294.33 0.591881L294.293 1.09056C294.798 1.12731 295.303 1.16175 295.808 1.1939L295.84 0.694914L295.872 0.195925C295.37 0.163968 294.868 0.129727 294.366 0.0932007L294.33 0.591881ZM295.84 0.694914L295.808 1.1939C296.34 1.22776 296.871 1.21649 297.402 1.16015L297.35 0.662938L297.297 0.165725C296.822 0.216131 296.347 0.226178 295.872 0.195925L295.84 0.694914ZM297.35 0.662938L297.402 1.16015C297.888 1.10866 298.373 1.08345 298.859 1.08447L298.86 0.584475L298.861 0.0844765C298.339 0.0833755 297.818 0.110471 297.297 0.165725L297.35 0.662938ZM298.86 0.584475L298.859 1.08447C299.334 1.08548 299.81 1.12623 300.286 1.20696L300.37 0.713985L300.453 0.22101C299.923 0.131183 299.392 0.0855985 298.861 0.0844765L298.86 0.584475ZM300.37 0.713985L300.286 1.20696C300.823 1.29797 301.362 1.33266 301.9 1.31069L301.88 0.811108L301.859 0.311523C301.391 0.330614 300.923 0.300552 300.453 0.22101L300.37 0.713985ZM301.88 0.811108L301.9 1.31069C302.408 1.28997 302.916 1.26194 303.424 1.22659L303.39 0.727796L303.355 0.229002C302.856 0.263683 302.358 0.29119 301.859 0.311523L301.88 0.811108ZM303.39 0.727796L303.424 1.22659C303.91 1.19281 304.396 1.18491 304.881 1.20289L304.9 0.70323L304.918 0.203573C304.397 0.184279 303.876 0.192759 303.355 0.229002L303.39 0.727796ZM304.9 0.70323L304.881 1.20289C305.411 1.22252 305.941 1.1995 306.471 1.13392L306.41 0.637706L306.348 0.141495C305.871 0.200554 305.395 0.221213 304.918 0.203573L304.9 0.70323ZM306.41 0.637706L306.471 1.13392C306.938 1.07613 307.404 1.07035 307.871 1.1165L307.92 0.618929L307.969 0.121359C307.429 0.0679044 306.888 0.0746419 306.348 0.141495L306.41 0.637706ZM307.92 0.618929L307.871 1.1165C308.413 1.17017 308.956 1.15984 309.498 1.08563L309.43 0.590253L309.362 0.0948752C308.897 0.158505 308.433 0.167287 307.969 0.121359L307.92 0.618929ZM309.43 0.590253L309.498 1.08563C309.965 1.02161 310.432 1.00879 310.899 1.047L310.94 0.548664L310.98 0.0503304C310.441 0.0061571 309.901 0.0210584 309.362 0.0948752L309.43 0.590253ZM310.94 0.548664L310.899 1.047C311.408 1.08862 311.916 1.1221 312.425 1.14742L312.45 0.648041L312.475 0.148659C311.977 0.123866 311.479 0.0910901 310.98 0.0503304L310.94 0.548664ZM312.45 0.648041L312.425 1.14742C312.935 1.17282 313.445 1.1878 313.955 1.19236L313.96 0.692384L313.964 0.192404C313.468 0.187961 312.971 0.17338 312.475 0.148659L312.45 0.648041ZM313.96 0.692384L313.955 1.19236C314.443 1.19672 314.93 1.22454 315.417 1.27584L315.47 0.778584L315.522 0.28133C315.003 0.226702 314.484 0.197051 313.964 0.192404L313.96 0.692384ZM315.47 0.778584L315.417 1.27584C315.953 1.33222 316.489 1.33587 317.025 1.28673L316.98 0.788822L316.934 0.290909C316.463 0.334056 315.993 0.330872 315.522 0.28133L315.47 0.778584ZM316.98 0.788822L317.025 1.28673C317.526 1.24089 318.026 1.19988 318.526 1.16372L318.49 0.665023L318.454 0.166325C317.947 0.202951 317.441 0.244479 316.934 0.290909L316.98 0.788822ZM318.49 0.665023L318.526 1.16372C318.993 1.12992 319.461 1.14643 319.929 1.21343L320 0.718476L320.071 0.223519C319.532 0.146463 318.993 0.127333 318.454 0.166325L318.49 0.665023ZM320 0.718476L319.929 1.21343C320.458 1.2891 320.987 1.32349 321.516 1.31648L321.51 0.816521L321.503 0.316565C321.026 0.322885 320.549 0.291915 320.071 0.223519L320 0.718476ZM321.51 0.816521L321.516 1.31648C322.027 1.30971 322.538 1.2915 323.049 1.26185L323.02 0.762687L322.991 0.263527C322.495 0.292318 321.999 0.309996 321.503 0.316565L321.51 0.816521ZM323.02 0.762687L323.049 1.26185C323.536 1.23358 324.022 1.2294 324.509 1.2493L324.53 0.749714L324.55 0.250131C324.03 0.228884 323.51 0.233351 322.991 0.263527L323.02 0.762687ZM324.53 0.749714L324.509 1.2493C325.033 1.27069 325.556 1.26042 326.08 1.21849L326.04 0.720088L326 0.221685C325.517 0.260405 325.033 0.26988 324.55 0.250131L324.53 0.749714ZM326.04 0.720088L326.08 1.21849C326.558 1.18017 327.036 1.17797 327.514 1.21189L327.55 0.713141L327.585 0.214393C327.057 0.176921 326.528 0.179355 326 0.221685L326.04 0.720088ZM327.55 0.713141L327.514 1.21189C328.025 1.24813 328.536 1.2725 329.047 1.28501L329.06 0.785159L329.072 0.285309C328.576 0.273178 328.081 0.249541 327.585 0.214393L327.55 0.713141ZM329.06 0.785159L329.047 1.28501C329.557 1.29749 330.067 1.29982 330.577 1.29198L330.57 0.792043L330.562 0.292102C330.065 0.29973 329.569 0.297466 329.072 0.285309L329.06 0.785159ZM330.57 0.792043L330.577 1.29198C331.096 1.28401 331.615 1.25159 332.134 1.19474L332.08 0.697718L332.025 0.200693C331.537 0.254153 331.05 0.284613 330.562 0.292102L330.57 0.792043ZM332.08 0.697718L332.134 1.19474C332.603 1.14339 333.071 1.14122 333.54 1.18821L333.59 0.690706L333.64 0.193202C333.102 0.139239 332.563 0.141744 332.025 0.200693L332.08 0.697718ZM333.59 0.690706L333.54 1.18821C334.088 1.24321 334.637 1.2239 335.184 1.13066L335.1 0.637763L335.016 0.144868C334.556 0.223178 334.098 0.239162 333.64 0.193202L333.59 0.690706ZM335.1 0.637763L335.184 1.13066C335.655 1.05036 336.125 1.01726 336.595 1.03106L336.61 0.531272L336.624 0.031487C336.088 0.0157232 335.551 0.0536197 335.016 0.144868L335.1 0.637763ZM336.61 0.531272L336.595 1.03106C337.11 1.04618 337.625 1.0433 338.14 1.02241L338.12 0.522825L338.099 0.0232357C337.608 0.043175 337.116 0.0459251 336.624 0.031487L336.61 0.531272ZM338.12 0.522825L338.14 1.02241C338.597 1.00386 339.056 1.04742 339.517 1.15413L339.63 0.667018L339.743 0.179904C339.197 0.0535325 338.649 0.000965476 338.099 0.0232357L338.12 0.522825ZM339.63 0.667018L339.517 1.15413C340.065 1.28097 340.615 1.3303 341.166 1.30103L341.14 0.801729L341.113 0.302433C340.658 0.326619 340.201 0.286144 339.743 0.179904L339.63 0.667018ZM341.14 0.801729L341.166 1.30103C341.679 1.27378 342.192 1.23155 342.705 1.17435L342.65 0.677426L342.594 0.180507C342.101 0.235577 341.607 0.276216 341.113 0.302433L341.14 0.801729ZM342.65 0.677426L342.705 1.17435C343.192 1.12002 343.679 1.08978 344.166 1.08359L344.16 0.583631L344.153 0.0836717C343.634 0.090275 343.114 0.122565 342.594 0.180507L342.65 0.677426ZM344.16 0.583631L344.166 1.08359C344.653 1.07741 345.14 1.09522 345.627 1.13706L345.67 0.638893L345.713 0.140727C345.193 0.0960938 344.673 0.0770688 344.153 0.0836717L344.16 0.583631ZM345.67 0.638893L345.627 1.13706C346.139 1.18102 346.651 1.21181 347.163 1.22943L347.18 0.729727L347.197 0.230023C346.702 0.212996 346.207 0.183232 345.713 0.140727L345.67 0.638893ZM347.18 0.729727L347.163 1.22943C347.676 1.24709 348.189 1.2495 348.702 1.23665L348.69 0.736807L348.677 0.236963C348.184 0.249317 347.69 0.247004 347.197 0.230023L347.18 0.729727ZM348.69 0.736807L348.702 1.23665C349.218 1.22375 349.733 1.19232 350.248 1.14237L350.2 0.644705L350.151 0.147038C349.66 0.194691 349.169 0.224661 348.677 0.236963L348.69 0.736807ZM350.2 0.644705L350.248 1.14237C350.71 1.09758 351.172 1.11019 351.635 1.18043L351.71 0.686093L351.785 0.191754C351.241 0.109213 350.696 0.09423 350.151 0.147038L350.2 0.644705ZM351.71 0.686093L351.635 1.18043C352.193 1.26522 352.753 1.256 353.31 1.15311L353.22 0.66141L353.129 0.169706C352.68 0.252516 352.233 0.259752 351.785 0.191754L351.71 0.686093ZM353.22 0.66141L353.31 1.15311C353.782 1.06615 354.252 1.02644 354.722 1.03361L354.73 0.533668L354.737 0.033726C354.201 0.025529 353.664 0.0709779 353.129 0.169706L353.22 0.66141ZM354.73 0.533668L354.722 1.03361C355.22 1.04121 355.718 1.05678 356.216 1.0803L356.24 0.580862L356.263 0.0814189C355.755 0.0573904 355.246 0.0414926 354.737 0.033726L354.73 0.533668ZM356.24 0.580862L356.216 1.0803C356.736 1.10487 357.256 1.10352 357.776 1.07625L357.75 0.57694L357.724 0.077626C357.237 0.103149 356.75 0.104413 356.263 0.0814189L356.24 0.580862ZM357.75 0.57694L357.776 1.07625C358.266 1.05056 358.756 1.0446 359.246 1.05836L359.26 0.558558L359.274 0.0587553C358.757 0.044241 358.24 0.0505325 357.724 0.077626L357.75 0.57694ZM359.26 0.558558L359.246 1.05836C359.746 1.0724 360.245 1.09161 360.745 1.116L360.77 0.616591L360.794 0.117185C360.287 0.0924661 359.781 0.0729895 359.274 0.0587553L359.26 0.558558ZM360.77 0.616591L360.745 1.116C361.272 1.14168 361.798 1.1308 362.325 1.0834L362.28 0.58542L362.235 0.0874358C361.754 0.130701 361.274 0.140607 360.794 0.117185L360.77 0.616591ZM362.28 0.58542L362.325 1.0834C362.797 1.0409 363.268 1.04301 363.74 1.08975L363.79 0.592187L363.839 0.0946211C363.304 0.0416816 362.77 0.039281 362.235 0.0874358L362.28 0.58542ZM363.79 0.592187L363.74 1.08975C364.249 1.14012 364.758 1.1823 365.266 1.21628L365.3 0.717388L365.333 0.2185C364.835 0.185233 364.337 0.14394 363.839 0.0946211L363.79 0.592187ZM365.3 0.717388L365.266 1.21628C365.686 1.2443 365.927 1.36779 366.069 1.52249L366.437 1.18436L366.805 0.846234C366.438 0.445546 365.92 0.25772 365.333 0.2185L365.3 0.717388ZM366.437 1.18436L366.069 1.52249C366.204 1.67003 366.3 1.91058 366.275 2.32599L366.774 2.3559L367.273 2.3858C367.309 1.79454 367.18 1.25408 366.805 0.846234L366.437 1.18436ZM366.774 2.3559L366.275 2.32599C366.244 2.84475 366.237 3.36356 366.254 3.88237L366.754 3.8659L367.254 3.84942C367.238 3.36157 367.244 2.87371 367.273 2.3858L366.774 2.3559ZM366.754 3.8659L366.254 3.88237C366.27 4.35701 366.245 4.83187 366.179 5.30727L366.674 5.3759L367.169 5.44453C367.243 4.91326 367.271 4.38145 367.254 3.84942L366.754 3.8659ZM366.674 5.3759L366.179 5.30727C366.107 5.82534 366.059 6.34363 366.034 6.86208L366.534 6.8859L367.033 6.90971C367.056 6.4215 367.102 5.93312 367.169 5.44453L366.674 5.3759ZM366.534 6.8859L366.034 6.86208C366.009 7.39742 366.034 7.93255 366.111 8.46703L366.606 8.3959L367.101 8.32476C367.033 7.85258 367.01 7.38103 367.033 6.90971L366.534 6.8859ZM366.606 8.3959L366.111 8.46703C366.18 8.94517 366.211 9.4228 366.206 9.90016L366.706 9.9059L367.206 9.91163C367.212 9.38232 367.177 8.85329 367.101 8.32476L366.606 8.3959ZM366.706 9.9059L366.206 9.90016C366.2 10.3785 366.159 10.8572 366.082 11.3364L366.576 11.4159L367.069 11.4953C367.154 10.9679 367.2 10.44 367.206 9.91163L366.706 9.9059ZM366.576 11.4159L366.082 11.3364C365.993 11.8927 365.993 12.45 366.084 13.0062L366.577 12.9259L367.071 12.8456C366.997 12.3951 366.997 11.9458 367.069 11.4953L366.576 11.4159ZM366.577 12.9259L366.084 13.0062C366.161 13.4837 366.201 13.9605 366.202 14.437L366.702 14.4359L367.202 14.4348C367.2 13.9046 367.157 13.3748 367.071 12.8456L366.577 12.9259ZM366.702 14.4359L366.202 14.437C366.203 14.9311 366.19 15.4252 366.164 15.9194L366.663 15.9459L367.162 15.9724C367.19 15.4599 367.203 14.9474 367.202 14.4348L366.702 14.4359ZM366.663 15.9459L366.164 15.9194C366.137 16.4217 366.109 16.924 366.079 17.4263L366.578 17.4559L367.077 17.4855C367.107 16.9811 367.136 16.4768 367.162 15.9724L366.663 15.9459ZM366.578 17.4559L366.079 17.4263C366.047 17.9719 366.083 18.517 366.188 19.0605L366.679 18.9659L367.17 18.8712C367.081 18.4081 367.05 17.9465 367.077 17.4855L366.578 17.4559ZM366.679 18.9659L366.188 19.0605C366.277 19.5233 366.307 19.9844 366.279 20.4451L366.778 20.4759L367.277 20.5067C367.311 19.9607 367.275 19.4152 367.17 18.8712L366.679 18.9659ZM366.778 20.4759L366.279 20.4451C366.248 20.9377 366.202 21.4303 366.14 21.9231L366.636 21.9859L367.132 22.0487C367.197 21.5348 367.245 21.0208 367.277 20.5067L366.778 20.4759ZM366.636 21.9859L366.14 21.9231C366.071 22.4665 366.068 23.0103 366.132 23.5538L366.628 23.4959L367.125 23.438C367.071 22.9748 367.073 22.512 367.132 22.0487L366.636 21.9859ZM366.628 23.4959L366.132 23.5538C366.186 24.0213 366.19 24.4884 366.143 24.9557L366.64 25.0059L367.138 25.0561C367.192 24.5167 367.188 23.9771 367.125 23.438L366.628 23.4959ZM366.64 25.0059L366.143 24.9557C366.091 25.4686 366.054 25.9816 366.032 26.4947L366.532 26.5159L367.031 26.5371C367.052 26.0435 367.088 25.5498 367.138 25.0561L366.64 25.0059ZM366.532 26.5159L366.032 26.4947C366.009 27.0367 366.049 27.5782 366.151 28.1184L366.642 28.0259L367.133 27.9334C367.045 27.4669 367.012 27.0017 367.031 26.5371L366.532 26.5159ZM366.642 28.0259L366.151 28.1184C366.236 28.572 366.251 29.0239 366.196 29.4759L366.693 29.5359L367.189 29.5959C367.256 29.0413 367.237 28.4865 367.133 27.9334L366.642 28.0259ZM366.693 29.5359L366.196 29.4759C366.133 29.9973 366.099 30.519 366.094 31.0408L366.594 31.0459L367.094 31.051C367.099 30.5662 367.13 30.0812 367.189 29.5959L366.693 29.5359ZM366.594 31.0459L366.094 31.0408C366.089 31.5358 366.071 32.0309 366.042 32.526L366.541 32.5559L367.04 32.5858C367.071 32.0742 367.089 31.5626 367.094 31.051L366.594 31.0459ZM366.541 32.5559L366.042 32.526C366.011 33.0451 366.004 33.5644 366.023 34.0836L366.522 34.0659L367.022 34.0482C367.005 33.5607 367.011 33.0733 367.04 32.5858L366.541 32.5559ZM366.522 34.0659L366.023 34.0836C366.04 34.5824 366.051 35.0812 366.055 35.58L366.555 35.5759L367.055 35.5718C367.051 35.0639 367.04 34.556 367.022 34.0482L366.522 34.0659ZM366.555 35.5759L366.055 35.58C366.059 36.0715 366.046 36.563 366.015 37.0545L366.514 37.0859L367.013 37.1173C367.046 36.6022 367.06 36.087 367.055 35.5718L366.555 35.5759ZM366.514 37.0859L366.015 37.0545C365.982 37.5868 365.994 38.1191 366.053 38.651L366.55 38.5959L367.047 38.5408C366.995 38.066 366.983 37.5917 367.013 37.1173L366.514 37.0859ZM366.55 38.5959L366.053 38.651C366.107 39.1325 366.128 39.6137 366.118 40.0948L366.617 40.1059L367.117 40.117C367.129 39.5914 367.106 39.0659 367.047 38.5408L366.55 38.5959ZM366.617 40.1059L366.118 40.0948C366.106 40.6146 366.12 41.1343 366.16 41.6539L366.658 41.6159L367.157 41.5779C367.12 41.0909 367.107 40.6039 367.117 40.117L366.617 40.1059ZM366.658 41.6159L366.16 41.6539C366.195 42.1154 366.172 42.5772 366.092 43.0403L366.584 43.1259L367.077 43.2115C367.172 42.6679 367.198 42.1231 367.157 41.5779L366.658 41.6159ZM366.584 43.1259L366.092 43.0403C365.993 43.6084 366.007 44.1776 366.132 44.744L366.62 44.6359L367.109 44.5278C367.011 44.0876 367.001 43.65 367.077 43.2115L366.584 43.1259ZM366.62 44.6359L366.132 44.744C366.236 45.2145 366.291 45.6836 366.296 46.1517L366.796 46.1459L367.296 46.1401C367.29 45.6015 367.227 45.0639 367.109 44.5278L366.62 44.6359ZM366.796 46.1459L366.296 46.1517C366.302 46.6294 366.271 47.1074 366.203 47.5859L366.698 47.6559L367.193 47.7259C367.268 47.1977 367.302 46.669 367.296 46.1401L366.796 46.1459ZM366.698 47.6559L366.203 47.5859C366.127 48.1251 366.11 48.6651 366.152 49.205L366.651 49.1659L367.149 49.1267C367.113 48.6601 367.127 48.1933 367.193 47.7259L366.698 47.6559ZM366.651 49.1659L366.152 49.205C366.19 49.6805 366.187 50.1558 366.145 50.6313L366.643 50.6759L367.141 50.7205C367.188 50.1894 367.191 49.658 367.149 49.1267L366.651 49.1659ZM366.643 50.6759L366.145 50.6313C366.098 51.1514 366.078 51.6717 366.084 52.1921L366.584 52.1859L367.084 52.1797C367.078 51.6934 367.097 51.207 367.141 50.7205L366.643 50.6759ZM366.584 52.1859L366.084 52.1921C366.09 52.6975 366.1 53.2029 366.112 53.7083L366.612 53.6959L367.112 53.6834C367.1 53.1822 367.09 52.681 367.084 52.1797L366.584 52.1859ZM366.612 53.6959L366.112 53.7083C366.125 54.2281 366.164 54.7477 366.228 55.2672L366.724 55.2059L367.221 55.1446C367.16 54.6574 367.124 54.1703 367.112 53.6834L366.612 53.6959ZM366.724 55.2059L366.228 55.2672C366.286 55.7337 366.291 56.1997 366.244 56.666L366.742 56.7159L367.239 56.7658C367.294 56.2254 367.287 55.6848 367.221 55.1446L366.724 55.2059ZM366.742 56.7159L366.244 56.666C366.191 57.199 366.185 57.7322 366.227 58.2654L366.726 58.2259L367.224 58.1864C367.187 57.7129 367.192 57.2395 367.239 56.7658L366.742 56.7159ZM366.726 58.2259L366.227 58.2654C366.258 58.6471 366.152 58.8259 366.004 58.9338L366.3 59.337L366.596 59.7401C367.096 59.3733 367.274 58.8114 367.224 58.1864L366.726 58.2259ZM366.3 59.337L366.004 58.9338C365.77 59.1056 365.449 59.2256 365.012 59.2671L365.06 59.7648L365.107 60.2626C365.677 60.2084 366.183 60.0431 366.596 59.7401L366.3 59.337ZM365.06 59.7648L365.012 59.2671C364.547 59.3114 364.081 59.3029 363.615 59.2414L363.55 59.7371L363.484 60.2328C364.025 60.3041 364.566 60.3141 365.107 60.2626L365.06 59.7648ZM363.55 59.7371L363.615 59.2414C363.097 59.1731 362.58 59.1277 362.062 59.1051L362.04 59.6046L362.018 60.1041C362.507 60.1255 362.995 60.1683 363.484 60.2328L363.55 59.7371ZM362.04 59.6046L362.062 59.1051C361.55 59.0827 361.039 59.0728 360.527 59.0752L360.53 59.5752L360.532 60.0752C361.027 60.0729 361.523 60.0825 362.018 60.1041L362.04 59.6046ZM360.53 59.5752L360.527 59.0752C360.032 59.0775 359.536 59.0684 359.04 59.0479L359.02 59.5475L358.999 60.0471C359.51 60.0682 360.021 60.0776 360.532 60.0752L360.53 59.5752ZM359.02 59.5475L359.04 59.0479C358.514 59.0262 357.988 59.0405 357.462 59.091L357.51 59.5887L357.557 60.0864C358.038 60.0403 358.519 60.0272 358.999 60.0471L359.02 59.5475ZM357.51 59.5887L357.462 59.091C356.982 59.1369 356.503 59.1484 356.024 59.1254L356 59.6248L355.976 60.1242C356.503 60.1495 357.03 60.1369 357.557 60.0864L357.51 59.5887ZM356 59.6248L356.024 59.1254C355.521 59.1012 355.019 59.0755 354.517 59.0482L354.49 59.5475L354.463 60.0468C354.967 60.0742 355.471 60.1 355.976 60.1242L356 59.6248ZM354.49 59.5475L354.517 59.0482C353.98 59.0191 353.442 59.0445 352.906 59.1245L352.98 59.619L353.053 60.1135C353.524 60.0435 353.993 60.0213 354.463 60.0468L354.49 59.5475ZM352.98 59.619L352.906 59.1245C352.432 59.1951 351.958 59.223 351.485 59.2082L351.47 59.708L351.454 60.2078C351.988 60.2244 352.521 60.1929 353.053 60.1135L352.98 59.619ZM351.47 59.708L351.485 59.2082C350.965 59.192 350.444 59.2029 349.923 59.2409L349.96 59.7395L349.996 60.2382C350.482 60.2028 350.968 60.1926 351.454 60.2078L351.47 59.708ZM349.96 59.7395L349.923 59.2409C349.443 59.2759 348.962 59.2782 348.481 59.2478L348.45 59.7468L348.418 60.2458C348.944 60.2791 349.47 60.2766 349.996 60.2382L349.96 59.7395ZM348.45 59.7468L348.481 59.2478C347.95 59.2141 347.418 59.2256 346.887 59.2823L346.94 59.7795L346.993 60.2767C347.468 60.2259 347.943 60.2157 348.418 60.2458L348.45 59.7468ZM346.94 59.7795L346.887 59.2823C346.414 59.3328 345.941 59.3399 345.468 59.3038L345.43 59.8023L345.392 60.3009C345.925 60.3417 346.459 60.3336 346.993 60.2767L346.94 59.7795ZM345.43 59.8023L345.468 59.3038C344.939 59.2634 344.41 59.2634 343.882 59.3039L343.92 59.8025L343.958 60.301C344.436 60.2644 344.914 60.2643 345.392 60.3009L345.43 59.8023ZM343.92 59.8025L343.882 59.3039C343.397 59.3411 342.912 59.3513 342.427 59.3346L342.41 59.8343L342.393 60.334C342.914 60.352 343.436 60.341 343.958 60.301L343.92 59.8025ZM342.41 59.8343L342.427 59.3346C341.951 59.3182 341.474 59.2629 340.997 59.1685L340.9 59.659L340.803 60.1496C341.332 60.2541 341.862 60.3157 342.393 60.334L342.41 59.8343ZM340.9 59.659L340.997 59.1685C340.448 59.0601 339.898 59.0292 339.347 59.0766L339.39 59.5748L339.433 60.0729C339.888 60.0337 340.345 60.059 340.803 60.1496L340.9 59.659ZM339.39 59.5748L339.347 59.0766C338.84 59.1202 338.333 59.1695 337.826 59.2244L337.88 59.7215L337.934 60.2186C338.433 60.1645 338.933 60.1159 339.433 60.0729L339.39 59.5748ZM337.88 59.7215L337.826 59.2244C337.368 59.274 336.91 59.2613 336.451 59.186L336.37 59.6794L336.289 60.1728C336.836 60.2626 337.385 60.278 337.934 60.2186L337.88 59.7215ZM336.37 59.6794L336.451 59.186C335.893 59.0945 335.334 59.0956 334.777 59.1893L334.86 59.6824L334.943 60.1755C335.392 60.1 335.84 60.0991 336.289 60.1728L336.37 59.6794Z" /> + android:pathData="M334.86 59.6824L334.777 59.1893C334.309 59.268 333.841 59.2957 333.374 59.2727L333.35 59.7721L333.325 60.2715C333.865 60.298 334.404 60.2659 334.943 60.1755L334.86 59.6824ZM333.35 59.7721L333.374 59.2727C332.86 59.2474 332.345 59.2396 331.831 59.2491L331.84 59.749L331.849 60.2489C332.341 60.2398 332.833 60.2473 333.325 60.2715L333.35 59.7721ZM331.84 59.749L331.831 59.2491C331.36 59.2578 330.89 59.2198 330.418 59.1349L330.33 59.627L330.241 60.1191C330.776 60.2155 331.312 60.2588 331.849 60.2489L331.84 59.749ZM330.33 59.627L330.418 59.1349C329.88 59.0381 329.341 58.9994 328.801 59.0192L328.82 59.5189L328.838 60.0185C329.305 60.0014 329.773 60.0348 330.241 60.1191L330.33 59.627ZM328.82 59.5189L328.801 59.0192C328.273 59.0387 327.744 59.0985 327.217 59.1985L327.31 59.6898L327.403 60.181C327.882 60.0902 328.36 60.0361 328.838 60.0185L328.82 59.5189ZM327.31 59.6898L327.217 59.1985C326.762 59.2847 326.309 59.3015 325.857 59.2497L325.8 59.7464L325.743 60.2432C326.297 60.3066 326.851 60.2857 327.403 60.181L327.31 59.6898ZM325.8 59.7464L325.857 59.2497C325.336 59.19 324.815 59.158 324.294 59.1535L324.29 59.6535L324.285 60.1534C324.771 60.1576 325.257 60.1875 325.743 60.2432L325.8 59.7464ZM324.29 59.6535L324.294 59.1535C323.796 59.1492 323.297 59.1374 322.799 59.1182L322.78 59.6178L322.76 60.1174C323.269 60.137 323.777 60.1491 324.285 60.1534L324.29 59.6535ZM322.78 59.6178L322.799 59.1182C322.262 59.0975 321.726 59.1302 321.19 59.2162L321.27 59.7099L321.349 60.2035C321.82 60.1279 322.29 60.0993 322.76 60.1174L322.78 59.6178ZM321.27 59.7099L321.19 59.2162C320.739 59.2886 320.289 59.2892 319.838 59.218L319.76 59.7119L319.682 60.2058C320.237 60.2934 320.794 60.2927 321.349 60.2035L321.27 59.7099ZM319.76 59.7119L319.838 59.218C319.286 59.1309 318.733 59.1259 318.18 59.2032L318.25 59.6983L318.319 60.1935C318.773 60.13 319.227 60.134 319.682 60.2058L319.76 59.7119ZM318.25 59.6983L318.18 59.2032C317.727 59.2666 317.273 59.262 316.819 59.1892L316.74 59.6829L316.661 60.1766C317.213 60.2651 317.766 60.2708 318.319 60.1935L318.25 59.6983ZM316.74 59.6829L316.819 59.1892C316.283 59.1033 315.746 59.0711 315.21 59.0928L315.23 59.5924L315.25 60.0919C315.72 60.073 316.19 60.1011 316.661 60.1766L316.74 59.6829ZM315.23 59.5924L315.21 59.0928C314.699 59.1134 314.188 59.1451 313.678 59.188L313.72 59.6862L313.762 60.1845C314.258 60.1428 314.754 60.112 315.25 60.0919L315.23 59.5924ZM313.72 59.6862L313.678 59.188C313.186 59.2293 312.695 59.253 312.203 59.2592L312.21 59.7592L312.216 60.2592C312.731 60.2526 313.246 60.2277 313.762 60.1845L313.72 59.6862ZM312.21 59.7592L312.203 59.2592C311.728 59.2653 311.253 59.231 310.777 59.1563L310.7 59.6503L310.622 60.1442C311.153 60.2275 311.684 60.2659 312.216 60.2592L312.21 59.7592ZM310.7 59.6503L310.777 59.1563C310.251 59.0738 309.725 59.0278 309.199 59.0185L309.19 59.5184L309.181 60.0184C309.661 60.0268 310.141 60.0687 310.622 60.1442L310.7 59.6503ZM309.19 59.5184L309.199 59.0185C308.662 59.0091 308.126 59.0531 307.59 59.1502L307.68 59.6422L307.769 60.1342C308.241 60.0486 308.711 60.0101 309.181 60.0184L309.19 59.5184ZM307.68 59.6422L307.59 59.1502C307.118 59.236 306.646 59.2762 306.175 59.2711L306.17 59.7711L306.164 60.2711C306.7 60.2768 307.235 60.2311 307.769 60.1342L307.68 59.6422ZM306.17 59.7711L306.175 59.2711C305.66 59.2656 305.144 59.279 304.628 59.3113L304.66 59.8103L304.691 60.3093C305.182 60.2786 305.673 60.2658 306.164 60.2711L306.17 59.7711ZM304.66 59.8103L304.628 59.3113C304.162 59.3405 303.695 59.3185 303.227 59.2449L303.15 59.7388L303.072 60.2328C303.611 60.3176 304.151 60.3432 304.691 60.3093L304.66 59.8103ZM303.15 59.7388L303.227 59.2449C302.678 59.1585 302.128 59.1495 301.578 59.2181L301.64 59.7143L301.702 60.2104C302.158 60.1534 302.615 60.1608 303.072 60.2328L303.15 59.7388ZM301.64 59.7143L301.578 59.2181C301.108 59.2768 300.639 59.2873 300.17 59.2497L300.13 59.7481L300.09 60.2465C300.627 60.2895 301.165 60.2775 301.702 60.2104L301.64 59.7143ZM300.13 59.7481L300.17 59.2497C299.645 59.2077 299.121 59.1987 298.597 59.2227L298.62 59.7222L298.643 60.2216C299.125 60.1996 299.607 60.2079 300.09 60.2465L300.13 59.7481ZM298.62 59.7222L298.597 59.2227C298.094 59.2457 297.591 59.2684 297.087 59.291L297.11 59.7905L297.132 60.29C297.636 60.2674 298.139 60.2446 298.643 60.2216L298.62 59.7222ZM297.11 59.7905L297.087 59.291C296.592 59.3132 296.096 59.3238 295.601 59.3228L295.6 59.8228L295.599 60.3228C296.11 60.3239 296.621 60.3129 297.132 60.29L297.11 59.7905ZM295.6 59.8228L295.601 59.3228C295.118 59.3218 294.634 59.2915 294.151 59.2319L294.09 59.7281L294.029 60.2244C294.552 60.2889 295.075 60.3217 295.599 60.3228L295.6 59.8228ZM294.09 59.7281L294.151 59.2319C293.62 59.1664 293.088 59.1461 292.556 59.1711L292.58 59.6705L292.603 60.17C293.078 60.1477 293.553 60.1657 294.029 60.2244L294.09 59.7281ZM292.58 59.6705L292.556 59.1711C292.056 59.1946 291.555 59.2143 291.054 59.2299L291.07 59.7297L291.085 60.2294C291.591 60.2136 292.097 60.1938 292.603 60.17L292.58 59.6705ZM291.07 59.7297L291.054 59.2299C290.552 59.2456 290.051 59.2588 289.549 59.2695L289.56 59.7694L289.57 60.2693C290.075 60.2585 290.58 60.2453 291.085 60.2294L291.07 59.7297ZM289.56 59.7694L289.549 59.2695C289.044 59.2803 288.538 59.2944 288.032 59.3119L288.05 59.8116L288.067 60.3113C288.568 60.294 289.069 60.28 289.57 60.2693L289.56 59.7694ZM288.05 59.8116L288.032 59.3119C287.536 59.329 287.04 59.3356 286.544 59.3317L286.54 59.8317L286.536 60.3317C287.046 60.3357 287.557 60.3289 288.067 60.3113L288.05 59.8116ZM286.54 59.8317L286.544 59.3317C286.037 59.3278 285.53 59.3292 285.023 59.336L285.03 59.8359L285.036 60.3359C285.536 60.3292 286.036 60.3278 286.536 60.3317L286.54 59.8317ZM285.03 59.8359L285.023 59.336C284.542 59.3424 284.06 59.317 283.579 59.2598L283.52 59.7563L283.461 60.2528C283.986 60.3152 284.511 60.3429 285.036 60.3359L285.03 59.8359ZM283.52 59.7563L283.579 59.2598C283.06 59.1982 282.542 59.1603 282.023 59.1463L282.01 59.6461L281.996 60.1459C282.484 60.1591 282.972 60.1948 283.461 60.2528L283.52 59.7563ZM282.01 59.6461L282.023 59.1463C281.494 59.1319 280.965 59.1583 280.437 59.2253L280.5 59.7213L280.563 60.2173C281.041 60.1567 281.519 60.1329 281.996 60.1459L282.01 59.6461ZM280.5 59.7213L280.437 59.2253C279.984 59.2827 279.531 59.2715 279.077 59.1913L278.99 59.6837L278.903 60.1761C279.455 60.2736 280.009 60.2875 280.563 60.2173L280.5 59.7213ZM278.99 59.6837L279.077 59.1913C278.532 59.0952 277.986 59.0685 277.44 59.1117L277.48 59.6101L277.519 60.1086C277.98 60.0722 278.441 60.0945 278.903 60.1761L278.99 59.6837ZM277.48 59.6101L277.44 59.1117C276.956 59.1499 276.473 59.1597 275.989 59.1412L275.97 59.6408L275.951 60.1404C276.473 60.1605 276.996 60.1499 277.519 60.1086L277.48 59.6101ZM275.97 59.6408L275.989 59.1412C275.49 59.122 274.992 59.0957 274.493 59.0621L274.46 59.561L274.426 60.0598C274.934 60.0941 275.442 60.1209 275.951 60.1404L275.97 59.6408ZM274.46 59.561L274.493 59.0621C273.966 59.0266 273.438 59.0295 272.911 59.0708L272.95 59.5693L272.989 60.0678C273.468 60.0302 273.947 60.0276 274.426 60.0598L274.46 59.561ZM272.95 59.5693L272.911 59.0708C272.431 59.1084 271.951 59.1123 271.471 59.0823L271.44 59.5814L271.409 60.0804C271.935 60.1133 272.462 60.109 272.989 60.0678L272.95 59.5693ZM271.44 59.5814L271.471 59.0823C270.926 59.0484 270.382 59.0817 269.839 59.1819L269.93 59.6736L270.021 60.1653C270.484 60.0797 270.947 60.0516 271.409 60.0804L271.44 59.5814ZM269.93 59.6736L269.839 59.1819C269.383 59.2661 268.928 59.2832 268.474 59.2336L268.42 59.7307L268.366 60.2277C268.918 60.2879 269.47 60.2669 270.021 60.1653L269.93 59.6736ZM268.42 59.7307L268.474 59.2336C267.946 59.1761 267.418 59.1577 266.89 59.1783L266.91 59.6779L266.929 60.1775C267.408 60.1588 267.887 60.1755 268.366 60.2277L268.42 59.7307ZM266.91 59.6779L266.89 59.1783C266.412 59.197 265.934 59.1793 265.456 59.1252L265.4 59.622L265.344 60.1188C265.872 60.1786 266.401 60.1982 266.929 60.1775L266.91 59.6779ZM265.4 59.622L265.456 59.1252C264.905 59.0628 264.353 59.08 263.803 59.1764L263.89 59.6689L263.976 60.1613C264.433 60.0813 264.888 60.0673 265.344 60.1188L265.4 59.622ZM263.89 59.6689L263.803 59.1764C263.339 59.2578 262.876 59.2828 262.413 59.2518L262.38 59.7507L262.346 60.2495C262.89 60.2859 263.434 60.2564 263.976 60.1613L263.89 59.6689ZM262.38 59.7507L262.413 59.2518C261.913 59.2183 261.413 59.1798 260.913 59.1363L260.87 59.6344L260.826 60.1325C261.333 60.1766 261.84 60.2156 262.346 60.2495L262.38 59.7507ZM260.87 59.6344L260.913 59.1363C260.383 59.0902 259.854 59.0861 259.324 59.1241L259.36 59.6229L259.396 60.1216C259.872 60.0873 260.349 60.091 260.826 60.1325L260.87 59.6344ZM259.36 59.6229L259.324 59.1241C258.836 59.1592 258.349 59.1711 257.861 59.16L257.85 59.6598L257.838 60.1597C258.357 60.1716 258.877 60.1588 259.396 60.1216L259.36 59.6229ZM257.85 59.6598L257.861 59.16C257.352 59.1484 256.843 59.1452 256.335 59.1505L256.34 59.6505L256.345 60.1504C256.843 60.1453 257.341 60.1483 257.838 60.1597L257.85 59.6598ZM256.34 59.6505L256.335 59.1505C255.831 59.1557 255.328 59.1609 254.825 59.166L254.83 59.666L254.835 60.1659C255.338 60.1609 255.842 60.1557 256.345 60.1504L256.34 59.6505ZM254.83 59.666L254.825 59.166C254.308 59.1712 253.792 59.1963 253.276 59.2413L253.32 59.7394L253.363 60.2375C253.854 60.1948 254.344 60.1709 254.835 60.1659L254.83 59.666ZM253.32 59.7394L253.276 59.2413C252.783 59.2843 252.29 59.312 251.797 59.3244L251.81 59.8242L251.822 60.3241C252.336 60.3112 252.85 60.2823 253.363 60.2375L253.32 59.7394ZM251.81 59.8242L251.797 59.3244C251.318 59.3365 250.838 59.3142 250.358 59.2575L250.3 59.754L250.241 60.2505C250.768 60.3128 251.295 60.3373 251.822 60.3241L251.81 59.8242ZM250.3 59.754L250.358 59.2575C249.826 59.1945 249.293 59.1788 248.76 59.2103L248.79 59.7094L248.819 60.2085C249.293 60.1805 249.767 60.1945 250.241 60.2505L250.3 59.754ZM248.79 59.7094L248.76 59.2103C248.288 59.2382 247.816 59.2222 247.343 59.1623L247.28 59.6583L247.217 60.1544C247.751 60.222 248.285 60.2401 248.819 60.2085L248.79 59.7094ZM247.28 59.6583L247.343 59.1623C246.826 59.0968 246.309 59.0525 245.792 59.0293L245.77 59.5288L245.747 60.0283C246.237 60.0502 246.727 60.0922 247.217 60.1544L247.28 59.6583ZM245.77 59.5288L245.792 59.0293C245.265 59.0057 244.739 59.0192 244.212 59.0696L244.26 59.5673L244.307 60.0651C244.788 60.019 245.267 60.0068 245.747 60.0283L245.77 59.5288ZM244.26 59.5673L244.212 59.0696C243.739 59.1149 243.267 59.1167 242.794 59.0749L242.75 59.573L242.706 60.071C243.24 60.1182 243.774 60.1162 244.307 60.0651L244.26 59.5673ZM242.75 59.573L242.794 59.0749C242.263 59.028 241.731 59.0256 241.2 59.0678L241.24 59.5662L241.279 60.0646C241.755 60.0269 242.23 60.029 242.706 60.071L242.75 59.573ZM241.24 59.5662L241.2 59.0678C240.719 59.106 240.237 59.1125 239.756 59.0873L239.73 59.5867L239.704 60.086C240.229 60.1135 240.754 60.1064 241.279 60.0646L241.24 59.5662ZM239.73 59.5867L239.756 59.0873C239.248 59.0607 238.74 59.0412 238.232 59.0286L238.22 59.5285L238.207 60.0283C238.706 60.0406 239.205 60.0599 239.704 60.086L239.73 59.5867ZM238.22 59.5285L238.232 59.0286C237.707 59.0156 237.181 59.0375 236.656 59.0942L236.71 59.5913L236.763 60.0884C237.245 60.0364 237.726 60.0164 238.207 60.0283L238.22 59.5285ZM236.71 59.5913L236.656 59.0942C236.157 59.1481 235.657 59.1959 235.158 59.2375L235.2 59.7358L235.241 60.2341C235.749 60.1917 236.256 60.1432 236.763 60.0884L236.71 59.5913ZM235.2 59.7358L235.158 59.2375C234.69 59.2766 234.221 59.2668 233.752 59.2079L233.69 59.704L233.627 60.2001C234.165 60.2676 234.703 60.279 235.241 60.2341L235.2 59.7358ZM233.69 59.704L233.752 59.2079C233.214 59.1403 232.675 59.1301 232.136 59.1775L232.18 59.6756L232.224 60.1737C232.691 60.1325 233.159 60.1413 233.627 60.2001L233.69 59.704ZM232.18 59.6756L232.136 59.1775C231.669 59.2186 231.203 59.208 230.736 59.1457L230.67 59.6413L230.604 60.1369C231.143 60.2089 231.683 60.2212 232.224 60.1737L232.18 59.6756ZM230.67 59.6413L230.736 59.1457C230.187 59.0724 229.637 59.0755 229.088 59.155L229.16 59.6499L229.231 60.1447C229.689 60.0784 230.146 60.0758 230.604 60.1369L230.67 59.6413ZM229.16 59.6499L229.088 59.155C228.633 59.2209 228.179 59.2195 227.724 59.1509L227.65 59.6453L227.575 60.1397C228.127 60.2229 228.68 60.2246 229.231 60.1447L229.16 59.6499ZM227.65 59.6453L227.724 59.1509C227.174 59.0678 226.623 59.0636 226.073 59.1383L226.14 59.6338L226.207 60.1292C226.663 60.0673 227.119 60.0708 227.575 60.1397L227.65 59.6453ZM226.14 59.6338L226.073 59.1383C225.586 59.2043 225.099 59.2454 224.613 59.2616L224.63 59.7613L224.646 60.261C225.167 60.2437 225.687 60.1998 226.207 60.1292L226.14 59.6338ZM224.63 59.7613L224.613 59.2616C224.143 59.2772 223.672 59.2466 223.2 59.1695L223.12 59.663L223.039 60.1564C223.574 60.2439 224.11 60.2789 224.646 60.261L224.63 59.7613ZM223.12 59.663L223.2 59.1695C222.65 59.0796 222.098 59.0693 221.547 59.139L221.61 59.635L221.672 60.1311C222.128 60.0735 222.583 60.0819 223.039 60.1564L223.12 59.663ZM221.61 59.635L221.547 59.139C221.089 59.1969 220.631 59.1924 220.172 59.1253L220.1 59.62L220.027 60.1148C220.575 60.1949 221.124 60.2004 221.672 60.1311L221.61 59.635ZM220.1 59.62L220.172 59.1253C219.622 59.0448 219.07 59.0434 218.52 59.1211L218.59 59.6162L218.66 60.1113C219.116 60.0469 219.571 60.0481 220.027 60.1148L220.1 59.62ZM218.59 59.6162L218.52 59.1211C218.044 59.1883 217.568 59.2158 217.092 59.2037L217.08 59.7035L217.067 60.2033C217.598 60.2168 218.129 60.1861 218.66 60.1113L218.59 59.6162ZM217.08 59.7035L217.092 59.2037C216.581 59.1907 216.07 59.1895 215.559 59.2002L215.57 59.7001L215.58 60.2C216.076 60.1896 216.571 60.1907 217.067 60.2033L217.08 59.7035ZM215.57 59.7001L215.559 59.2002C215.068 59.2105 214.577 59.2029 214.086 59.1775L214.06 59.6769L214.034 60.1762C214.549 60.2029 215.065 60.2108 215.58 60.2L215.57 59.7001ZM214.06 59.6769L214.086 59.1775C213.551 59.1499 213.017 59.172 212.483 59.2436L212.55 59.7392L212.616 60.2347C213.089 60.1712 213.562 60.1518 214.034 60.1762L214.06 59.6769ZM212.55 59.7392L212.483 59.2436C212.031 59.3044 211.579 59.2954 211.126 59.2164L211.04 59.709L210.954 60.2015C211.507 60.298 212.062 60.3092 212.616 60.2347L212.55 59.7392ZM211.04 59.709L211.126 59.2164C210.564 59.1185 210.001 59.1207 209.44 59.2227L209.53 59.7146L209.619 60.2066C210.065 60.1256 210.509 60.1239 210.954 60.2015L211.04 59.709ZM209.53 59.7146L209.44 59.2227C209.003 59.3023 208.566 59.2942 208.127 59.1978L208.02 59.6861L207.912 60.1745C208.48 60.2991 209.05 60.31 209.619 60.2066L209.53 59.7146ZM208.02 59.6861L208.127 59.1978C207.588 59.0794 207.047 59.0222 206.505 59.0269L206.51 59.5269L206.514 60.0269C206.979 60.0229 207.445 60.0718 207.912 60.1745L208.02 59.6861ZM206.51 59.5269L206.505 59.0269C205.987 59.0314 205.469 59.0594 204.95 59.111L205 59.6085L205.049 60.106C205.538 60.0575 206.026 60.0311 206.514 60.0269L206.51 59.5269ZM205 59.6085L204.95 59.111C204.459 59.1598 203.967 59.1914 203.475 59.2056L203.49 59.7054L203.504 60.2052C204.019 60.1903 204.534 60.1572 205.049 60.106L205 59.6085ZM203.49 59.7054L203.475 59.2056C202.966 59.2204 202.457 59.2438 201.948 59.2758L201.98 59.7748L202.011 60.2739C202.509 60.2425 203.007 60.2197 203.504 60.2052L203.49 59.7054ZM201.98 59.7748L201.948 59.2758C201.461 59.3065 200.975 59.313 200.488 59.2953L200.47 59.7949L200.452 60.2946C200.971 60.3135 201.491 60.3066 202.011 60.2739L201.98 59.7748ZM200.47 59.7949L200.488 59.2953C199.984 59.2769 199.479 59.2602 198.975 59.245L198.96 59.7447L198.945 60.2445C199.447 60.2597 199.949 60.2764 200.452 60.2946L200.47 59.7949ZM198.96 59.7447L198.975 59.245C198.477 59.23 197.979 59.2069 197.481 59.1759L197.45 59.675L197.419 60.174C197.927 60.2057 198.436 60.2292 198.945 60.2445L198.96 59.7447ZM197.45 59.675L197.481 59.1759C196.944 59.1425 196.407 59.1633 195.871 59.2381L195.94 59.7333L196.009 60.2285C196.479 60.1629 196.949 60.1448 197.419 60.174L197.45 59.675ZM195.94 59.7333L195.871 59.2381C195.419 59.3011 194.969 59.2929 194.516 59.2133L194.43 59.7058L194.343 60.1982C194.898 60.2958 195.453 60.306 196.009 60.2285L195.94 59.7333ZM194.43 59.7058L194.516 59.2133C193.982 59.1192 193.446 59.0774 192.91 59.0884L192.92 59.5883L192.93 60.0882C193.4 60.0786 193.871 60.1151 194.343 60.1982L194.43 59.7058ZM192.92 59.5883L192.91 59.0884C192.406 59.0987 191.903 59.109 191.4 59.1192L191.41 59.6191L191.42 60.119C191.923 60.1088 192.427 60.0985 192.93 60.0882L192.92 59.5883ZM191.41 59.6191L191.4 59.1192C190.901 59.1293 190.402 59.1326 189.903 59.1293L189.9 59.6292L189.896 60.1292C190.404 60.1327 190.912 60.1292 191.42 60.119L191.41 59.6191ZM189.9 59.6292L189.903 59.1293C189.378 59.1257 188.853 59.1562 188.329 59.2208L188.39 59.717L188.451 60.2133C188.933 60.154 189.415 60.126 189.896 60.1292L189.9 59.6292ZM188.39 59.717L188.329 59.2208C187.844 59.2804 187.359 59.3125 186.875 59.3171L186.88 59.817L186.884 60.317C187.407 60.3121 187.929 60.2775 188.451 60.2133L188.39 59.717ZM186.88 59.817L186.875 59.3171C186.376 59.3218 185.878 59.3194 185.379 59.31L185.37 59.8099L185.36 60.3098C185.868 60.3194 186.376 60.3218 186.884 60.317L186.88 59.817ZM185.37 59.8099L185.379 59.31C184.888 59.3007 184.396 59.2741 183.904 59.2304L183.86 59.7284L183.815 60.2264C184.33 60.2722 184.845 60.3 185.36 60.3098L185.37 59.8099ZM183.86 59.7284L183.904 59.2304C183.373 59.1831 182.841 59.1808 182.31 59.2236L182.35 59.722L182.39 60.2203C182.865 60.1821 183.34 60.1841 183.815 60.2264L183.86 59.7284ZM182.35 59.722L182.31 59.2236C181.852 59.2604 181.395 59.2344 180.935 59.145L180.84 59.6358L180.744 60.1265C181.292 60.2331 181.84 60.2646 182.39 60.2203L182.35 59.722ZM180.84 59.6358L180.935 59.145C180.391 59.0391 179.846 59.0024 179.299 59.0355L179.33 59.5346L179.36 60.0337C179.821 60.0057 180.282 60.0365 180.744 60.1265L180.84 59.6358ZM179.33 59.5346L179.299 59.0355C178.79 59.0664 178.281 59.1066 177.771 59.1561L177.82 59.6537L177.868 60.1514C178.365 60.1031 178.863 60.0638 179.36 60.0337L179.33 59.5346ZM177.82 59.6537L177.771 59.1561C177.307 59.2012 176.842 59.1925 176.377 59.1298L176.31 59.6253L176.243 60.1208C176.784 60.1938 177.326 60.204 177.868 60.1514L177.82 59.6537ZM176.31 59.6253L176.377 59.1298C175.843 59.0578 175.308 59.0359 174.773 59.0641L174.8 59.5634L174.826 60.0627C175.298 60.0378 175.77 60.0571 176.243 60.1208L176.31 59.6253ZM174.8 59.5634L174.773 59.0641C174.269 59.0907 173.766 59.1182 173.262 59.1466L173.29 59.6458L173.318 60.145C173.821 60.1166 174.323 60.0892 174.826 60.0627L174.8 59.5634ZM173.29 59.6458L173.262 59.1466C172.776 59.174 172.29 59.176 171.804 59.1527L171.78 59.6521L171.756 60.1516C172.276 60.1765 172.797 60.1743 173.318 60.145L173.29 59.6458ZM171.78 59.6521L171.804 59.1527C171.287 59.128 170.77 59.1238 170.254 59.1403L170.27 59.64L170.286 60.1397C170.776 60.1241 171.266 60.1281 171.756 60.1516L171.78 59.6521ZM170.27 59.64L170.254 59.1403C169.754 59.1562 169.253 59.1675 168.753 59.1741L168.76 59.6741L168.766 60.174C169.273 60.1673 169.779 60.1559 170.286 60.1397L170.27 59.64ZM168.76 59.6741L168.753 59.1741C168.253 59.1807 167.752 59.1832 167.251 59.1815L167.25 59.6815L167.248 60.1815C167.754 60.1832 168.26 60.1807 168.766 60.174L168.76 59.6741ZM167.25 59.6815L167.251 59.1815C166.762 59.1798 166.272 59.1584 165.782 59.1172L165.74 59.6154L165.698 60.1136C166.214 60.1571 166.731 60.1797 167.248 60.1815L167.25 59.6815ZM165.74 59.6154L165.782 59.1172C165.265 59.0737 164.748 59.0513 164.231 59.0501L164.23 59.5501L164.229 60.0501C164.718 60.0513 165.208 60.0724 165.698 60.1136L165.74 59.6154ZM164.23 59.5501L164.231 59.0501C163.732 59.049 163.233 59.0415 162.733 59.0279L162.72 59.5277L162.706 60.0275C163.214 60.0414 163.721 60.0489 164.229 60.0501L164.23 59.5501ZM162.72 59.5277L162.733 59.0279C162.204 59.0134 161.674 59.0409 161.145 59.1103L161.21 59.6061L161.275 60.1018C161.752 60.0392 162.229 60.0145 162.706 60.0275L162.72 59.5277ZM161.21 59.6061L161.145 59.1103C160.678 59.1715 160.213 59.1799 159.747 59.1356L159.7 59.6334L159.652 60.1311C160.193 60.1826 160.734 60.1728 161.275 60.1018L161.21 59.6061ZM159.7 59.6334L159.747 59.1356C159.229 59.0863 158.711 59.0602 158.193 59.0572L158.19 59.5572L158.187 60.0572C158.675 60.06 159.164 60.0846 159.652 60.1311L159.7 59.6334ZM158.19 59.5572L158.193 59.0572C157.683 59.0543 157.174 59.0607 156.664 59.0764L156.68 59.5761L156.695 60.0759C157.192 60.0606 157.69 60.0544 158.187 60.0572L158.19 59.5572ZM156.68 59.5761L156.664 59.0764C156.169 59.0916 155.673 59.0951 155.178 59.0869L155.17 59.5868L155.161 60.0867C155.673 60.0952 156.184 60.0916 156.695 60.0759L156.68 59.5761ZM155.17 59.5868L155.178 59.0869C154.656 59.0782 154.135 59.0981 153.614 59.1465L153.66 59.6443L153.706 60.1422C154.191 60.0971 154.676 60.0787 155.161 60.0867L155.17 59.5868ZM153.66 59.6443L153.614 59.1465C153.146 59.1899 152.678 59.1835 152.209 59.1272L152.15 59.6237L152.09 60.1201C152.628 60.1848 153.167 60.1922 153.706 60.1422L153.66 59.6443ZM152.15 59.6237L152.209 59.1272C151.658 59.0609 151.106 59.0754 150.555 59.1703L150.64 59.663L150.725 60.1558C151.181 60.0772 151.635 60.0654 152.09 60.1201L152.15 59.6237ZM150.64 59.663L150.555 59.1703C150.082 59.2518 149.61 59.2885 149.138 59.2808L149.13 59.7807L149.122 60.2806C149.657 60.2894 150.191 60.2477 150.725 60.1558L150.64 59.663ZM149.13 59.7807L149.138 59.2808C148.649 59.2727 148.159 59.2441 147.67 59.195L147.62 59.6925L147.57 60.19C148.087 60.2419 148.604 60.2721 149.122 60.2806L149.13 59.7807ZM147.62 59.6925L147.67 59.195C147.133 59.1412 146.597 59.1407 146.061 59.1936L146.11 59.6911L146.159 60.1887C146.629 60.1423 147.099 60.1428 147.57 60.19L147.62 59.6925ZM146.11 59.6911L146.061 59.1936C145.576 59.2413 145.092 59.2613 144.608 59.2535L144.6 59.7534L144.592 60.2534C145.114 60.2618 145.637 60.2402 146.159 60.1887L146.11 59.6911ZM144.6 59.7534L144.608 59.2535C144.13 59.2458 143.651 59.2022 143.172 59.1226L143.09 59.6158L143.008 60.109C143.535 60.1967 144.063 60.2449 144.592 60.2534L144.6 59.7534ZM143.09 59.6158L143.172 59.1226C142.626 59.0319 142.08 59.0121 141.533 59.0634L141.58 59.5612L141.627 60.059C142.087 60.0158 142.547 60.0324 143.008 60.109L143.09 59.6158ZM141.58 59.5612L141.533 59.0634C141.031 59.1106 140.529 59.1559 140.027 59.1993L140.07 59.6974L140.113 60.1955C140.617 60.152 141.122 60.1065 141.627 60.059L141.58 59.5612ZM140.07 59.6974L140.027 59.1993C139.544 59.241 139.061 59.2528 138.578 59.2348L138.56 59.7345L138.541 60.2341C139.065 60.2537 139.589 60.2408 140.113 60.1955L140.07 59.6974ZM138.56 59.7345L138.578 59.2348C138.054 59.2153 137.531 59.2281 137.007 59.2732L137.05 59.7714L137.093 60.2695C137.576 60.2279 138.058 60.2161 138.541 60.2341L138.56 59.7345ZM137.05 59.7714L137.007 59.2732C136.525 59.3147 136.044 59.3241 135.563 59.3016L135.54 59.801L135.516 60.3005C136.042 60.3251 136.567 60.3148 137.093 60.2695L137.05 59.7714ZM135.54 59.801L135.563 59.3016C135.072 59.2786 134.581 59.2376 134.089 59.1787L134.03 59.6752L133.97 60.1716C134.485 60.2334 135.001 60.2764 135.516 60.3005L135.54 59.801ZM134.03 59.6752L134.089 59.1787C133.541 59.113 132.993 59.1214 132.445 59.2037L132.52 59.6981L132.594 60.1926C133.053 60.1235 133.512 60.1166 133.97 60.1716L134.03 59.6752ZM132.52 59.6981L132.445 59.2037C131.974 59.2746 131.503 59.2995 131.032 59.2786L131.01 59.7781L130.988 60.2777C131.524 60.3014 132.059 60.273 132.594 60.1926L132.52 59.6981ZM131.01 59.7781L131.032 59.2786C130.518 59.2559 130.005 59.2487 129.492 59.257L129.5 59.757L129.508 60.2569C130.001 60.2489 130.494 60.2558 130.988 60.2777L131.01 59.7781ZM129.5 59.757L129.492 59.257C129.013 59.2648 128.534 59.2373 128.055 59.1742L127.99 59.6699L127.925 60.1657C128.452 60.2351 128.98 60.2655 129.508 60.2569L129.5 59.757ZM127.99 59.6699L128.055 59.1742C127.516 59.1034 126.977 59.0902 126.438 59.135L126.48 59.6333L126.521 60.1316C126.989 60.0927 127.456 60.1041 127.925 60.1657L127.99 59.6699ZM126.48 59.6333L126.438 59.135C125.969 59.174 125.499 59.1655 125.029 59.1094L124.97 59.6059L124.91 60.1023C125.447 60.1664 125.984 60.1762 126.521 60.1316L126.48 59.6333ZM124.97 59.6059L125.029 59.1094C124.494 59.0455 123.958 59.0339 123.422 59.0747L123.46 59.5732L123.498 60.0718C123.968 60.0359 124.439 60.0461 124.91 60.1023L124.97 59.6059ZM123.46 59.5732L123.422 59.0747C122.919 59.1129 122.416 59.1503 121.914 59.1867L121.95 59.6854L121.986 60.1841C122.49 60.1476 122.994 60.1101 123.498 60.0718L123.46 59.5732ZM121.95 59.6854L121.914 59.1867C121.434 59.2215 120.955 59.2214 120.476 59.1866L120.44 59.6852L120.403 60.1839C120.931 60.2223 121.458 60.2224 121.986 60.1841L121.95 59.6854ZM120.44 59.6852L120.476 59.1866C119.936 59.1472 119.395 59.168 118.856 59.2487L118.93 59.7432L119.004 60.2377C119.471 60.1678 119.937 60.15 120.403 60.1839L120.44 59.6852ZM118.93 59.7432L118.856 59.2487C118.401 59.3166 117.948 59.3168 117.493 59.2493L117.42 59.7438L117.346 60.2384C117.898 60.3205 118.451 60.3203 119.004 60.2377L118.93 59.7432ZM117.42 59.7438L117.493 59.2493C116.955 59.1693 116.417 59.1464 115.878 59.1808L115.91 59.6797L115.942 60.1787C116.409 60.1489 116.877 60.1687 117.346 60.2384L117.42 59.7438ZM115.91 59.6797L115.878 59.1808C115.385 59.2122 114.892 59.2279 114.4 59.2279L114.4 59.7279L114.4 60.2279C114.914 60.2279 115.428 60.2115 115.942 60.1787L115.91 59.6797ZM114.4 59.7279L114.4 59.2279C113.903 59.2278 113.406 59.2184 112.909 59.1995L112.89 59.6991L112.871 60.1987C113.38 60.2181 113.89 60.2278 114.4 60.2279L114.4 59.7279ZM112.89 59.6991L112.909 59.1995C112.416 59.1807 111.924 59.1457 111.432 59.0944L111.38 59.5917L111.328 60.089C111.842 60.1426 112.356 60.1792 112.871 60.1987L112.89 59.6991ZM111.38 59.5917L111.432 59.0944C110.907 59.0397 110.382 59.0192 109.857 59.0329L109.87 59.5328L109.883 60.0326C110.364 60.02 110.846 60.0387 111.328 60.089L111.38 59.5917ZM109.87 59.5328L109.857 59.0329C109.347 59.0463 108.838 59.0692 108.328 59.1017L108.36 59.6006L108.392 60.0996C108.889 60.068 109.386 60.0456 109.883 60.0326L109.87 59.5328ZM108.36 59.6006L108.328 59.1017C107.818 59.1342 107.307 59.1773 106.797 59.2311L106.85 59.7283L106.902 60.2256C107.399 60.1732 107.895 60.1312 108.392 60.0996L108.36 59.6006ZM106.85 59.7283L106.797 59.2311C106.339 59.2794 105.881 59.2657 105.421 59.1896L105.34 59.6828L105.258 60.1761C105.805 60.2668 106.354 60.2834 106.902 60.2256L106.85 59.7283ZM105.34 59.6828L105.421 59.1896C104.897 59.1026 104.371 59.0507 103.846 59.034L103.83 59.5338L103.814 60.0335C104.295 60.0488 104.776 60.0963 105.258 60.1761L105.34 59.6828ZM103.83 59.5338L103.846 59.034C103.309 59.017 102.772 59.0538 102.237 59.1442L102.32 59.6372L102.403 60.1302C102.874 60.0507 103.344 60.0186 103.814 60.0335L103.83 59.5338ZM102.32 59.6372L102.237 59.1442C101.783 59.2208 101.33 59.2282 100.877 59.1668L100.81 59.6622L100.743 60.1577C101.296 60.2327 101.85 60.2235 102.403 60.1302L102.32 59.6372ZM100.81 59.6622L100.877 59.1668C100.335 59.0932 99.792 59.0838 99.2494 59.1387L99.2998 59.6362L99.3501 60.1336C99.8141 60.0867 100.278 60.0947 100.743 60.1577L100.81 59.6622ZM99.2998 59.6362L99.2494 59.1387C98.7737 59.1868 98.2983 59.1951 97.8228 59.1636L97.7897 59.6625L97.7567 60.1615C98.2879 60.1966 98.8191 60.1873 99.3501 60.1336L99.2998 59.6362ZM97.7897 59.6625L97.8228 59.1636C97.3109 59.1297 96.799 59.109 96.2871 59.1015L96.2797 59.6014L96.2724 60.1014C96.7671 60.1087 97.2619 60.1287 97.7567 60.1615L97.7897 59.6625ZM96.2797 59.6014L96.2871 59.1015C95.791 59.0942 95.295 59.0761 94.7989 59.0471L94.7697 59.5463L94.7406 60.0454C95.2512 60.0752 95.7618 60.0939 96.2724 60.1014L96.2797 59.6014ZM94.7697 59.5463L94.7989 59.0471C94.2771 59.0167 93.7553 59.0151 93.2335 59.0425L93.2597 59.5418L93.2859 60.0411C93.7708 60.0157 94.2557 60.0171 94.7406 60.0454L94.7697 59.5463ZM93.2597 59.5418L93.2335 59.0425C92.7188 59.0695 92.2042 59.1142 91.6896 59.1765L91.7497 59.6729L91.8098 60.1692C92.302 60.1096 92.794 60.067 93.2859 60.0411L93.2597 59.5418ZM91.7497 59.6729L91.6896 59.1765C91.2356 59.2314 90.7818 59.219 90.3265 59.1388L90.2397 59.6312L90.153 60.1236C90.7044 60.2208 91.2572 60.2361 91.8098 60.1692L91.7497 59.6729ZM90.2397 59.6312L90.3265 59.1388C89.7653 59.0399 89.2027 59.0408 88.6416 59.1412L88.7297 59.6334L88.8179 60.1256C89.2634 60.0458 89.7075 60.0452 90.153 60.1236L90.2397 59.6312ZM88.7297 59.6334L88.6416 59.1412C88.1802 59.2239 87.7202 59.2461 87.2604 59.2086L87.2197 59.7069L87.179 60.2053C87.7259 60.2499 88.2726 60.2232 88.8179 60.1256L88.7297 59.6334ZM87.2197 59.7069L87.2604 59.2086C86.7373 59.1658 86.214 59.1545 85.6906 59.1745L85.7097 59.6741L85.7289 60.1738C86.2122 60.1553 86.6955 60.1658 87.179 60.2053L87.2197 59.7069ZM85.7097 59.6741L85.6906 59.1745C85.1905 59.1936 84.6904 59.2079 84.1902 59.2174L84.1997 59.7174L84.2093 60.2173C84.7158 60.2076 85.2223 60.1931 85.7289 60.1738L85.7097 59.6741ZM84.1997 59.7174L84.1902 59.2174C83.7026 59.2267 83.2149 59.2131 82.7272 59.1764L82.6897 59.675L82.6523 60.1736C83.1712 60.2126 83.6902 60.2271 84.2093 60.2173L84.1997 59.7174ZM82.6897 59.675L82.7272 59.1764C82.2068 59.1373 81.6863 59.1251 81.1658 59.1396L81.1797 59.6394L81.1937 60.1392C81.6799 60.1256 82.166 60.1371 82.6523 60.1736L82.6897 59.675ZM81.1797 59.6394L81.1658 59.1396C80.6779 59.1533 80.19 59.1443 79.702 59.1128L79.6697 59.6118L79.6375 60.1107C80.1562 60.1442 80.6749 60.1537 81.1937 60.1392L81.1797 59.6394ZM79.6697 59.6118L79.702 59.1128C79.1675 59.0783 78.6331 59.0937 78.0991 59.1589L78.1597 59.6552L78.2204 60.1515C78.6931 60.0938 79.1653 60.0802 79.6375 60.1107L79.6697 59.6118ZM78.1597 59.6552L78.0991 59.1589C77.6073 59.219 77.1155 59.2618 76.6239 59.2873L76.6498 59.7866L76.6756 60.286C77.1907 60.2593 77.7056 60.2144 78.2204 60.1515L78.1597 59.6552ZM76.6498 59.7866L76.6239 59.2873C76.146 59.3121 75.6681 59.3004 75.1899 59.2521L75.1397 59.7496L75.0895 60.247C75.6181 60.3004 76.1468 60.3134 76.6756 60.286L76.6498 59.7866ZM75.1397 59.7496L75.1899 59.2521C74.6842 59.2011 74.1784 59.1538 73.6726 59.1102L73.6297 59.6084L73.5868 60.1065C74.0877 60.1497 74.5886 60.1965 75.0895 60.247L75.1397 59.7496ZM73.6297 59.6084L73.6726 59.1102C73.1443 59.0647 72.6158 59.0591 72.0873 59.0935L72.1197 59.5924L72.1522 60.0914C72.6304 60.0603 73.1085 60.0653 73.5868 60.1065L73.6297 59.6084Z" /> + android:pathData="M72.1197 59.5924L72.0873 59.0935C71.6073 59.1247 71.1273 59.1222 70.6473 59.0861L70.6097 59.5847L70.5722 60.0832C71.0988 60.1229 71.6255 60.1256 72.1522 60.0914L72.1197 59.5924ZM70.6097 59.5847L70.6473 59.0861C70.1279 59.0469 69.6084 59.033 69.0888 59.0444L69.0997 59.5442L69.1106 60.0441C69.5978 60.0335 70.0849 60.0465 70.5722 60.0832L70.6097 59.5847ZM69.0997 59.5442L69.0888 59.0444C68.5962 59.0551 68.1035 59.0501 67.6108 59.0294L67.5897 59.5289L67.5687 60.0285C68.0827 60.0501 68.5966 60.0553 69.1106 60.0441L69.0997 59.5442ZM67.5897 59.5289L67.6108 59.0294C67.0813 59.0071 66.5519 59.0263 66.0228 59.087L66.0797 59.5837L66.1367 60.0805C66.6143 60.0257 67.0916 60.0084 67.5687 60.0285L67.5897 59.5289ZM66.0797 59.5837L66.0228 59.087C65.5391 59.1424 65.0556 59.1689 64.5723 59.1664L64.5697 59.6664L64.5672 60.1664C65.0905 60.1691 65.6137 60.1404 66.1367 60.0805L66.0797 59.5837ZM64.5697 59.6664L64.5723 59.1664C64.0881 59.164 63.6038 59.1337 63.1192 59.0756L63.0597 59.5721L63.0002 60.0685C63.5223 60.1311 64.0447 60.1637 64.5672 60.1664L64.5697 59.6664ZM63.0597 59.5721L63.1192 59.0756C62.559 59.0085 61.9989 59.0373 61.4415 59.1609L61.5498 59.649L61.658 60.1372C62.1072 60.0375 62.5539 60.015 63.0002 60.0685L63.0597 59.5721ZM61.5498 59.649L61.4415 59.1609C60.9923 59.2605 60.5457 59.2829 60.0994 59.2293L60.0397 59.7257L59.9801 60.2222C60.5404 60.2895 61.1005 60.2608 61.658 60.1372L61.5498 59.649ZM60.0397 59.7257L60.0994 59.2293C59.5735 59.1661 59.0474 59.1391 58.521 59.1482L58.5297 59.6482L58.5384 60.1481C59.0188 60.1397 59.4993 60.1644 59.9801 60.2222L60.0397 59.7257ZM58.5297 59.6482L58.521 59.1482C58.0306 59.1568 57.5401 59.1464 57.0496 59.117L57.0197 59.6161L56.9899 60.1152C57.506 60.1461 58.0222 60.1571 58.5384 60.1481L58.5297 59.6482ZM57.0197 59.6161L57.0496 59.117C56.5166 59.0851 55.9837 59.1006 55.4512 59.1634L55.5097 59.6599L55.5683 60.1565C56.0424 60.1006 56.5162 60.0869 56.9899 60.1152L57.0197 59.6161ZM55.5097 59.6599L55.4512 59.1634C54.9655 59.2206 54.48 59.2517 53.9946 59.2567L53.9997 59.7566L54.0048 60.2566C54.5261 60.2513 55.0473 60.2179 55.5683 60.1565L55.5097 59.6599ZM53.9997 59.7566L53.9946 59.2567C53.493 59.2618 52.9913 59.2644 52.4897 59.2645L52.4897 59.7645L52.4898 60.2645C52.9948 60.2644 53.4998 60.2618 54.0048 60.2566L53.9997 59.7566ZM52.4897 59.7645L52.4897 59.2645C51.9955 59.2645 51.5013 59.251 51.0071 59.2239L50.9797 59.7231L50.9523 60.2224C51.4648 60.2505 51.9773 60.2645 52.4898 60.2645L52.4897 59.7645ZM50.9797 59.7231L51.0071 59.2239C50.4828 59.1951 49.9584 59.1995 49.4341 59.237L49.4697 59.7357L49.5054 60.2344C49.9878 60.1999 50.47 60.1959 50.9523 60.2224L50.9797 59.7231ZM49.4697 59.7357L49.4341 59.237C48.9704 59.2702 48.5065 59.2484 48.0415 59.1713L47.9598 59.6646L47.878 60.1578C48.4197 60.2476 48.9624 60.2733 49.5054 60.2344L49.4697 59.7357ZM47.9598 59.6646L48.0415 59.1713C47.4942 59.0806 46.9457 59.064 46.3972 59.1219L46.4497 59.6192L46.5023 60.1164C46.9605 60.068 47.4187 60.0817 47.878 60.1578L47.9598 59.6646ZM46.4497 59.6192L46.3972 59.1219C45.9047 59.1739 45.4122 59.2097 44.9199 59.2293L44.9397 59.7289L44.9596 60.2285C45.4739 60.2081 45.9881 60.1707 46.5023 60.1164L46.4497 59.6192ZM44.9397 59.7289L44.9199 59.2293C44.4302 59.2488 43.9405 59.2483 43.4507 59.2277L43.4297 59.7272L43.4087 60.2268C43.9257 60.2485 44.4427 60.2491 44.9596 60.2285L44.9397 59.7289ZM43.4297 59.7272L43.4507 59.2277C42.9236 59.2055 42.3966 59.221 41.8697 59.2739L41.9197 59.7714L41.9697 60.2689C42.4496 60.2207 42.9292 60.2067 43.4087 60.2268L43.4297 59.7272ZM41.9197 59.7714L41.8697 59.2739C41.4223 59.3188 40.9742 59.2894 40.5232 59.1843L40.4097 59.6713L40.2962 60.1582C40.8519 60.2877 41.4105 60.3251 41.9697 60.2689L41.9197 59.7714ZM40.4097 59.6713L40.5232 59.1843C39.9767 59.0569 39.4278 59.0048 38.8778 59.0289L38.8998 59.5284L38.9217 60.0279C39.3783 60.0079 39.8361 60.051 40.2962 60.1582L40.4097 59.6713ZM38.8998 59.5284L38.8778 59.0289C38.3666 59.0513 37.8554 59.0859 37.3443 59.1325L37.3897 59.6305L37.4352 60.1284C37.9307 60.0832 38.4262 60.0497 38.9217 60.0279L38.8998 59.5284ZM37.3897 59.6305L37.3443 59.1325C36.8585 59.1769 36.3729 59.1954 35.8873 59.188L35.8797 59.688L35.8722 60.1879C36.3933 60.1958 36.9143 60.176 37.4352 60.1284L37.3897 59.6305ZM35.8797 59.688L35.8873 59.188C35.3621 59.1801 34.8371 59.2064 34.3124 59.267L34.3697 59.7637L34.4271 60.2604C34.909 60.2047 35.3906 60.1806 35.8722 60.1879L35.8797 59.688ZM34.3697 59.7637L34.3124 59.267C33.8303 59.3226 33.3484 59.3471 32.8667 59.3404L32.8598 59.8403L32.8528 60.3403C33.3778 60.3476 33.9026 60.3209 34.4271 60.2604L34.3697 59.7637ZM32.8598 59.8403L32.8667 59.3404C32.3694 59.3335 31.8721 59.3175 31.3747 59.2926L31.3497 59.792L31.3247 60.2914C31.8341 60.3169 32.3434 60.3332 32.8528 60.3403L32.8598 59.8403ZM31.3497 59.792L31.3747 59.2926C30.8805 59.2679 30.3862 59.2296 29.8918 59.1779L29.8397 59.6752L29.7877 60.1725C30.3 60.2261 30.8123 60.2657 31.3247 60.2914L31.3497 59.792ZM29.8397 59.6752L29.8918 59.1779C29.3571 59.1219 28.8221 59.1167 28.2873 59.1623L28.3297 59.6605L28.3722 60.1587C28.844 60.1185 29.3157 60.1231 29.7877 60.1725L29.8397 59.6752ZM28.3297 59.6605L28.2873 59.1623C27.7908 59.2047 27.2943 59.2367 26.7978 59.2585L26.8197 59.758L26.8416 60.2575C27.3519 60.2352 27.8621 60.2022 28.3722 60.1587L28.3297 59.6605ZM26.8197 59.758L26.7978 59.2585C26.2945 59.2806 25.7912 59.3026 25.2879 59.3246L25.3097 59.8241L25.3316 60.3236C25.8349 60.3016 26.3383 60.2796 26.8416 60.2575L26.8197 59.758ZM25.3097 59.8241L25.2879 59.3246C24.8167 59.3452 24.3451 59.3206 23.8729 59.2508L23.7997 59.7454L23.7266 60.24C24.261 60.3191 24.7962 60.347 25.3316 60.3236L25.3097 59.8241ZM23.7997 59.7454L23.8729 59.2508C23.3391 59.1718 22.8045 59.1429 22.2697 59.1643L22.2897 59.6639L22.3097 60.1635C22.7816 60.1446 23.2537 60.17 23.7266 60.24L23.7997 59.7454ZM22.2897 59.6639L22.2697 59.1643C21.7566 59.1848 21.2435 59.2205 20.7305 59.2712L20.7797 59.7688L20.8289 60.2663C21.3226 60.2175 21.8162 60.1833 22.3097 60.1635L22.2897 59.6639ZM20.7797 59.7688L20.7305 59.2712C20.2761 59.3161 19.8214 59.2946 19.365 59.2061L19.2697 59.6969L19.1745 60.1877C19.7247 60.2945 20.2767 60.3209 20.8289 60.2663L20.7797 59.7688ZM19.2697 59.6969L19.365 59.2061C18.8302 59.1023 18.2943 59.0512 17.7578 59.0534L17.7597 59.5534L17.7617 60.0534C18.2318 60.0515 18.7026 60.0962 19.1745 60.1877L19.2697 59.6969ZM17.7597 59.5534L17.7578 59.0534C17.26 59.0553 16.7622 59.049 16.2644 59.0344L16.2497 59.5342L16.2351 60.034C16.7439 60.0489 17.2528 60.0554 17.7617 60.0534L17.7597 59.5534ZM16.2497 59.5342L16.2644 59.0344C15.7314 59.0187 15.1987 59.0503 14.6666 59.129L14.7397 59.6236L14.8129 60.1183C15.2875 60.0481 15.7614 60.0201 16.2351 60.034L16.2497 59.5342ZM14.7397 59.6236L14.6666 59.129C14.1848 59.2002 13.7033 59.2395 13.2221 59.2468L13.2297 59.7467L13.2373 60.2467C13.7628 60.2387 14.2881 60.1958 14.8129 60.1183L14.7397 59.6236ZM13.2297 59.7467L13.2221 59.2468C12.7214 59.2544 12.2208 59.258 11.7201 59.2576L11.7197 59.7576L11.7194 60.2576C12.2254 60.258 12.7313 60.2544 13.2373 60.2467L13.2297 59.7467ZM11.7197 59.7576L11.7201 59.2576C11.2368 59.2573 10.7534 59.228 10.2696 59.1696L10.2098 59.666L10.1499 60.1624C10.6728 60.2255 11.196 60.2573 11.7194 60.2576L11.7197 59.7576ZM10.2098 59.666L10.2696 59.1696C9.75353 59.1074 9.2373 59.0653 8.72096 59.0433L8.69974 59.5429L8.67853 60.0424C9.16886 60.0633 9.6593 60.1032 10.1499 60.1624L10.2098 59.666ZM8.69974 59.5429L8.72096 59.0433C8.18583 59.0206 7.65097 59.0487 7.11678 59.1275L7.18974 59.6222L7.2627 60.1168C7.73517 60.0471 8.20698 60.0224 8.67853 60.0424L8.69974 59.5429ZM7.18974 59.6222L7.11678 59.1275C6.66228 59.1946 6.20855 59.194 5.75398 59.1257L5.67974 59.6202L5.60551 60.1147C6.15761 60.1975 6.71055 60.1983 7.2627 60.1168L7.18974 59.6222ZM5.67974 59.6202L5.75398 59.1257C5.18813 59.0408 4.62199 59.063 4.05878 59.1911L4.16973 59.6787L4.28068 60.1662C4.72414 60.0653 5.16468 60.0485 5.60551 60.1147L5.67974 59.6202ZM4.16973 59.6787L4.05878 59.1911C3.59501 59.2967 3.13325 59.3431 2.67259 59.3312L2.65974 59.8311L2.64689 60.3309C3.19287 60.3449 3.73778 60.2897 4.28068 60.1662L4.16973 59.6787ZM2.65974 59.8311L2.67259 59.3312C2.20766 59.3193 1.77617 59.2549 1.37624 59.1405L1.23881 59.6213L1.10138 60.102C1.58937 60.2415 2.10517 60.317 2.64689 60.3309L2.65974 59.8311ZM1.23881 59.6213L1.37624 59.1405C1.24058 59.1018 1.172 59.046 1.13144 58.9915C1.08818 58.9333 1.0443 58.8321 1.03551 58.6516L0.5361 58.6759L0.0366917 58.7002C0.0524081 59.023 0.137896 59.3311 0.328872 59.588C0.522547 59.8486 0.793073 60.0139 1.10138 60.102L1.23881 59.6213ZM0.5361 58.6759L1.03551 58.6516C1.01149 58.1583 1.0024 57.6651 1.00823 57.1718L0.508261 57.1659L0.00829536 57.16C0.00223041 57.6734 0.0116966 58.1868 0.0366917 58.7002L0.5361 58.6759ZM0.508261 57.1659L1.00823 57.1718C1.0141 56.6749 1.02955 56.178 1.0546 55.6811L0.555231 55.6559L0.0558652 55.6307C0.0301741 56.1405 0.0143172 56.6502 0.00829536 57.16L0.508261 57.1659ZM0.555231 55.6559L1.0546 55.6811C1.07915 55.1939 1.12737 54.7065 1.19931 54.2189L0.704663 54.1459L0.210017 54.0729C0.133448 54.592 0.0820474 55.1112 0.0558652 55.6307L0.555231 55.6559ZM0.704663 54.1459L1.19931 54.2189C1.28173 53.6602 1.27023 53.1009 1.16525 52.5434L0.673885 52.6359L0.182519 52.7284C0.267081 53.1775 0.276107 53.6249 0.210017 54.0729L0.704663 54.1459ZM0.673885 52.6359L1.16525 52.5434C1.0756 52.0673 1.02683 51.592 1.01867 51.1173L0.518744 51.1259L0.0188177 51.1345C0.0279654 51.6665 0.0826252 52.1979 0.182519 52.7284L0.673885 52.6359ZM0.518744 51.1259L1.01867 51.1173C1.01005 50.6161 1.00467 50.1149 1.00252 49.6138L0.502526 49.6159L0.00253087 49.618C0.00469685 50.1235 0.0101258 50.629 0.0188177 51.1345L0.518744 51.1259ZM0.502526 49.6159L1.00252 49.6138C1.00038 49.114 1.00364 48.6143 1.01231 48.1146L0.51238 48.1059L0.0124554 48.0972C0.00366688 48.6042 0.000358701 49.1111 0.00253087 49.618L0.502526 49.6159ZM0.51238 48.1059L1.01231 48.1146C1.02071 47.6298 1.05607 47.1449 1.11844 46.6597L0.62252 46.5959L0.1266 46.5322C0.0595701 47.0536 0.0215033 47.5753 0.0124554 48.0972L0.51238 48.1059ZM0.62252 46.5959L1.11844 46.6597C1.18634 46.1314 1.21401 45.6028 1.20135 45.0739L0.701489 45.0859L0.201632 45.0979C0.213076 45.5757 0.188099 46.0537 0.1266 46.5322L0.62252 46.5959ZM0.701489 45.0859L1.20135 45.0739C1.18929 44.5706 1.17718 44.0672 1.16501 43.5638L0.665157 43.5759L0.165303 43.588C0.177468 44.0913 0.189578 44.5946 0.201632 45.0979L0.701489 45.0859ZM0.665157 43.5759L1.16501 43.5638C1.15274 43.0561 1.13376 42.5484 1.10807 42.0406L0.608713 42.0659L0.109351 42.0912C0.134592 42.5901 0.153242 43.0891 0.165303 43.588L0.665157 43.5759ZM0.608713 42.0659L1.10807 42.0406C1.08323 41.5496 1.0765 41.0585 1.08786 40.5675L0.587998 40.5559L0.0881318 40.5443C0.0761945 41.06 0.0832688 41.5756 0.109351 42.0912L0.608713 42.0659ZM0.587998 40.5559L1.08786 40.5675C1.09947 40.0661 1.11402 39.5647 1.13151 39.0633L0.631812 39.0459L0.132116 39.0285C0.114491 39.5338 0.0998297 40.039 0.0881318 40.5443L0.587998 40.5559ZM0.631812 39.0459L1.13151 39.0633C1.14932 38.5526 1.15585 38.042 1.15111 37.5313L0.651128 37.5359L0.151149 37.5406C0.155761 38.0365 0.149417 38.5325 0.132116 39.0285L0.631812 39.0459ZM0.651128 37.5359L1.15111 37.5313C1.14642 37.0271 1.14043 36.5229 1.13315 36.0187L0.633201 36.0259L0.133253 36.0331C0.140512 36.5356 0.146478 37.0381 0.151149 37.5406L0.651128 37.5359ZM0.633201 36.0259L1.13315 36.0187C1.12581 35.5108 1.11147 35.0028 1.09014 34.4949L0.590577 34.5159L0.0910175 34.5369C0.111969 35.0356 0.126048 35.5344 0.133253 36.0331L0.633201 36.0259ZM0.590577 34.5159L1.09014 34.4949C1.06905 33.9931 1.05023 33.4912 1.03366 32.9894L0.53393 33.0059L0.0342026 33.0224C0.0508721 33.5272 0.0698104 34.0321 0.0910175 34.5369L0.590577 34.5159ZM0.53393 33.0059L1.03366 32.9894C1.0177 32.5062 1.03089 32.023 1.07325 31.5396L0.575162 31.4959L0.0770706 31.4523C0.0312161 31.9755 0.0169179 32.4989 0.0342026 33.0224L0.53393 33.0059ZM0.575162 31.4959L1.07325 31.5396C1.12041 31.0015 1.11125 30.4633 1.04586 29.9256L0.54952 29.9859L0.0531756 30.0463C0.110196 30.5152 0.118133 30.9837 0.0770706 31.4523L0.575162 31.4959ZM0.54952 29.9859L1.04586 29.9256C0.991536 29.4788 1.01282 29.0318 1.11074 28.5823L0.622204 28.4759L0.133664 28.3695C0.012268 28.9267 -0.0149013 29.4864 0.0531756 30.0463L0.54952 29.9859ZM0.622204 28.4759L1.11074 28.5823C1.23116 28.0296 1.26568 27.4746 1.21329 26.919L0.715499 26.9659L0.217708 27.0128C0.260241 27.4639 0.232566 27.9155 0.133664 28.3695L0.622204 28.4759ZM0.715499 26.9659L1.21329 26.919C1.16768 26.4353 1.1509 25.9518 1.16291 25.4683L0.663067 25.4559L0.163222 25.4435C0.15022 25.9667 0.168393 26.4899 0.217708 27.0128L0.715499 26.9659ZM0.663067 25.4559L1.16291 25.4683C1.17576 24.9515 1.16762 24.4346 1.1385 23.9178L0.63929 23.9459L0.140082 23.974C0.167681 24.4639 0.175393 24.9537 0.163222 25.4435L0.663067 25.4559ZM0.63929 23.9459L1.1385 23.9178C1.11088 23.4276 1.10271 22.9375 1.11396 22.4474L0.614096 22.4359L0.114228 22.4244C0.102362 22.941 0.110981 23.4575 0.140082 23.974L0.63929 23.9459ZM0.614096 22.4359L1.11396 22.4474C1.12604 21.9219 1.10331 21.3966 1.04584 20.8715L0.548804 20.9259L0.0517725 20.9803C0.104483 21.4619 0.125283 21.9432 0.114228 22.4244L0.614096 22.4359ZM0.548804 20.9259L1.04584 20.8715C0.994158 20.3993 0.987159 19.9275 1.02478 19.4556L0.526365 19.4159L0.0279469 19.3762C-0.0146914 19.9109 -0.00673091 20.4458 0.0517725 20.9803L0.548804 20.9259ZM0.526365 19.4159L1.02478 19.4556C1.06417 18.9617 1.11761 18.4677 1.18513 17.9736L0.689736 17.9059L0.19434 17.8382C0.124296 18.3508 0.068828 18.8634 0.0279469 19.3762L0.526365 19.4159ZM0.689736 17.9059L1.18513 17.9736C1.25908 17.4325 1.2706 16.8907 1.21956 16.349L0.721767 16.3959L0.223972 16.4428C0.267787 16.9078 0.257957 17.3727 0.19434 17.8382L0.689736 17.9059ZM0.721767 16.3959L1.21956 16.349C1.17278 15.8526 1.13641 15.3562 1.11044 14.8598L0.611127 14.8859L0.111809 14.912C0.138506 15.4223 0.175895 15.9326 0.223972 16.4428L0.721767 16.3959ZM0.611127 14.8859L1.11044 14.8598C1.08568 14.3865 1.10349 13.9131 1.16397 13.4392L0.667994 13.3759L0.172018 13.3126C0.104015 13.8454 0.083905 14.3786 0.111809 14.912L0.611127 14.8859ZM0.667994 13.3759L1.16397 13.4392C1.2321 12.9055 1.25069 12.3712 1.21963 11.8369L0.720473 11.8659L0.221316 11.8949C0.248772 12.3672 0.232381 12.8397 0.172018 13.3126L0.667994 13.3759ZM0.720473 11.8659L1.21963 11.8369C1.19002 11.3275 1.15109 10.8181 1.10283 10.3087L0.60506 10.3559L0.107289 10.4031C0.154402 10.9004 0.192411 11.3977 0.221316 11.8949L0.720473 11.8659ZM0.60506 10.3559L1.10283 10.3087C1.05704 9.82544 1.04062 9.34231 1.05352 8.85925L0.553697 8.8459L0.0538751 8.83255C0.0398921 9.35616 0.0577081 9.8797 0.107289 10.4031L0.60506 10.3559ZM0.553697 8.8459L1.05352 8.85925C1.06716 8.34845 1.06935 7.83764 1.06008 7.32684L0.560166 7.3359L0.0602477 7.34497C0.0692414 7.84083 0.0671173 8.33669 0.0538751 8.83255L0.553697 8.8459ZM0.560166 7.3359L1.06008 7.32684C1.05157 6.85727 1.09026 6.38713 1.17652 5.91594L0.684689 5.8259L0.192862 5.73586C0.094833 6.27134 0.0505061 6.80787 0.0602477 7.34497L0.560166 7.3359ZM0.684689 5.8259L1.17652 5.91594C1.27649 5.36984 1.30397 4.82232 1.25845 4.2745L0.760165 4.3159L0.261883 4.35731C0.300013 4.81616 0.277178 5.2753 0.192862 5.73586L0.684689 5.8259ZM0.760165 4.3159L1.25845 4.2745C1.2155 3.75767 1.15144 3.24102 1.06632 2.72459L0.572977 2.8059L0.079634 2.88722C0.16044 3.37746 0.221178 3.86748 0.261883 4.35731L0.760165 4.3159ZM0.572977 2.8059L1.06632 2.72459C0.990445 2.26427 0.980463 1.83143 1.03227 1.42397L0.536269 1.3609L0.0402627 1.29783C-0.0249118 1.81038 -0.0104212 2.34088 0.079634 2.88722L0.572977 2.8059ZM0.536269 1.3609L1.03227 1.42397C1.06149 1.19425 1.08367 1.0233 1.09892 0.910259C1.10661 0.853324 1.11224 0.813272 1.11598 0.788425C1.11797 0.775187 1.11879 0.770488 1.1187 0.771004C1.11861 0.771495 1.11828 0.773329 1.11771 0.776103C1.11724 0.778432 1.11605 0.784248 1.11416 0.792003C1.1132 0.79592 1.10806 0.817445 1.09817 0.8443C1.09442 0.854506 1.08091 0.891138 1.05473 0.933568C1.0422 0.953889 1.01308 0.998335 0.963233 1.04307C0.916513 1.08499 0.802021 1.1709 0.629372 1.1709V0.670898V0.170898C0.456666 0.170898 0.342115 0.256832 0.295333 0.298815C0.245426 0.343602 0.216238 0.388133 0.203634 0.408566C0.177322 0.45122 0.163661 0.488189 0.159739 0.498845C0.149522 0.526598 0.143995 0.54947 0.142603 0.555185C0.138529 0.571895 0.135859 0.586453 0.134787 0.592356C0.132084 0.607247 0.129442 0.62411 0.127108 0.639623C0.122195 0.672276 0.11574 0.718474 0.107905 0.776539C0.0921227 0.893503 0.0695451 1.06755 0.0402627 1.29783L0.536269 1.3609ZM0.629372 0.670898V1.1709C0.652754 1.1709 0.748157 1.16442 0.854514 1.15752C0.98136 1.14929 1.17065 1.13695 1.42233 1.12051L1.38974 0.621569L1.35714 0.122633C1.10549 0.139075 0.916383 0.151401 0.789774 0.159615C0.726446 0.163723 0.678882 0.166795 0.646984 0.168835C0.630993 0.169858 0.619179 0.170607 0.611365 0.171093C0.607388 0.171341 0.604868 0.171494 0.603499 0.171575C0.588652 0.172452 0.610271 0.170898 0.629372 0.170898V0.670898Z" /> diff --git a/core/designsystem/src/main/res/drawable/bg_input_box_focus.xml b/core/designsystem/src/main/res/drawable/bg_input_box_focus.xml deleted file mode 100644 index e0885619..00000000 --- a/core/designsystem/src/main/res/drawable/bg_input_box_focus.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - diff --git a/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt b/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt index c2752962..23c05368 100644 --- a/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt +++ b/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt @@ -166,7 +166,7 @@ private fun HomeInputSection( if (state.isEmotionPickerExpanded) { val pickerOffset = with(LocalDensity.current) { - // 입력창 높이가 64dp/149dp+ 로 달라지므로 겹침 폭만 고정해 실측 높이 기준으로 잡는다. + // 입력창 높이가 100dp/149dp+ 로 달라지므로 겹침 폭만 고정해 실측 높이 기준으로 잡는다. IntOffset( x = PickerHorizontalInset.roundToPx(), y = (inputBarHeightPx.toDp() - PickerOverlapHeight).roundToPx(), @@ -271,7 +271,7 @@ private val InputBarHorizontalPadding: Dp = 18.dp private val PickerHorizontalInset = (-61).dp -// 64dp/149dp 두 입력창 높이 모두에서 Figma가 보여 준 9~10dp 겹침 폭. +// 100dp/149dp 두 입력창 높이 모두에서 Figma가 보여 준 9~10dp 겹침 폭. private val PickerOverlapHeight = 9.dp @Preview(name = "Home - Light", showBackground = true, widthDp = 402, heightDp = 720) diff --git a/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt b/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt index d086463e..529d1140 100644 --- a/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt +++ b/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt @@ -99,6 +99,8 @@ class HomeViewModel @Inject constructor( // applicationScope 로 돌려서 이 화면을 벗어나도 끊기지 않는다. viewModelScope.launch { session.finishSend() } reduce { state.copy(input = "") } + // 비운 입력은 상한과 무관해졌으므로, 다시 넘기면 새로 알린다. + lengthWarned = false postSideEffect(HomeSideEffect.OpenConversation(result.data.message.conversationId)) } // 입력은 남겨 둔다. 실패한 문구를 다시 치게 하면 안 된다. From cf247254737e8f68790715fae432126862795ad2 Mon Sep 17 00:00:00 2001 From: soyeonLee <109227292+soyeonLee126@users.noreply.github.com> Date: Mon, 17 Aug 2026 22:38:37 +0900 Subject: [PATCH 06/20] =?UTF-8?q?fix:=20=EC=9E=85=EB=A0=A5=EC=B0=BD=20?= =?UTF-8?q?=EC=A2=8C=EC=9A=B0=20=EC=97=AC=EB=B0=B1=EC=9D=84=2020dp=20?= =?UTF-8?q?=EB=A1=9C=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 시안이 default 20dp, focus 16dp 로 어긋나 있어 포커스가 들어갈 때 텍스트가 왼쪽으로 4dp 움직였다. 최근 노드(4001:8200)의 20dp 로 맞춘다. --- .../android/core/designsystem/component/GamssInputBar.kt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt index 1e1ca2f2..1de7a3d9 100644 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt @@ -41,9 +41,7 @@ private val InputBarTopPadding = 16.dp private val InputBarBottomPadding = 16.dp private val ControlsRowHeight = 32.dp -// 좌우 여백은 Figma 가 두 상태에서 다르게 잡혀 있다(default 20, focus 16). -private val CollapsedHorizontalPadding = 20.dp -private val ExpandedHorizontalPadding = 16.dp +private val InputBarHorizontalPadding = 20.dp // 배경까지 담긴 에셋이라 tint 하지 않는다. 컨트롤 행이 32dp 고정이라 // Android 권장 터치 영역(48dp)은 주지 못하고 아이콘 크기가 곧 터치 영역이다. @@ -87,8 +85,6 @@ fun GamssInputBar( val barHeight = animatedBaseHeight + (InputLineHeight * lineCount - textAreaHeight).coerceAtLeast(0.dp) - val horizontalPadding = if (isExpanded) ExpandedHorizontalPadding else CollapsedHorizontalPadding - Layout( modifier = modifier .height(barHeight) @@ -115,7 +111,7 @@ fun GamssInputBar( }, ) { measurables, constraints -> val (textFieldMeasurable, controlsMeasurable) = measurables - val sidePad = horizontalPadding.roundToPx() + val sidePad = InputBarHorizontalPadding.roundToPx() // 폭이 열려 있는 부모(가로 스크롤 등) 아래에서는 maxWidth 가 Infinity 라 그대로 쓰면 터진다. val barWidth = constraints.constrainWidth( if (constraints.hasBoundedWidth) constraints.maxWidth else constraints.minWidth, From 5c836ef3d947b90ca4bdfcabb99a8a319bd1a40c Mon Sep 17 00:00:00 2001 From: soyeonLee <109227292+soyeonLee126@users.noreply.github.com> Date: Mon, 17 Aug 2026 23:02:20 +0900 Subject: [PATCH 07/20] =?UTF-8?q?design:=20=EC=86=90=EA=B7=B8=EB=A6=BC=20?= =?UTF-8?q?=ED=85=8C=EB=91=90=EB=A6=AC=EB=A5=BC=20Card=5FButton=20?= =?UTF-8?q?=EC=95=84=ED=8A=B8=EB=A1=9C=20=EA=B5=90=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 선 굵기 1.5dp, 색 #303136 으로 바뀐다. 9-slice 로 그리므로 원본이 99x38 이어도 입력창(366x100/149)과 감정 선택 패널(211x94) 모두 굵기가 유지된다. --- .../src/main/res/drawable/bg_input_box.xml | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/core/designsystem/src/main/res/drawable/bg_input_box.xml b/core/designsystem/src/main/res/drawable/bg_input_box.xml index a1acd766..60fd627d 100644 --- a/core/designsystem/src/main/res/drawable/bg_input_box.xml +++ b/core/designsystem/src/main/res/drawable/bg_input_box.xml @@ -1,21 +1,12 @@ - + android:width="99dp" + android:height="38dp" + android:viewportWidth="99" + android:viewportHeight="38"> - - - + android:fillColor="#FF303136" + android:pathData="M2.12597 0.895054L2.18494 1.64273L2.12597 0.895054ZM4.39097 0.895628L4.33162 1.64328L4.39097 0.895628ZM6.65597 0.928952L6.61855 0.179886L6.65597 0.928952ZM15.716 0.831648L15.6808 1.58082L15.716 0.831648ZM56.486 1.1037L56.4781 1.85365L56.486 1.1037ZM70.076 0.907937L70.1483 0.161436L70.076 0.907937ZM76.871 1.08028L76.9189 1.82874L76.871 1.08028ZM88.196 1.01353L88.1445 1.76176L88.196 1.01353ZM92.726 1.03481L92.5996 0.295533L92.726 1.03481ZM97.9685 11.5719L97.2343 11.725L97.9685 11.5719ZM97.9753 16.1019L97.2361 16.2284L97.9753 16.1019ZM98.1872 18.3669L98.9371 18.3549L98.1872 18.3669ZM98.0398 25.1619L97.2938 25.2392L98.0398 25.1619ZM98.0673 29.6919L97.323 29.7845L98.0673 29.6919ZM97.8862 34.2219L97.1366 34.2466L97.8862 34.2219ZM97.641 36.1071L96.9389 35.8435L97.641 36.1071ZM96.226 37.0201L96.1209 36.2775L96.226 37.0201ZM93.961 37.0375L94.0549 36.2934L93.961 37.0375ZM89.431 37.0899L89.4561 37.8395L89.431 37.0899ZM80.371 37.0715L80.417 36.323L80.371 37.0715ZM75.841 36.918L75.8586 36.1682L75.841 36.918ZM53.191 37.1587L53.1894 36.4087L53.191 37.1587ZM50.926 37.1583L50.9278 36.4083L50.926 37.1583ZM39.601 36.8571L39.5924 37.607L39.601 36.8571ZM32.806 36.9923L32.9153 36.2503L32.806 36.9923ZM30.541 36.9991L30.4273 36.2577L30.541 36.9991ZM28.276 37.1309L28.2483 37.8804L28.276 37.1309ZM23.746 36.978L23.7956 36.2297L23.746 36.978ZM19.216 37.1128L19.1419 36.3665L19.216 37.1128ZM5.62599 36.9581L5.65887 37.7074L5.62599 36.9581ZM0.924823 21.2319L0.178176 21.3027L0.924823 21.2319ZM0.877561 1.48188L0.147123 1.31171L0.877561 1.48188ZM2.12597 0.895054L2.18494 1.64273C2.90056 1.58629 3.616 1.58647 4.33162 1.64328L4.39097 0.895628L4.45032 0.14798C3.65594 0.0849246 2.86138 0.0847231 2.067 0.147376L2.12597 0.895054ZM4.39097 0.895628L4.33162 1.64328C5.11875 1.70576 5.90607 1.71735 6.69339 1.67802L6.65597 0.928952L6.61855 0.179886C5.89586 0.215986 5.17319 0.205359 4.45032 0.14798L4.39097 0.895628ZM6.65597 0.928952L6.69339 1.67802C7.39951 1.64275 8.10597 1.67622 8.81342 1.77872L8.92097 1.03647L9.02852 0.294224C8.22597 0.177941 7.42242 0.139731 6.61855 0.179886L6.65597 0.928952ZM8.92097 1.03647L8.81342 1.77872C9.61819 1.89533 10.424 1.93003 11.2301 1.88255L11.186 1.13384L11.1419 0.385142C10.4379 0.426613 9.73375 0.396407 9.02852 0.294224L8.92097 1.03647ZM11.186 1.13384L11.2301 1.88255C12.0021 1.83706 12.774 1.76514 13.5458 1.6668L13.451 0.92282L13.3562 0.178835C12.6179 0.272905 11.8798 0.341666 11.1419 0.385142L11.186 1.13384ZM13.451 0.92282L13.5458 1.6668C14.258 1.57605 14.9695 1.54745 15.6808 1.58082L15.716 0.831648L15.7511 0.0824717C14.9524 0.0450017 14.154 0.077182 13.3562 0.178835L13.451 0.92282ZM15.716 0.831648L15.6808 1.58082C16.4624 1.61749 17.244 1.61255 18.0255 1.56602L17.981 0.817342L17.9364 0.0686685C17.2079 0.112046 16.4795 0.116645 15.7511 0.0824717L15.716 0.831648ZM17.981 0.817342L18.0255 1.56602C18.7657 1.52194 19.5058 1.49998 20.2458 1.50011L20.246 0.750109L20.2461 0.000109196C19.4762 -2.8789e-05 18.7062 0.0228261 17.9364 0.0686685L17.981 0.817342ZM20.246 0.750109L20.2458 1.50011C20.9617 1.50024 21.6781 1.55611 22.3954 1.66797L22.511 0.926932L22.6265 0.18589C21.8338 0.0622594 21.0402 0.000251472 20.2461 0.000109196L20.246 0.750109ZM22.511 0.926932L22.3954 1.66797C23.1882 1.79162 23.9819 1.85352 24.7761 1.85343L24.776 1.10343L24.7759 0.353428C24.0601 0.353507 23.3437 0.297742 22.6265 0.18589L22.511 0.926932ZM24.776 1.10343L24.7761 1.85343C25.5501 1.85334 26.324 1.82385 27.0979 1.76497L27.041 1.01714L26.9841 0.269297C26.2479 0.325308 25.5119 0.353347 24.7759 0.353428L24.776 1.10343ZM27.041 1.01714L27.0979 1.76497C27.8462 1.70804 28.5945 1.66115 29.3428 1.62432L29.306 0.875227L29.2691 0.126134C28.5074 0.163624 27.7457 0.211345 26.9841 0.269297L27.041 1.01714ZM29.306 0.875227L29.3428 1.62432C30.0932 1.58739 30.8435 1.55751 31.5938 1.53468L31.571 0.785028L31.5482 0.035375C30.7885 0.0584907 30.0288 0.0887438 29.2691 0.126134L29.306 0.875227ZM31.571 0.785028L31.5938 1.53468C32.2846 1.51366 32.977 1.58022 33.6724 1.73565L33.836 1.00371L33.9996 0.271772C33.1849 0.0896745 32.3673 0.0104504 31.5482 0.035375L31.571 0.785028ZM33.836 1.00371L33.6724 1.73565C34.5038 1.92149 35.339 1.97375 36.1751 1.89073L36.101 1.1444L36.0269 0.398072C35.3529 0.464989 34.6782 0.423454 33.9996 0.271772L33.836 1.00371ZM36.101 1.1444L36.1751 1.89073C36.9041 1.81835 37.6329 1.78434 38.3615 1.78867L38.366 1.03869L38.3704 0.288699C37.5891 0.284054 36.8078 0.320527 36.0269 0.398072L36.101 1.1444ZM38.366 1.03869L38.3615 1.78867C39.0964 1.79304 39.8313 1.82697 40.5664 1.89049L40.631 1.14327L40.6955 0.396053C39.9206 0.329099 39.1456 0.293308 38.3704 0.288699L38.366 1.03869ZM40.631 1.14327L40.5664 1.89049C41.3781 1.96061 42.1898 1.93855 43.0004 1.82457L42.896 1.08187L42.7915 0.339179C42.0922 0.43752 41.3939 0.45639 40.6955 0.396053L40.631 1.14327ZM42.896 1.08187L43.0004 1.82457C43.7195 1.72345 44.438 1.67535 45.156 1.68011L45.161 0.930122L45.1659 0.180138C44.374 0.174897 43.5824 0.22797 42.7915 0.339179L42.896 1.08187ZM45.161 0.930122L45.156 1.68011C45.8789 1.68489 46.6022 1.7359 47.326 1.83326L47.426 1.08996L47.526 0.346653C46.7397 0.240893 45.953 0.185347 45.1659 0.180138L45.161 0.930122ZM47.426 1.08996L47.326 1.83326C48.1502 1.94414 48.9754 1.9394 49.7993 1.81913L49.691 1.077L49.5826 0.334862C48.8966 0.435002 48.2117 0.438897 47.526 0.346653L47.426 1.08996ZM49.691 1.077L49.7993 1.81913C50.5193 1.71404 51.2386 1.66086 51.9575 1.65942L51.956 0.90942L51.9545 0.159422C51.1633 0.161012 50.3726 0.219553 49.5826 0.334862L49.691 1.077ZM51.956 0.90942L51.9575 1.65942C52.6916 1.65794 53.4257 1.68712 54.16 1.74698L54.221 0.999456L54.2819 0.251936C53.5062 0.188707 52.7304 0.157862 51.9545 0.159422L51.956 0.90942ZM54.221 0.999456L54.16 1.74698C54.9326 1.80995 55.7053 1.84552 56.4781 1.85365L56.486 1.1037L56.4939 0.353737C55.7566 0.345973 55.0193 0.312044 54.2819 0.251936L54.221 0.999456ZM56.486 1.1037L56.4781 1.85365C57.2336 1.86161 57.9892 1.86871 58.7448 1.87495L58.751 1.12498L58.7572 0.375001C58.0027 0.36877 57.2483 0.361682 56.4939 0.353737L56.486 1.1037ZM58.751 1.12498L58.7448 1.87495C59.5467 1.88157 60.3479 1.81328 61.1477 1.67052L61.016 0.932191L60.8842 0.193859C60.174 0.320619 59.4652 0.380848 58.7572 0.375001L58.751 1.12498ZM61.016 0.932191L61.1477 1.67052C61.8034 1.55349 62.4576 1.56857 63.116 1.71703L63.281 0.985402L63.4459 0.253769C62.5944 0.0617635 61.7385 0.0413775 60.8842 0.193859L61.016 0.932191ZM63.281 0.985402L63.116 1.71703C63.9327 1.90118 64.7525 1.97865 65.5739 1.94809L65.546 1.19861L65.5181 0.449127C64.8294 0.474753 64.1392 0.410092 63.4459 0.253769L63.281 0.985402ZM65.546 1.19861L65.5739 1.94809C66.3591 1.91887 67.1439 1.84216 67.9282 1.7181L67.811 0.977316L67.6938 0.236528C66.968 0.351339 66.2428 0.422158 65.5181 0.449127L65.546 1.19861ZM67.811 0.977316L67.9282 1.7181C68.621 1.60851 69.3123 1.58743 70.0036 1.65444L70.076 0.907937L70.1483 0.161436C69.3297 0.0820851 68.511 0.107256 67.6938 0.236528L67.811 0.977316ZM70.076 0.907937L70.0036 1.65444C70.7967 1.73131 71.59 1.74727 72.3834 1.70226L72.341 0.953468L72.2985 0.204671C71.5819 0.24532 70.8653 0.230928 70.1483 0.161436L70.076 0.907937ZM72.341 0.953468L72.3834 1.70226C73.0951 1.66189 73.8069 1.6831 74.5193 1.76601L74.606 1.02104L74.6927 0.276071C73.895 0.18323 73.0968 0.159388 72.2985 0.204671L72.341 0.953468ZM74.606 1.02104L74.5193 1.76601C75.3187 1.85907 76.1188 1.88002 76.9189 1.82874L76.871 1.08028L76.823 0.331815C76.1131 0.377308 75.4032 0.358769 74.6927 0.276071L74.606 1.02104ZM76.871 1.08028L76.9189 1.82874C77.6696 1.78064 78.4202 1.73912 79.1708 1.70419L79.136 0.954998L79.1011 0.205809C78.3417 0.24115 77.5824 0.283152 76.823 0.331815L76.871 1.08028ZM79.136 0.954998L79.1708 1.70419C79.9353 1.66861 80.6998 1.61845 81.4642 1.55371L81.401 0.806381L81.3377 0.0590559C80.5921 0.122197 79.8466 0.171114 79.1011 0.205809L79.136 0.954998ZM81.401 0.806381L81.4642 1.55371C82.1817 1.49295 82.8988 1.48649 83.6161 1.53431L83.666 0.785968L83.7159 0.0376293C82.9231 -0.0152196 82.1303 -0.00806868 81.3377 0.0590559L81.401 0.806381ZM83.666 0.785968L83.6161 1.53431C84.3795 1.5852 85.143 1.62314 85.9064 1.64812L85.931 0.898526L85.9555 0.148927C85.209 0.124498 84.4624 0.0873995 83.7159 0.0376293L83.666 0.785968ZM85.931 0.898526L85.9064 1.64812C86.6524 1.67254 87.3984 1.71041 88.1445 1.76176L88.196 1.01353L88.2475 0.265302C87.4835 0.21272 86.7195 0.173928 85.9555 0.148927L85.931 0.898526ZM88.196 1.01353L88.1445 1.76176C88.9015 1.81387 89.6586 1.86281 90.4157 1.90858L90.461 1.15995L90.5062 0.411314C89.7533 0.365795 89.0004 0.317124 88.2475 0.265302L88.196 1.01353ZM90.461 1.15995L90.4157 1.90858C91.2288 1.95774 92.0414 1.91272 92.8523 1.77408L92.726 1.03481L92.5996 0.295533C91.9005 0.415041 91.2031 0.453447 90.5062 0.411314L90.461 1.15995ZM92.726 1.03481L92.8523 1.77408C93.5287 1.65846 94.2032 1.65189 94.8788 1.75411L94.991 1.01255L95.1032 0.27099C94.2688 0.144746 93.4332 0.153016 92.5996 0.295533L92.726 1.03481ZM94.991 1.01255L94.8788 1.75411C95.6435 1.86982 96.3521 1.96116 97.0039 2.02775L97.0802 1.28163L97.1564 0.535518C96.5326 0.471795 95.8484 0.383744 95.1032 0.27099L94.991 1.01255ZM97.0802 1.28163L97.0039 2.02775C97.1701 2.04473 97.1996 2.09145 97.2044 2.0977C97.2237 2.12259 97.2849 2.22628 97.2843 2.51017L98.0343 2.51187L98.7843 2.51357C98.7854 2.04246 98.6884 1.56361 98.3897 1.17842C98.0765 0.774589 97.628 0.583701 97.1564 0.535518L97.0802 1.28163ZM98.0343 2.51187L97.2843 2.51017C97.2825 3.28198 97.3067 4.05374 97.3568 4.82545L98.1052 4.77687L98.8537 4.72829C98.8057 3.98999 98.7826 3.25176 98.7843 2.51357L98.0343 2.51187ZM98.1052 4.77687L97.3568 4.82545C97.4023 5.52585 97.3715 6.22655 97.2642 6.92849L98.0055 7.04187L98.7469 7.15525C98.8705 6.34719 98.9062 5.53789 98.8537 4.72829L98.1052 4.77687ZM98.0055 7.04187L97.2642 6.92849C97.1447 7.70981 97.0676 8.49161 97.0331 9.27378L97.7823 9.30687L98.5316 9.33996C98.5637 8.61213 98.6355 7.88393 98.7469 7.15525L98.0055 7.04187ZM97.7823 9.30687L97.0331 9.27378C96.9969 10.0925 97.0643 10.9101 97.2343 11.725L97.9685 11.5719L98.7027 11.4188C98.5577 10.7237 98.5011 10.0312 98.5316 9.33996L97.7823 9.30687ZM97.9685 11.5719L97.2343 11.725C97.3718 12.3846 97.3788 13.0408 97.2558 13.6991L97.9931 13.8369L98.7303 13.9746C98.8894 13.123 98.8801 12.2692 98.7027 11.4188L97.9685 11.5719ZM97.9931 13.8369L97.2558 13.6991C97.0985 14.5412 97.0918 15.3857 97.2361 16.2284L97.9753 16.1019L98.7146 15.9753C98.6004 15.3081 98.6055 14.6426 98.7303 13.9746L97.9931 13.8369ZM97.9753 16.1019L97.2361 16.2284C97.359 16.9461 97.4259 17.6628 97.4373 18.3788L98.1872 18.3669L98.9371 18.3549C98.9245 17.561 98.8502 16.7676 98.7146 15.9753L97.9753 16.1019ZM98.1872 18.3669L97.4373 18.3788C97.4487 19.091 97.3993 19.8036 97.289 20.5173L98.0302 20.6319L98.7714 20.7465C98.8945 19.9501 98.9498 19.1528 98.9371 18.3549L98.1872 18.3669ZM98.0302 20.6319L97.289 20.5173C97.1653 21.317 97.1152 22.1179 97.1391 22.9192L97.8887 22.8969L98.6384 22.8746C98.6173 22.1659 98.6615 21.4567 98.7714 20.7465L98.0302 20.6319ZM97.8887 22.8969L97.1391 22.9192C97.1621 23.6926 97.2137 24.466 97.2938 25.2392L98.0398 25.1619L98.7858 25.0846C98.7094 24.3478 98.6603 23.6111 98.6384 22.8746L97.8887 22.8969ZM98.0398 25.1619L97.2938 25.2392C97.3667 25.9432 97.3673 26.6468 97.2956 27.3509L98.0417 27.4269L98.7879 27.5029C98.87 26.6969 98.8693 25.8905 98.7858 25.0846L98.0398 25.1619ZM98.0417 27.4269L97.2956 27.3509C97.213 28.1621 97.2221 28.9737 97.323 29.7845L98.0673 29.6919L98.8115 29.5993C98.7245 28.9001 98.7167 28.2016 98.7879 27.5029L98.0417 27.4269ZM98.0673 29.6919L97.323 29.7845C97.4082 30.4693 97.3966 31.1535 97.2878 31.8393L98.0285 31.9569L98.7693 32.0744C98.9 31.2502 98.9142 30.4245 98.8115 29.5993L98.0673 29.6919ZM98.0285 31.9569L97.2878 31.8393C97.1606 32.6408 97.1101 33.4434 97.1366 34.2466L97.8862 34.2219L98.6358 34.1971C98.6125 33.4903 98.6569 32.7829 98.7693 32.0744L98.0285 31.9569ZM97.8862 34.2219L97.1366 34.2466C97.1595 34.9406 97.0816 35.4632 96.9389 35.8435L97.641 36.1071L98.3432 36.3706C98.5771 35.7473 98.6627 35.0131 98.6358 34.1971L97.8862 34.2219ZM97.641 36.1071L96.9389 35.8435C96.8853 35.9861 96.7331 36.1908 96.1209 36.2775L96.226 37.0201L96.3311 37.7627C97.2289 37.6356 98.02 37.2317 98.3432 36.3706L97.641 36.1071ZM96.226 37.0201L96.1209 36.2775C95.4317 36.375 94.7436 36.3803 94.0549 36.2934L93.961 37.0375L93.8671 37.7816C94.6883 37.8852 95.5102 37.8789 96.3311 37.7627L96.226 37.0201ZM93.961 37.0375L94.0549 36.2934C93.2518 36.192 92.4479 36.1692 91.644 36.2251L91.696 36.9733L91.748 37.7215C92.4541 37.6724 93.1602 37.6924 93.8671 37.7816L93.961 37.0375ZM91.696 36.9733L91.644 36.2251C90.8979 36.2769 90.1519 36.3153 89.4059 36.3404L89.431 37.0899L89.4561 37.8395C90.2201 37.8139 90.984 37.7746 91.748 37.7215L91.696 36.9733ZM89.431 37.0899L89.4059 36.3404C88.6463 36.3658 87.8868 36.3982 87.1273 36.4374L87.166 37.1864L87.2047 37.9354C87.9552 37.8966 88.7056 37.8647 89.4561 37.8395L89.431 37.0899ZM87.166 37.1864L87.1273 36.4374C86.4073 36.4746 85.6872 36.4614 84.9669 36.3979L84.901 37.1449L84.835 37.892C85.6247 37.9618 86.4147 37.9762 87.2047 37.9354L87.166 37.1864ZM84.901 37.1449L84.9669 36.3979C84.1795 36.3283 83.3919 36.3103 82.6042 36.3437L82.636 37.0931L82.6678 37.8424C83.3901 37.8117 84.1124 37.8283 84.835 37.892L84.901 37.1449ZM82.636 37.0931L82.6042 36.3437C81.8751 36.3747 81.1461 36.3678 80.417 36.323L80.371 37.0715L80.325 37.8201C81.1059 37.8681 81.8868 37.8755 82.6678 37.8424L82.636 37.0931ZM80.371 37.0715L80.417 36.323C79.653 36.276 78.889 36.2429 78.125 36.2235L78.106 36.9733L78.087 37.723C78.833 37.7419 79.579 37.7743 80.325 37.8201L80.371 37.0715ZM78.106 36.9733L78.125 36.2235C77.3695 36.2044 76.614 36.1859 75.8586 36.1682L75.841 36.918L75.8234 37.6678C76.5779 37.6855 77.3325 37.7039 78.087 37.723L78.106 36.9733ZM75.841 36.918L75.8586 36.1682C75.0653 36.1496 74.2723 36.1916 73.4799 36.294L73.576 37.0378L73.6721 37.7816C74.3897 37.6889 75.1067 37.651 75.8234 37.6678L75.841 36.918ZM73.576 37.0378L73.4799 36.294C72.7531 36.3879 72.0268 36.4398 71.3007 36.4497L71.311 37.1996L71.3213 37.9496C72.1052 37.9388 72.8889 37.8828 73.6721 37.7816L73.576 37.0378ZM71.311 37.1996L71.3007 36.4497C70.5872 36.4595 69.8731 36.4104 69.1582 36.3022L69.046 37.0438L68.9337 37.7853C69.7288 37.9056 70.5248 37.9605 71.3213 37.9496L71.311 37.1996ZM69.046 37.0438L69.1582 36.3022C68.3441 36.179 67.5287 36.1541 66.7133 36.228L66.781 36.975L66.8487 37.7219C67.5433 37.659 68.2378 37.68 68.9337 37.7853L69.046 37.0438ZM66.781 36.975L66.7133 36.228C66.0144 36.2914 65.3157 36.2761 64.616 36.1819L64.516 36.9252L64.416 37.6685C65.2263 37.7776 66.0375 37.7954 66.8487 37.7219L66.781 36.975ZM64.516 36.9252L64.616 36.1819C63.8064 36.073 62.996 36.0539 62.1856 36.1249L62.251 36.872L62.3164 37.6192C63.016 37.5579 63.7155 37.5743 64.416 37.6685L64.516 36.9252ZM62.251 36.872L62.1856 36.1249C61.4438 36.1898 60.702 36.235 59.9604 36.2603L59.986 37.0099L60.0116 37.7595C60.7799 37.7332 61.5482 37.6864 62.3164 37.6192L62.251 36.872ZM59.986 37.0099L59.9604 36.2603C59.1911 36.2866 58.422 36.3348 57.6529 36.4049L57.721 37.1518L57.789 37.8987C58.53 37.8312 59.2708 37.7848 60.0116 37.7595L59.986 37.0099ZM57.721 37.1518L57.6529 36.4049C56.9313 36.4706 56.2099 36.4878 55.4886 36.4564L55.456 37.2057L55.4233 37.955C56.212 37.9893 57.0007 37.9705 57.789 37.8987L57.721 37.1518ZM55.456 37.2057L55.4886 36.4564C54.7222 36.423 53.9558 36.4071 53.1894 36.4087L53.191 37.1587L53.1925 37.9087C53.9361 37.9072 54.6797 37.9226 55.4233 37.955L55.456 37.2057ZM53.191 37.1587L53.1894 36.4087C52.4356 36.4103 51.6817 36.4101 50.9278 36.4083L50.926 37.1583L50.9241 37.9083C51.6803 37.9101 52.4364 37.9103 53.1925 37.9087L53.191 37.1587ZM50.926 37.1583L50.9278 36.4083C50.1642 36.4064 49.4005 36.4177 48.6369 36.4423L48.661 37.1919L48.6851 37.9415C49.4315 37.9175 50.1778 37.9064 50.9241 37.9083L50.926 37.1583ZM48.661 37.1919L48.6369 36.4423C47.8931 36.4662 47.1494 36.4734 46.4057 36.4637L46.396 37.2137L46.3863 37.9636C47.1525 37.9736 47.9188 37.9662 48.6851 37.9415L48.661 37.1919ZM46.396 37.2137L46.4057 36.4637C45.6579 36.454 44.9101 36.4336 44.1622 36.4024L44.131 37.1517L44.0997 37.9011C44.8619 37.9329 45.6241 37.9537 46.3863 37.9636L46.396 37.2137ZM44.131 37.1517L44.1622 36.4024C43.4227 36.3715 42.683 36.3178 41.9432 36.2413L41.866 36.9873L41.7888 37.7333C42.559 37.813 43.3293 37.869 44.0997 37.9011L44.131 37.1517ZM41.866 36.9873L41.9432 36.2413C41.1655 36.1608 40.3876 36.116 39.6096 36.1071L39.601 36.8571L39.5924 37.607C40.3243 37.6154 41.0564 37.6575 41.7888 37.7333L41.866 36.9873ZM39.601 36.8571L39.6096 36.1071C38.8336 36.0982 38.0577 36.1218 37.2819 36.1779L37.336 36.926L37.3901 37.674C38.1243 37.6209 38.8583 37.5986 39.5924 37.607L39.601 36.8571ZM37.336 36.926L37.2819 36.1779C36.5283 36.2324 35.7746 36.2848 35.021 36.3352L35.071 37.0835L35.1209 37.8318C35.8773 37.7813 36.6337 37.7287 37.3901 37.674L37.336 36.926ZM35.071 37.0835L35.021 36.3352C34.3196 36.382 33.618 36.3538 32.9153 36.2503L32.806 36.9923L32.6967 37.7342C33.5039 37.8532 34.3123 37.8858 35.1209 37.8318L35.071 37.0835ZM32.806 36.9923L32.9153 36.2503C32.0863 36.1281 31.2561 36.1306 30.4273 36.2577L30.541 36.9991L30.6546 37.7404C31.3358 37.6359 32.0157 37.6339 32.6967 37.7342L32.806 36.9923ZM30.541 36.9991L30.4273 36.2577C29.7185 36.3664 29.0109 36.4075 28.3036 36.3814L28.276 37.1309L28.2483 37.8804C29.0511 37.91 29.8534 37.8632 30.6546 37.7404L30.541 36.9991ZM28.276 37.1309L28.3036 36.3814C27.5434 36.3533 26.7831 36.3333 26.0229 36.3212L26.011 37.0711L25.9991 37.821C26.7488 37.8329 27.4986 37.8527 28.2483 37.8804L28.276 37.1309ZM26.011 37.0711L26.0229 36.3212C25.2805 36.3094 24.5381 36.2789 23.7956 36.2297L23.746 36.978L23.6964 37.7264C24.4639 37.7773 25.2315 37.8088 25.9991 37.821L26.011 37.0711ZM23.746 36.978L23.7956 36.2297C23.0134 36.1778 22.2311 36.1687 21.4488 36.2023L21.481 36.9516L21.5132 37.7009C22.2409 37.6697 22.9686 37.6781 23.6964 37.7264L23.746 36.978ZM21.481 36.9516L21.4488 36.2023C20.6797 36.2354 19.9107 36.2901 19.1419 36.3665L19.216 37.1128L19.2901 37.8591C20.0312 37.7855 20.7722 37.7328 21.5132 37.7009L21.481 36.9516ZM19.216 37.1128L19.1419 36.3665C18.4303 36.4371 17.7192 36.4454 17.008 36.3912L16.951 37.139L16.894 37.8868C17.6928 37.9477 18.4916 37.9384 19.2901 37.8591L19.216 37.1128ZM16.951 37.139L17.008 36.3912C16.2617 36.3343 15.5154 36.2643 14.769 36.1812L14.686 36.9266L14.603 37.672C15.3666 37.757 16.1303 37.8286 16.894 37.8868L16.951 37.139ZM14.686 36.9266L14.769 36.1812C13.9881 36.0943 13.2069 36.0484 12.4255 36.0436L12.421 36.7936L12.4164 37.5436C13.1451 37.548 13.8739 37.5908 14.603 37.672L14.686 36.9266ZM12.421 36.7936L12.4255 36.0436C11.6731 36.0391 10.9207 36.0306 10.1683 36.0183L10.156 36.7682L10.1437 37.5181C10.9013 37.5305 11.6588 37.539 12.4164 37.5436L12.421 36.7936ZM10.156 36.7682L10.1683 36.0183C9.39163 36.0055 8.61503 36.0264 7.83854 36.0808L7.89098 36.829L7.94343 37.5772C8.67693 37.5257 9.41033 37.506 10.1437 37.5181L10.156 36.7682ZM7.89098 36.829L7.83854 36.0808C7.09005 36.1333 6.34157 36.176 5.5931 36.2088L5.62599 36.9581L5.65887 37.7074C6.42041 37.6739 7.18193 37.6305 7.94343 37.5772L7.89098 36.829ZM5.62599 36.9581L5.5931 36.2088C4.82602 36.2425 4.05899 36.2947 3.29203 36.3655L3.36099 37.1123L3.42994 37.8592C4.17298 37.7906 4.91595 37.74 5.65887 37.7074L5.62599 36.9581ZM3.36099 37.1123L3.29203 36.3655C2.68765 36.4213 2.3802 36.2737 2.20043 36.0781L1.64825 36.5856L1.09607 37.0932C1.68995 37.7393 2.52432 37.9428 3.42994 37.8592L3.36099 37.1123ZM1.64825 36.5856L2.20043 36.0781C1.95751 35.8138 1.77301 35.4009 1.71744 34.7574L0.970223 34.8219L0.223004 34.8864C0.297821 35.7529 0.565341 36.5158 1.09607 37.0932L1.64825 36.5856ZM0.970223 34.8219L1.71744 34.7574C1.65288 34.0097 1.59944 33.2621 1.55711 32.5145L0.808309 32.5569L0.0595084 32.5993C0.102676 33.3617 0.157175 34.1241 0.223004 34.8864L0.970223 34.8219ZM0.808309 32.5569L1.55711 32.5145C1.51691 31.8044 1.54042 31.0943 1.62781 30.3834L0.883418 30.2919L0.139023 30.2004C0.0407726 30.9995 0.0142145 31.7993 0.0595084 32.5993L0.808309 32.5569ZM0.883418 30.2919L1.62781 30.3834C1.72268 29.6117 1.7913 28.8399 1.83364 28.068L1.08476 28.0269L0.335886 27.9858C0.29541 28.7239 0.229796 29.462 0.139023 30.2004L0.883418 30.2919ZM1.08476 28.0269L1.83364 28.068C1.87695 27.2782 1.86536 26.4885 1.79891 25.699L1.05155 25.7619L0.304193 25.8248C0.364835 26.5453 0.375389 27.2655 0.335886 27.9858L1.08476 28.0269ZM1.05155 25.7619L1.79891 25.699C1.73782 24.9732 1.71947 24.2476 1.74382 23.5221L0.994246 23.4969L0.244669 23.4717C0.218335 24.2562 0.238188 25.0406 0.304193 25.8248L1.05155 25.7619ZM0.994246 23.4969L1.74382 23.5221C1.77025 22.7349 1.74611 21.9478 1.67147 21.1611L0.924823 21.2319L0.178176 21.3027C0.246787 22.0259 0.268933 22.7489 0.244669 23.4717L0.994246 23.4969ZM0.924823 21.2319L1.67147 21.1611C1.60221 20.431 1.56979 19.7011 1.57417 18.9714L0.824181 18.9669L0.0741942 18.9624C0.0695109 19.7427 0.104184 20.5228 0.178176 21.3027L0.924823 21.2319ZM0.824181 18.9669L1.57417 18.9714C1.57853 18.2446 1.62374 17.5175 1.70987 16.7901L0.965074 16.7019L0.220277 16.6137C0.127614 17.3963 0.0788949 18.1792 0.0741942 18.9624L0.824181 18.9669ZM0.965074 16.7019L1.70987 16.7901C1.80355 15.9989 1.83909 15.2073 1.81636 14.4154L1.06667 14.4369L0.316979 14.4584C0.337587 15.1765 0.305393 15.8949 0.220277 16.6137L0.965074 16.7019ZM1.06667 14.4369L1.81636 14.4154C1.79446 13.6522 1.76003 12.889 1.71307 12.1258L0.964489 12.1719L0.215905 12.2179C0.261856 12.9648 0.295547 13.7116 0.316979 14.4584L1.06667 14.4369ZM0.964489 12.1719L1.71307 12.1258C1.66853 11.4019 1.66914 10.6781 1.7149 9.9542L0.966391 9.90689L0.217885 9.85958C0.168198 10.6457 0.167538 11.4319 0.215905 12.2179L0.964489 12.1719ZM0.966391 9.90689L1.7149 9.9542C1.76413 9.17533 1.77602 8.39637 1.75057 7.6174L1.00097 7.64189L0.251368 7.66639C0.275255 8.39741 0.264098 9.12845 0.217885 9.85958L0.966391 9.90689ZM1.00097 7.64189L1.75057 7.6174C1.72536 6.84588 1.67465 6.07446 1.59846 5.30317L0.852093 5.37689L0.105726 5.45062C0.178694 6.18932 0.227236 6.9279 0.251368 7.66639L1.00097 7.64189ZM0.852093 5.37689L1.59846 5.30317C1.52652 4.57489 1.49402 3.84687 1.50091 3.11899L0.75094 3.11189L0.000973046 3.1048C-0.00642598 3.88691 0.0285074 4.66889 0.105726 5.45062L0.852093 5.37689ZM0.75094 3.11189L1.50091 3.11899C1.508 2.36909 1.5509 1.89716 1.608 1.65206L0.877561 1.48188L0.147123 1.31171C0.049683 1.72994 0.00816387 2.34469 0.000973046 3.1048L0.75094 3.11189ZM0.877561 1.48188L1.608 1.65206C1.57775 1.78191 1.48851 1.80191 1.56667 1.76517C1.65695 1.72274 1.84695 1.66939 2.18494 1.64273L2.12597 0.895054L2.067 0.147376C1.64999 0.180266 1.25442 0.254498 0.928563 0.40767C0.590588 0.566538 0.254648 0.850181 0.147123 1.31171L0.877561 1.48188Z" /> From e2547d0d2b09a4c15a5ce61a745b51c53a40018a Mon Sep 17 00:00:00 2001 From: soyeonLee <109227292+soyeonLee126@users.noreply.github.com> Date: Mon, 17 Aug 2026 23:37:50 +0900 Subject: [PATCH 08/20] =?UTF-8?q?fix:=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=20?= =?UTF-8?q?=ED=84=B0=EC=B9=98=20=EC=98=81=EC=97=AD=EC=9D=84=2048dp=20?= =?UTF-8?q?=EB=A1=9C=20=EB=84=93=ED=9E=88=EA=B3=A0=20=EB=B0=B0=EA=B2=BD=20?= =?UTF-8?q?=EB=AA=A8=EC=84=9C=EB=A6=AC=EB=A5=BC=20=ED=85=8C=EB=91=90?= =?UTF-8?q?=EB=A6=AC=EC=97=90=20=EB=A7=9E=EC=B6=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 시안의 컨트롤 행은 32dp 라 터치 영역이 Android 권장에 못 미쳤다. 행을 48dp 로 재고 32dp 밴드 중앙에 놓아, 보이는 위치는 그대로 두고 터치만 넓힌다. 전송 버튼도 48dp 터치 박스에 32dp 아이콘을 중앙 배치하고 남는 폭은 오른쪽 여백으로 넘겨 위치를 지킨다 - 테두리 아트의 모서리가 둥글어 직각 배경이 선 밖으로 삐져나오던 것을 라운드로 맞춘다 --- .../designsystem/component/GamssInputBar.kt | 32 +++++++++++++------ .../designsystem/component/GamssSketchyBox.kt | 6 +++- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt index 1de7a3d9..a195a35e 100644 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt @@ -43,9 +43,15 @@ private val ControlsRowHeight = 32.dp private val InputBarHorizontalPadding = 20.dp -// 배경까지 담긴 에셋이라 tint 하지 않는다. 컨트롤 행이 32dp 고정이라 -// Android 권장 터치 영역(48dp)은 주지 못하고 아이콘 크기가 곧 터치 영역이다. +// 배경까지 담긴 에셋이라 tint 하지 않는다. private val SendButtonSize = 32.dp + +// 시안의 컨트롤 행은 32dp 지만 그대로 두면 터치 영역이 Android 권장(48dp)에 못 미친다. +// 행을 48dp 로 재고 32dp 밴드 중앙에 맞춰 놓으면, 보이는 위치는 그대로고 터치만 넓어진다. +// 넘치는 위쪽은 빈 텍스트 자리, 아래·오른쪽은 여백이라 겹쳐도 가려지는 것이 없다. +private val ControlsTouchHeight = 48.dp +private val SendButtonTouchSize = 48.dp +private val ControlsTouchOverhang = (ControlsTouchHeight - ControlsRowHeight) / 2 private const val INPUT_MAX_LINES = 6 private val InputLineHeight = 20.dp @@ -123,12 +129,15 @@ fun GamssInputBar( val textHeight = (totalHeight - topPad - InputBarBottomPadding.roundToPx() - controlsHeight).coerceAtLeast(0) - val controls = controlsMeasurable.measure(Constraints.fixed(availableWidth, controlsHeight)) + val overhang = ControlsTouchOverhang.roundToPx() + val controls = controlsMeasurable.measure( + Constraints.fixed(availableWidth + overhang, ControlsTouchHeight.roundToPx()), + ) val text = textFieldMeasurable.measure(Constraints.fixed(availableWidth, textHeight)) layout(barWidth, totalHeight) { text.place(sidePad, topPad) - controls.place(sidePad, topPad + textHeight) + controls.place(sidePad, topPad + textHeight - overhang) } } } @@ -199,11 +208,16 @@ private fun SendButton( contentDescription: String?, onClick: () -> Unit, ) { - Image( - painter = painterResource(if (canSubmit) GamssIcons.SendButtonOn else GamssIcons.SendButtonOff), - contentDescription = contentDescription, + Box( modifier = Modifier - .size(SendButtonSize) + .size(SendButtonTouchSize) .noRippleClickable(enabled = canSubmit, role = Role.Button, onClick = onClick), - ) + contentAlignment = Alignment.Center, + ) { + Image( + painter = painterResource(if (canSubmit) GamssIcons.SendButtonOn else GamssIcons.SendButtonOff), + contentDescription = contentDescription, + modifier = Modifier.size(SendButtonSize), + ) + } } diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBox.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBox.kt index 586e4f07..00cd7da6 100644 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBox.kt +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBox.kt @@ -1,6 +1,7 @@ package com.gamss.android.core.designsystem.component import androidx.compose.foundation.background +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind @@ -20,6 +21,9 @@ import com.gamss.android.core.designsystem.theme.GamssTheme // 모서리는 늘리지 않고 원본 크기로 찍어 낸다. 굴곡이 가장 심한 구간이라 넉넉히 잡는다. private val ArtCorner = 12.dp +// 아트의 모서리가 둥글어, 배경을 직각으로 칠하면 흰 모서리가 선 밖으로 삐져나온다. +private val BackgroundCorner = 4.dp + /** * 손그림 사각 테두리를 9-slice 로 그린다. 모서리는 원본 크기로 두고 직선 구간만 늘리므로, * 어떤 크기에 올려도 선 굵기가 변하지 않는다. 통째로 늘리면 늘린 방향의 선만 두꺼워진다. @@ -28,7 +32,7 @@ private val ArtCorner = 12.dp fun Modifier.gamssSketchyBox(background: Color = GamssTheme.colors.white): Modifier { val art = painterResource(R.drawable.bg_input_box) return this - .background(background) + .background(background, RoundedCornerShape(BackgroundCorner)) .drawBehind { drawNineSlice(art) } } From 428b1066e5bc14967e5d6f8235995953fc90f148 Mon Sep 17 00:00:00 2001 From: soyeonLee <109227292+soyeonLee126@users.noreply.github.com> Date: Tue, 18 Aug 2026 17:13:59 +0900 Subject: [PATCH 09/20] =?UTF-8?q?fix:=20=EA=B0=90=EC=A0=95=20=ED=8C=A8?= =?UTF-8?q?=EB=84=90=EC=9D=84=20=EB=92=A4=EB=A1=9C=EA=B0=80=EA=B8=B0?= =?UTF-8?q?=EC=99=80=20=EB=B0=94=EA=B9=A5=20=ED=83=AD=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=8B=AB=EA=B3=A0=20=ED=82=A4=EB=B3=B4=EB=93=9C=EB=A5=BC=20?= =?UTF-8?q?=ED=94=BC=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Popup 은 별도 창이라 뒤로가기를 받지 못해 BackHandler 로 직접 닫는다. 토글과 나눈 닫기 인텐트를 둬, 바깥 탭과 Popup dismiss 가 겹쳐 들어와도 다시 열리지 않는다. --- feature/home/build.gradle.kts | 3 ++ .../gamss/android/feature/home/HomeScreen.kt | 37 ++++++++++++++++--- .../android/feature/home/HomeViewModel.kt | 5 +++ .../android/feature/home/HomeViewModelTest.kt | 15 ++++++++ 4 files changed, 55 insertions(+), 5 deletions(-) diff --git a/feature/home/build.gradle.kts b/feature/home/build.gradle.kts index ee3693f0..e207d45a 100644 --- a/feature/home/build.gradle.kts +++ b/feature/home/build.gradle.kts @@ -10,6 +10,9 @@ dependencies { implementation(projects.core.common) implementation(projects.domain) + // 캐릭터 패널을 뒤로가기로 닫기 위한 BackHandler. compose-ui 에는 없는 API 다. + implementation(libs.androidx.activity.compose) + testImplementation(libs.mockk) // orbit-test 가 전이로 가져오지만, 직접 쓰는 API 라 명시한다. diff --git a/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt b/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt index 23c05368..3df205dc 100644 --- a/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt +++ b/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt @@ -1,6 +1,7 @@ package com.gamss.android.feature.home import android.widget.Toast +import androidx.activity.compose.BackHandler import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -9,11 +10,16 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.exclude import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.ime +import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.getValue @@ -78,10 +84,15 @@ fun HomeScreen( onInputChange = viewModel::onInputChange, onSubmit = viewModel::onSubmit, onEmotionPickerToggle = viewModel::onEmotionPickerToggle, + onEmotionPickerDismiss = viewModel::onEmotionPickerDismiss, onEmotionToggle = viewModel::onEmotionToggle, ) } + // Popup 은 별도 창이라 뒤로가기를 받지 못한다. 화면 쪽에서 직접 닫아 준다. + // BackHandler 는 dispatcher 소유자를 요구해 프리뷰에서 터지므로 HomeContent 밖에 둔다. + BackHandler(enabled = state.isEmotionPickerExpanded, onBack = actions.onEmotionPickerDismiss) + HomeContent(state = state, actions = actions, modifier = modifier) } @@ -91,6 +102,7 @@ private data class HomeActions( val onInputChange: (String) -> Unit, val onSubmit: () -> Unit, val onEmotionPickerToggle: () -> Unit, + val onEmotionPickerDismiss: () -> Unit, val onEmotionToggle: (EmotionCharacter) -> Unit, ) @@ -100,7 +112,13 @@ private fun HomeContent( actions: HomeActions, modifier: Modifier = Modifier, ) { - Box(modifier = modifier.fillMaxSize().dismissKeyboardOnTapOutside()) { + Box( + modifier = modifier + .fillMaxSize() + // 상위 Scaffold 가 내비게이션 바를 이미 소비했다. 남은 키보드 높이만 피한다. + .windowInsetsPadding(WindowInsets.ime.exclude(WindowInsets.navigationBars)) + .dismissOnTapOutside(actions.onEmotionPickerDismiss), + ) { HomeDecorations() Column(modifier = Modifier.fillMaxSize()) { @@ -175,7 +193,7 @@ private fun HomeInputSection( Popup( alignment = Alignment.TopEnd, offset = pickerOffset, - onDismissRequest = actions.onEmotionPickerToggle, + onDismissRequest = actions.onEmotionPickerDismiss, ) { GamssCharacterPicker(items = pickerItems, onToggle = actions.onEmotionToggle) } @@ -183,12 +201,20 @@ private fun HomeInputSection( } } -/** 입력창 바깥을 누르면 포커스를 내려놓아야 collapsed 로 돌아가고 키보드도 닫힌다. */ +/** + * 입력창 바깥을 누르면 포커스를 내려놓아야 collapsed 로 돌아가고 키보드도 닫힌다. 캐릭터 패널은 + * 포커스와 무관한 별도 창이라 [onDismiss] 로 같이 닫는다. + */ @Composable -private fun Modifier.dismissKeyboardOnTapOutside(): Modifier { +private fun Modifier.dismissOnTapOutside(onDismiss: () -> Unit): Modifier { val focusManager = LocalFocusManager.current // clickable 을 쓰면 레이블 없는 클릭 노드가 화면 전체 크기로 시맨틱 트리에 들어간다. - return pointerInput(Unit) { detectTapGestures { focusManager.clearFocus() } } + return pointerInput(onDismiss) { + detectTapGestures { + focusManager.clearFocus() + onDismiss() + } + } } private fun Set.toPickerItems(): List> = @@ -311,5 +337,6 @@ private fun previewActions() = HomeActions( onInputChange = {}, onSubmit = {}, onEmotionPickerToggle = {}, + onEmotionPickerDismiss = {}, onEmotionToggle = {}, ) diff --git a/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt b/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt index 529d1140..d4f23ba2 100644 --- a/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt +++ b/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt @@ -54,6 +54,11 @@ class HomeViewModel @Inject constructor( reduce { state.copy(isEmotionPickerExpanded = !state.isEmotionPickerExpanded) } } + // 닫기는 토글과 나눠 둔다. 바깥 탭과 Popup 의 dismiss 가 겹쳐 들어와도 다시 열리면 안 된다. + fun onEmotionPickerDismiss() = intent { + reduce { state.copy(isEmotionPickerExpanded = false) } + } + fun onEmotionToggle(character: EmotionCharacter) = intent { // 검사와 갱신을 한 reduce 안에서 끝낸다. 연타로 마지막 하나까지 빠지면 서버가 거부한다. var blocked = false diff --git a/feature/home/src/test/java/com/gamss/android/feature/home/HomeViewModelTest.kt b/feature/home/src/test/java/com/gamss/android/feature/home/HomeViewModelTest.kt index 8c393b50..d34705d2 100644 --- a/feature/home/src/test/java/com/gamss/android/feature/home/HomeViewModelTest.kt +++ b/feature/home/src/test/java/com/gamss/android/feature/home/HomeViewModelTest.kt @@ -119,6 +119,21 @@ class HomeViewModelTest { } } + @Test + fun `감정 목록 닫기는 여러 번 들어와도 다시 열리지 않는다`() = runTest { + viewModel().test(this) { + containerHost.onEmotionPickerToggle() + expectState { copy(isEmotionPickerExpanded = true) } + + // 바깥 탭과 Popup dismiss 가 함께 들어오는 경우다. + containerHost.onEmotionPickerDismiss() + expectState { copy(isEmotionPickerExpanded = false) } + + containerHost.onEmotionPickerDismiss() + expectNoItems() + } + } + @Test fun `보내면 감정 목록이 닫힌다`() = runTest { viewModel().test(this) { From 48dba6e36fdcb2ab865542aeab993e7d03c1f005 Mon Sep 17 00:00:00 2001 From: soyeonLee <109227292+soyeonLee126@users.noreply.github.com> Date: Tue, 18 Aug 2026 17:14:05 +0900 Subject: [PATCH 10/20] =?UTF-8?q?fix:=20=EC=9E=85=EB=A0=A5=EC=B0=BD=20?= =?UTF-8?q?=EC=A4=84=20=EB=86=92=EC=9D=B4=EB=A5=BC=20=EA=B8=80=EA=BC=B4=20?= =?UTF-8?q?=EB=B0=B0=EC=9C=A8=EC=97=90=20=EB=A7=9E=EC=B6=B0=20=EA=B3=84?= =?UTF-8?q?=EC=82=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 줄 높이를 20dp 상수로 굳혀 두면 시스템 글꼴 배율을 올렸을 때 마지막 줄이 잘린다. sp 인 typography 값을 그대로 dp 로 환산해 쓴다. --- .../designsystem/component/GamssInputBar.kt | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt index a195a35e..4ee8354b 100644 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt @@ -24,8 +24,10 @@ import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.layout.Layout +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.constrainWidth @@ -53,7 +55,6 @@ private val ControlsTouchHeight = 48.dp private val SendButtonTouchSize = 48.dp private val ControlsTouchOverhang = (ControlsTouchHeight - ControlsRowHeight) / 2 private const val INPUT_MAX_LINES = 6 -private val InputLineHeight = 20.dp private const val HEIGHT_ANIMATION_DURATION_MS = 350 @@ -78,6 +79,10 @@ fun GamssInputBar( var lineCount by remember { mutableIntStateOf(1) } var isExpanded by remember { mutableStateOf(false) } + // 줄 높이는 sp 라 시스템 글꼴 배율을 따라간다. dp 상수로 굳히면 배율을 올렸을 때 마지막 줄이 잘린다. + val textStyle = GamssTheme.typography.body4Medium + val lineHeight = with(LocalDensity.current) { textStyle.lineHeight.toDp() } + val baseHeight = if (isExpanded) ExpandedInputBarHeight else CollapsedInputBarHeight // 포커스로 기준 높이가 바뀔 때만 애니메이션한다. 줄이 늘어난 만큼은 곧바로 반영해야 // 방금 친 줄이 애니메이션이 끝날 때까지 잘려 보이지 않는다. @@ -89,7 +94,7 @@ fun GamssInputBar( val textAreaHeight = animatedBaseHeight - InputBarTopPadding - InputBarBottomPadding - ControlsRowHeight val barHeight = - animatedBaseHeight + (InputLineHeight * lineCount - textAreaHeight).coerceAtLeast(0.dp) + animatedBaseHeight + (lineHeight * lineCount - textAreaHeight).coerceAtLeast(0.dp) Layout( modifier = modifier @@ -103,6 +108,7 @@ fun GamssInputBar( onValueChange = onValueChange, enabled = enabled, placeholder = placeholder, + textStyle = textStyle, canSubmit = canSubmit, onSubmit = onSend, onFocusChanged = { isExpanded = it }, @@ -148,13 +154,14 @@ private fun InputTextField( onValueChange: (String) -> Unit, enabled: Boolean, placeholder: String, + textStyle: TextStyle, canSubmit: Boolean, onSubmit: () -> Unit, onFocusChanged: (Boolean) -> Unit, onLineCountChanged: (Int) -> Unit, ) { // 입력바 SVG는 라이트 배경이므로 시스템 다크 모드와 무관하게 전경을 검정으로 고정한다. - val textStyle = GamssTheme.typography.body4Medium.copy(color = GamssTheme.colors.black) + val filledStyle = textStyle.copy(color = GamssTheme.colors.black) BasicTextField( value = value, onValueChange = onValueChange, @@ -164,7 +171,7 @@ private fun InputTextField( minLines = 1, maxLines = INPUT_MAX_LINES, onTextLayout = { onLineCountChanged(it.lineCount.coerceIn(1, INPUT_MAX_LINES)) }, - textStyle = textStyle, + textStyle = filledStyle, cursorBrush = SolidColor(GamssTheme.colors.black), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Send), keyboardActions = KeyboardActions(onSend = { if (canSubmit) onSubmit() }), @@ -177,7 +184,7 @@ private fun InputTextField( if (value.isEmpty()) { GamssText( text = placeholder, - style = GamssTheme.typography.body4Medium, + style = textStyle, color = GamssTheme.colors.gray500, ) } From f7f679fc1323d5b3457d6bc1959fad187d86f93d Mon Sep 17 00:00:00 2001 From: soyeonLee <109227292+soyeonLee126@users.noreply.github.com> Date: Tue, 18 Aug 2026 17:14:13 +0900 Subject: [PATCH 11/20] =?UTF-8?q?fix:=20=ED=86=A0=EA=B8=80=20=EB=9D=BC?= =?UTF-8?q?=EB=B2=A8=20=EB=B0=80=EB=A6=BC=EC=9D=84=20=EB=B0=94=EB=A1=9C?= =?UTF-8?q?=EC=9E=A1=EA=B3=A0=209-slice=20=ED=94=84=EB=A0=88=EC=9E=84=20?= =?UTF-8?q?=ED=95=A0=EB=8B=B9=EC=9D=84=20=EC=97=86=EC=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 가로 터치 영역은 라벨과 셰브론 폭만으로 이미 권장치를 넘어, 여백은 라벨만 안쪽으로 밀었다. 9-slice 칸 목록은 그리기마다 새로 만들지 않게 상수로 올린다. --- .../main/kotlin/com/gamss/android/app/main/MainScreen.kt | 2 +- .../core/designsystem/component/GamssDisclosureToggle.kt | 6 ++---- .../core/designsystem/component/GamssSketchyBox.kt | 8 ++++---- core/designsystem/src/main/res/drawable/bg_input_box.xml | 3 ++- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/app/src/main/kotlin/com/gamss/android/app/main/MainScreen.kt b/app/src/main/kotlin/com/gamss/android/app/main/MainScreen.kt index 8063a072..3167bf61 100644 --- a/app/src/main/kotlin/com/gamss/android/app/main/MainScreen.kt +++ b/app/src/main/kotlin/com/gamss/android/app/main/MainScreen.kt @@ -72,7 +72,7 @@ fun MainScreen( ModelDownloadConfirmationEffect(modelDownloadPromptViewModel, snackbarHostState) - // 배경/탭바 에셋이 라이트 전용이라 다크 시안이 나올 때까지 셸은 라이트로 고정한다. + // 종이 배경과 손그림 에셋이 라이트 전용이라 다크 시안이 나올 때까지 셸은 라이트로 고정한다. GamssTheme(darkTheme = false) { // 종이 보드가 상태바와 탭바 뒤까지 이어져야 해서 Scaffold 바깥에 깐다. GamssPaperBackground { diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssDisclosureToggle.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssDisclosureToggle.kt index a5e6019a..449a1b8e 100644 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssDisclosureToggle.kt +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssDisclosureToggle.kt @@ -3,7 +3,6 @@ package com.gamss.android.core.designsystem.component import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.Icon import androidx.compose.runtime.Composable @@ -18,7 +17,6 @@ import com.gamss.android.core.designsystem.modifier.noRippleClickable import com.gamss.android.core.designsystem.theme.GamssTheme private val ToggleMinTouchHeight = 48.dp -private val ToggleHitPadding = 4.dp private val LabelChevronGap = 4.dp private val ChevronSize = 16.dp private const val CHEVRON_ROTATION = 90f @@ -32,6 +30,7 @@ private const val CHEVRON_CENTER_Y = 12.03f / 24f * 라벨과 펼침 상태를 가리키는 셰브론을 나란히 둔 토글. 아래위 셰브론 에셋이 없어 오른쪽 셰브론을 돌려 쓴다. * * 터치 영역은 [ToggleMinTouchHeight] 까지 넓히지만, 높이가 고정 측정되는 곳에 놓이면 그만큼만 잡힌다. + * 가로는 라벨과 셰브론 폭만으로 이미 권장치를 넘어, 여백을 더 주면 라벨만 안쪽으로 밀린다. */ @Composable fun GamssDisclosureToggle( @@ -43,8 +42,7 @@ fun GamssDisclosureToggle( Row( modifier = modifier .heightIn(min = ToggleMinTouchHeight) - .noRippleClickable(role = Role.DropdownList, onClick = onClick) - .padding(horizontal = ToggleHitPadding), + .noRippleClickable(role = Role.DropdownList, onClick = onClick), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(LabelChevronGap), ) { diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBox.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBox.kt index 00cd7da6..b5ccdeb9 100644 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBox.kt +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBox.kt @@ -24,6 +24,9 @@ private val ArtCorner = 12.dp // 아트의 모서리가 둥글어, 배경을 직각으로 칠하면 흰 모서리가 선 밖으로 삐져나온다. private val BackgroundCorner = 4.dp +// 링 안쪽(가운데 칸)은 비어 있어 그릴 것이 없다. 그리기마다 새로 만들지 않게 한 번만 계산한다. +private val NineSliceCells = (0..2).flatMap { col -> (0..2).map { row -> col to row } } - (1 to 1) + /** * 손그림 사각 테두리를 9-slice 로 그린다. 모서리는 원본 크기로 두고 직선 구간만 늘리므로, * 어떤 크기에 올려도 선 굵기가 변하지 않는다. 통째로 늘리면 늘린 방향의 선만 두꺼워진다. @@ -48,10 +51,7 @@ private fun DrawScope.drawNineSlice(art: Painter) { val dstX = floatArrayOf(0f, corner, size.width - corner, size.width) val dstY = floatArrayOf(0f, corner, size.height - corner, size.height) - // 링 안쪽(가운데 칸)은 비어 있어 그릴 것이 없다. - val slices = (0..2).flatMap { col -> (0..2).map { row -> col to row } } - (1 to 1) - - slices.forEach { (col, row) -> + NineSliceCells.forEach { (col, row) -> val scaleX = sliceScale(srcX, dstX, col) val scaleY = sliceScale(srcY, dstY, row) if (scaleX <= 0f || scaleY <= 0f) return@forEach diff --git a/core/designsystem/src/main/res/drawable/bg_input_box.xml b/core/designsystem/src/main/res/drawable/bg_input_box.xml index 60fd627d..76c2a767 100644 --- a/core/designsystem/src/main/res/drawable/bg_input_box.xml +++ b/core/designsystem/src/main/res/drawable/bg_input_box.xml @@ -1,6 +1,7 @@ + pathData 한 개는 aapt2 의 32KB 문자열 한도 아래여야 한다. 넘기면 STRING_TOO_LARGE 로 조용히 + 잘리므로, 에셋을 갈아 끼울 때 크기를 확인하고 필요하면 서브패스 단위로 나눠 담는다. --> Date: Tue, 18 Aug 2026 18:07:23 +0900 Subject: [PATCH 12/20] =?UTF-8?q?design:=20=EC=9E=85=EB=A0=A5=EC=B0=BD=20?= =?UTF-8?q?=ED=85=8C=EB=91=90=EB=A6=AC=EB=A5=BC=20Figma=20=EC=9B=90?= =?UTF-8?q?=EB=B3=B8=20=EC=95=84=ED=8A=B8=EB=A1=9C=20=EB=90=98=EB=8F=8C?= =?UTF-8?q?=EB=A6=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 99x38 버튼 아트를 9-slice 로 늘려 쓰다 보니 가로 4.6배, 세로 5.4~8.9배로 손그림 떨림이 펴져서 시안보다 매끈해 보였다. 368x114 원본은 가로 0.99배, 세로 0.84~1.39배로 거의 1:1 이다. pathData 가 80KB 라 aapt2 의 32767 바이트 한도에 걸리므로 서브패스 경계에서 4개로 나눠 담는다. --- .../src/main/res/drawable/bg_input_box.xml | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/core/designsystem/src/main/res/drawable/bg_input_box.xml b/core/designsystem/src/main/res/drawable/bg_input_box.xml index 76c2a767..f01910e7 100644 --- a/core/designsystem/src/main/res/drawable/bg_input_box.xml +++ b/core/designsystem/src/main/res/drawable/bg_input_box.xml @@ -1,13 +1,22 @@ - + + android:width="368dp" + android:height="114dp" + android:viewportWidth="368" + android:viewportHeight="114"> + android:fillColor="#FF1E1F22" + android:pathData="M65.5618 1.11874L65.4583 1.86157L65.5618 1.11874ZM115.392 1.03866L115.366 0.289122L115.392 1.03866ZM151.632 1.15118L151.648 0.401344L151.632 1.15118ZM178.812 1.06523L178.731 0.319607L178.812 1.06523ZM185.607 0.997367L185.619 0.247475L185.607 0.997367ZM212.787 1.08829L212.836 1.83665V1.83665L212.787 1.08829ZM230.907 0.897119L230.827 1.64283L230.907 0.897119ZM267.147 0.983911L267.108 0.234923L267.147 0.983911ZM296.592 1.06627L296.577 0.316411L296.592 1.06627ZM346.422 1.08222L346.409 1.83212L346.422 1.08222ZM366.308 1.3876L366.801 0.822839L366.308 1.3876ZM366.897 18.9287L367.645 18.8775L366.897 18.9287ZM366.851 23.4587L366.102 23.4162L366.851 23.4587ZM367.237 34.7837L366.488 34.8005L367.237 34.7837ZM367.18 39.3137L366.431 39.363L367.18 39.3137ZM367.14 43.8437L367.889 43.814L367.14 43.8437ZM367.124 50.6387L367.874 50.6755L367.124 50.6387ZM367.008 52.9037L367.757 52.9439L367.008 52.9037ZM366.842 55.1687L367.589 55.2378L366.842 55.1687ZM366.778 57.4337L367.527 57.4072L366.778 57.4337ZM366.85 59.6987L367.6 59.6772L366.85 59.6987ZM366.997 82.3487L367.746 82.3825L366.997 82.3487ZM367.064 95.9387L366.324 95.8186L367.064 95.9387ZM366.915 102.734L367.665 102.757L366.915 102.734ZM366.889 109.529L367.638 109.512L366.889 109.529ZM365.898 112.834L365.833 113.581L365.898 112.834ZM363.697 113.009L363.524 112.279L363.697 113.009ZM352.372 113.136L352.401 113.885L352.372 113.136ZM350.107 112.959L349.963 113.695L350.107 112.959ZM347.842 112.91L347.729 112.169L347.842 112.91ZM345.577 112.914L345.687 112.172L345.577 112.914ZM322.927 113.212L322.963 112.463L322.927 113.212ZM320.662 113.043L320.737 112.297L320.662 113.043ZM313.867 113.18L313.882 112.43L313.867 113.18ZM309.337 113.093L309.323 112.344L309.337 113.093ZM300.277 113.016L300.277 113.766L300.277 113.016ZM293.482 112.846L293.482 113.596L293.482 112.846ZM291.217 112.868L291.232 113.618L291.217 112.868ZM284.422 113.221L284.459 112.472L284.422 113.221ZM279.892 112.863L279.827 113.611L279.892 112.863ZM275.362 113.109L275.441 112.364L275.362 113.109ZM270.832 113.031L270.919 113.776L270.832 113.031ZM266.302 112.981L266.181 113.722L266.302 112.981ZM257.242 112.953L257.138 112.21L257.242 112.953ZM252.712 113.152L252.802 113.896L252.712 113.152ZM250.447 113.163L250.365 113.909L250.447 113.163ZM243.652 112.888L243.723 112.142L243.652 112.888ZM236.857 113.052L236.744 113.793L236.857 113.052ZM230.062 112.96L229.988 113.706L230.062 112.96ZM227.797 112.981L227.885 113.726L227.797 112.981ZM221.002 112.973L221.151 112.238L221.002 112.973ZM211.942 113.168L211.949 112.418L211.942 113.168ZM207.412 112.863L207.436 113.613L207.412 112.863ZM189.292 113.125L189.402 112.383L189.292 113.125ZM182.497 112.955L182.536 112.206L182.497 112.955ZM180.232 112.886L180.238 112.136L180.232 112.886ZM173.437 113.033L173.357 113.779L173.437 113.033ZM166.642 113.083L166.728 112.338L166.642 113.083ZM162.112 113.092L161.99 112.352L162.112 113.092ZM159.847 113.064L159.707 113.801L159.847 113.064ZM146.257 113.017L146.206 112.268L146.257 113.017ZM143.992 113.021L143.944 113.769L143.992 113.021ZM141.727 112.843L141.657 113.59L141.727 112.843ZM137.197 113.016L137.365 113.747L137.197 113.016ZM134.932 113.211L134.975 112.462L134.932 113.211ZM132.667 113.078L132.623 113.827L132.667 113.078ZM112.282 112.836L112.324 112.087L112.282 112.836ZM107.752 113.188L107.775 112.438L107.752 113.188ZM105.487 113.068L105.543 112.32L105.487 113.068ZM103.222 113.077L103.284 113.824L103.222 113.077ZM100.957 113.076L101.019 112.329L100.957 113.076ZM96.4268 112.83L96.3645 112.082L96.4268 112.83ZM89.6318 112.969L89.7631 112.23L89.6318 112.969ZM82.8368 112.933L82.8992 112.186L82.8368 112.933ZM78.3068 113.091L78.3974 113.836L78.3068 113.091ZM73.7768 112.872L73.765 113.622L73.7768 112.872ZM69.2468 112.842L69.2731 112.093L69.2468 112.842ZM62.4518 113.019L62.4727 112.269L62.4518 113.019ZM60.1868 113.124L60.2767 113.869L60.1868 113.124ZM55.6568 112.928L55.6586 112.178L55.6568 112.928ZM51.1268 113.249L51.1498 112.499L51.1268 113.249ZM39.8018 112.936L39.8873 113.681L39.8018 112.936ZM30.7418 113.124L30.7274 113.874L30.7418 113.124ZM21.6818 113.249L21.6889 112.499L21.6818 113.249ZM17.1518 112.766L17.1369 113.515L17.1518 112.766ZM14.8868 112.914L14.7748 112.173L14.8868 112.914ZM5.82683 112.954L5.8712 113.703L5.82683 112.954ZM3.56183 112.877L3.65634 112.133L3.56183 112.877ZM2.14178 0.953528L2.17994 1.70256C2.88973 1.66639 3.59972 1.69424 4.31045 1.78628L4.40678 1.0425L4.50311 0.298708C3.70384 0.195194 2.90383 0.163727 2.10361 0.204499L2.14178 0.953528ZM4.40678 1.0425L4.31045 1.78628C5.12687 1.89202 5.94411 1.89613 6.76075 1.79856L6.67178 1.05386L6.5828 0.309153C5.88945 0.391995 5.19669 0.388534 4.50311 0.298708L4.40678 1.0425ZM6.67178 1.05386L6.76075 1.79856C7.49863 1.7104 8.23635 1.64804 8.97392 1.61147L8.93678 0.862388L8.89963 0.113309C8.12721 0.151611 7.35492 0.216901 6.5828 0.309153L6.67178 1.05386ZM8.93678 0.862388L8.97392 1.61147C9.6926 1.57583 10.4113 1.59237 11.1304 1.66116L11.2018 0.914567L11.2732 0.167975C10.4822 0.0923107 9.69095 0.074069 8.89963 0.113309L8.93678 0.862388ZM11.2018 0.914567L11.1304 1.66116C11.9079 1.73554 12.6857 1.7744 13.4636 1.77771L13.4668 1.02772L13.47 0.277725C12.7379 0.274612 12.0056 0.238039 11.2732 0.167975L11.2018 0.914567ZM13.4668 1.02772L13.4636 1.77771C14.2418 1.78102 15.0199 1.7483 15.7978 1.67958L15.7318 0.932487L15.6658 0.185396C14.9337 0.250069 14.2018 0.280836 13.47 0.277725L13.4668 1.02772ZM15.7318 0.932487L15.7978 1.67958C16.5223 1.61557 17.2466 1.59615 17.9708 1.62126L17.9968 0.871715L18.0228 0.122166C17.237 0.0949146 16.4513 0.116005 15.6658 0.185396L15.7318 0.932487ZM17.9968 0.871715L17.9708 1.62126C18.698 1.64649 19.4257 1.71204 20.1537 1.81803L20.2618 1.07585L20.3698 0.333674C19.5879 0.219848 18.8055 0.149311 18.0228 0.122166L17.9968 0.871715ZM20.2618 1.07585L20.1537 1.81803C20.9949 1.94048 21.8367 1.91644 22.6754 1.74689L22.5268 1.01177L22.3782 0.276637C21.7068 0.41235 21.0387 0.43104 20.3698 0.333674L20.2618 1.07585ZM22.5268 1.01177L22.6754 1.74689C23.3667 1.60715 24.0553 1.56021 24.743 1.60501L24.7918 0.856599L24.8405 0.108185C24.0182 0.0546164 23.1969 0.111123 22.3782 0.276637L22.5268 1.01177ZM24.7918 0.856599L24.743 1.60501C25.5343 1.65656 26.3257 1.6505 27.1169 1.58684L27.0568 0.839255L26.9966 0.0916711C26.2778 0.149507 25.5592 0.155005 24.8405 0.108185L24.7918 0.856599ZM27.0568 0.839255L27.1169 1.58684C27.8143 1.53073 28.5117 1.55495 29.2104 1.65986L29.3218 0.918173L29.4331 0.176487C28.6218 0.0546733 27.8093 0.0262833 26.9966 0.0916711L27.0568 0.839255ZM29.3218 0.918173L29.2104 1.65986C30.0118 1.78018 30.8142 1.8243 31.617 1.7919L31.5868 1.04251L31.5565 0.293121C30.8494 0.321659 30.1418 0.282886 29.4331 0.176487L29.3218 0.918173ZM31.5868 1.04251L31.617 1.7919C32.3718 1.76144 33.1267 1.73125 33.8815 1.70133L33.8518 0.951922L33.8221 0.20251C33.0669 0.232441 32.3117 0.262645 31.5565 0.293121L31.5868 1.04251ZM33.8518 0.951922L33.8815 1.70133C34.6457 1.67105 35.4098 1.62671 36.1739 1.56832L36.1168 0.820501L36.0596 0.0726808C35.3138 0.129674 34.5679 0.17295 33.8221 0.20251L33.8518 0.951922ZM36.1168 0.820501L36.1739 1.56832C36.8549 1.51629 37.5369 1.56411 38.2223 1.71324L38.3818 0.980384L38.5412 0.247531C37.7166 0.0681036 36.8887 0.00933468 36.0596 0.0726808L36.1168 0.820501ZM38.3818 0.980384L38.2223 1.71324C39.082 1.90029 39.9453 1.901 40.8051 1.71531L40.6468 0.982212L40.4884 0.249115C39.8382 0.389547 39.1916 0.389037 38.5412 0.247531L38.3818 0.980384ZM40.6468 0.982212L40.8051 1.71531C41.4811 1.56932 42.1534 1.53604 42.8254 1.61399L42.9118 0.868982L42.9982 0.123976C42.1602 0.0267749 41.3225 0.068983 40.4884 0.249115L40.6468 0.982212ZM42.9118 0.868982L42.8254 1.61399C43.6253 1.70678 44.4259 1.72663 45.2265 1.67343L45.1768 0.925079L45.127 0.17673C44.4176 0.223874 43.7082 0.206331 42.9982 0.123976L42.9118 0.868982ZM45.1768 0.925079L45.2265 1.67343C45.9206 1.6273 46.6152 1.66525 47.3117 1.78792L47.4418 1.04929L47.5719 0.310661C46.7583 0.167367 45.943 0.122509 45.127 0.17673L45.1768 0.925079ZM47.4418 1.04929L47.3117 1.78792C48.1662 1.93844 49.0222 1.91578 49.8737 1.72134L49.7068 0.990163L49.5398 0.258983C48.8813 0.409345 48.2273 0.426107 47.5719 0.310661L47.4418 1.04929ZM49.7068 0.990163L49.8737 1.72134C50.5599 1.56465 51.2425 1.50903 51.9237 1.5528L51.9718 0.804345L52.0199 0.0558892C51.191 0.00262249 50.3636 0.0708739 49.5398 0.258983L49.7068 0.990163ZM51.9718 0.804345L51.9237 1.5528C52.6881 1.60193 53.4525 1.6366 54.2169 1.65681L54.2368 0.907074L54.2566 0.157336C53.511 0.137621 52.7655 0.103806 52.0199 0.0558892L51.9718 0.804345ZM54.2368 0.907074L54.2169 1.65681C54.9837 1.67709 55.7504 1.67938 56.5171 1.66368L56.5018 0.913842L56.4864 0.163999C55.7432 0.179212 54.9999 0.176991 54.2566 0.157336L54.2368 0.907074ZM56.5018 0.913842L56.5171 1.66368C57.2286 1.64912 57.9405 1.6962 58.6534 1.8052L58.7668 1.06382L58.8801 0.322433C58.083 0.200561 57.285 0.147654 56.4864 0.163999L56.5018 0.913842ZM58.7668 1.06382L58.6534 1.8052C59.4733 1.93056 60.2946 1.94724 61.1156 1.85493L61.0318 1.10962L60.948 0.36432C60.2589 0.441803 59.5702 0.427945 58.8801 0.322433L58.7668 1.06382ZM61.0318 1.10962L61.1156 1.85493C61.8453 1.77287 62.5746 1.72845 63.3038 1.72159L63.2968 0.971621L63.2897 0.221654C62.5089 0.228998 61.7283 0.276572 60.948 0.36432L61.0318 1.10962ZM63.2968 0.971621L63.3038 1.72159C64.0215 1.71484 64.7396 1.76144 65.4583 1.86157L65.5618 1.11874L65.6653 0.375917C64.874 0.265681 64.0821 0.214201 63.2897 0.221654L63.2968 0.971621ZM65.5618 1.11874L65.4583 1.86157C66.2936 1.97794 67.1296 1.95873 67.9632 1.80457L67.8268 1.06708L67.6904 0.329582C67.014 0.454679 66.34 0.469911 65.6653 0.375917L65.5618 1.11874ZM67.8268 1.06708L67.9632 1.80457C68.6592 1.67584 69.3533 1.63275 70.0466 1.67456L70.0918 0.92592L70.1369 0.17728C69.3203 0.128038 68.5043 0.179057 67.6904 0.329582L67.8268 1.06708ZM70.0918 0.92592L70.0466 1.67456C70.7992 1.71994 71.5517 1.76904 72.3042 1.82189L72.3568 1.07373L72.4093 0.325574C71.6518 0.272384 70.8944 0.222952 70.1369 0.17728L70.0918 0.92592ZM72.3568 1.07373L72.3042 1.82189C73.1016 1.87788 73.899 1.86622 74.696 1.78697L74.6218 1.04065L74.5476 0.294328C73.8346 0.365223 73.122 0.375619 72.4093 0.325574L72.3568 1.07373ZM74.6218 1.04065L74.696 1.78697C75.4285 1.71413 76.1608 1.67468 76.893 1.66859L76.8868 0.918618L76.8805 0.168644C76.1027 0.175113 75.325 0.217019 74.5476 0.294328L74.6218 1.04065ZM76.8868 0.918618L76.893 1.66859C77.604 1.66268 78.3156 1.71901 79.0283 1.83794L79.1518 1.09817L79.2752 0.358394C78.478 0.225365 77.6796 0.161998 76.8805 0.168644L76.8868 0.918618ZM79.1518 1.09817L79.0283 1.83794C79.8794 1.97995 80.7317 1.95591 81.5797 1.76714L81.4168 1.03506L81.2538 0.302975C80.5919 0.45031 79.9341 0.46834 79.2752 0.358394L79.1518 1.09817ZM81.4168 1.03506L81.5797 1.76714C82.2326 1.62184 82.8813 1.61549 83.5328 1.7475L83.6818 1.01243L83.8307 0.277367C82.9722 0.103442 82.111 0.112184 81.2538 0.302975L81.4168 1.03506ZM83.6818 1.01243L83.5328 1.7475C84.3435 1.91174 85.1564 1.98144 85.9705 1.95571L85.9468 1.20608L85.9231 0.456459C85.2271 0.478456 84.53 0.419056 83.8307 0.277367L83.6818 1.01243ZM85.9468 1.20608L85.9705 1.95571C86.7646 1.93061 87.5579 1.84355 88.3501 1.69486L88.2118 0.957728L88.0734 0.2206C87.3556 0.355325 86.6389 0.433832 85.9231 0.456459L85.9468 1.20608ZM88.2118 0.957728L88.3501 1.69486C89.0592 1.56176 89.7668 1.49702 90.4735 1.50011L90.4768 0.750116L90.48 0.00012362C89.6767 -0.00338709 88.8743 0.0702813 88.0734 0.2206L88.2118 0.957728ZM90.4768 0.750116L90.4735 1.50011C91.1844 1.50322 91.8961 1.56865 92.6091 1.69682L92.7418 0.958648L92.8745 0.22048C92.0774 0.077203 91.2791 0.00361586 90.48 0.00012362L90.4768 0.750116ZM92.7418 0.958648L92.6091 1.69682C93.4241 1.84333 94.2411 1.88861 95.0586 1.83196L95.0068 1.08375L94.9549 0.335546C94.2625 0.383532 93.5694 0.34541 92.8745 0.22048L92.7418 0.958648ZM95.0068 1.08375L95.0586 1.83196C95.798 1.78072 96.5374 1.75267 97.2767 1.74782L97.2718 0.997835L97.2669 0.247852C96.4962 0.252913 95.7255 0.282147 94.9549 0.335546L95.0068 1.08375ZM97.2718 0.997835L97.2767 1.74782C98.0217 1.74293 98.7667 1.75297 99.5116 1.77794L99.5368 1.02836L99.5619 0.278782C98.7969 0.253137 98.0319 0.242827 97.2669 0.247852L97.2718 0.997835ZM99.5368 1.02836L99.5116 1.77794C100.269 1.80331 101.026 1.82576 101.782 1.84527L101.802 1.09552L101.821 0.345769C101.068 0.326355 100.315 0.304026 99.5619 0.278782L99.5368 1.02836ZM101.802 1.09552L101.782 1.84527C102.534 1.86463 103.285 1.88993 104.036 1.92115L104.067 1.17179L104.098 0.422441C103.339 0.390892 102.58 0.365335 101.821 0.345769L101.802 1.09552ZM104.067 1.17179L104.036 1.92115C104.842 1.95468 105.648 1.90521 106.453 1.77319L106.332 1.03308L106.21 0.292975C105.505 0.408669 104.801 0.451678 104.098 0.422441L104.067 1.17179ZM106.332 1.03308L106.453 1.77319C107.154 1.65825 107.853 1.6221 108.552 1.66425L108.597 0.915613L108.642 0.166974C107.831 0.11803 107.02 0.160192 106.21 0.292975L106.332 1.03308ZM108.597 0.915613L108.552 1.66425C109.347 1.71221 110.142 1.69659 110.936 1.61747L110.862 0.871161L110.787 0.124852C110.072 0.196092 109.357 0.21011 108.642 0.166974L108.597 0.915613ZM110.862 0.871161L110.936 1.61747C111.631 1.54826 112.326 1.56268 113.022 1.66097L113.127 0.918342L113.232 0.175713C112.418 0.0607335 111.602 0.0436993 110.787 0.124852L110.862 0.871161ZM113.127 0.918342L113.022 1.66097C113.82 1.77368 114.619 1.81617 115.418 1.7882L115.392 1.03866L115.366 0.289122C114.655 0.313996 113.944 0.276273 113.232 0.175713L113.127 0.918342ZM115.392 1.03866L115.418 1.7882C116.165 1.76207 116.912 1.74815 117.658 1.74644L117.657 0.996437L117.655 0.246439C116.892 0.248187 116.129 0.262414 115.366 0.289122L115.392 1.03866ZM117.657 0.996437L117.658 1.74644C118.428 1.74467 119.198 1.72053 119.967 1.674L119.922 0.925365L119.877 0.176732C119.136 0.22151 118.395 0.244744 117.655 0.246439L117.657 0.996437ZM119.922 0.925365L119.967 1.674C120.701 1.62962 121.435 1.61625 122.169 1.63388L122.187 0.884093L122.205 0.134309C121.429 0.115663 120.653 0.129807 119.877 0.176732L119.922 0.925365ZM122.187 0.884093L122.169 1.63388C122.923 1.65201 123.678 1.67064 124.433 1.68978L124.452 0.940019L124.471 0.19026C123.715 0.171105 122.96 0.152455 122.205 0.134309L122.187 0.884093ZM124.452 0.940019L124.433 1.68978C125.19 1.70899 125.948 1.72412 126.706 1.73518L126.717 0.985256L126.728 0.235335C125.975 0.224363 125.223 0.209338 124.471 0.19026L124.452 0.940019ZM126.717 0.985256L126.706 1.73518C127.475 1.7464 128.244 1.73558 129.014 1.70272L128.982 0.953404L128.95 0.204087C128.209 0.235723 127.468 0.246138 126.728 0.235335L126.717 0.985256ZM128.982 0.953404L129.014 1.70272C129.768 1.6705 130.523 1.6392 131.277 1.60883L131.247 0.859436L131.217 0.110043C130.461 0.140467 129.705 0.171815 128.95 0.204087L128.982 0.953404ZM131.247 0.859436L131.277 1.60883C132.02 1.57892 132.763 1.56702 133.506 1.57315L133.512 0.823174L133.518 0.0731995C132.751 0.0668743 131.984 0.0791559 131.217 0.110043L131.247 0.859436ZM133.512 0.823174L133.506 1.57315C134.257 1.57934 135.008 1.59099 135.76 1.60809L135.777 0.858286L135.794 0.10848C135.035 0.0912147 134.277 0.0794545 133.518 0.0731995L133.512 0.823174ZM135.777 0.858286L135.76 1.60809C136.485 1.6246 137.21 1.68427 137.936 1.78724L138.042 1.04467L138.147 0.302097C137.363 0.190919 136.579 0.126341 135.794 0.10848L135.777 0.858286ZM138.042 1.04467L137.936 1.78724C138.733 1.90025 139.531 1.94498 140.329 1.92121L140.307 1.17155L140.284 0.421878C139.573 0.443081 138.86 0.403232 138.147 0.302097L138.042 1.04467ZM140.307 1.17155L140.329 1.92121C141.108 1.898 141.887 1.83692 142.666 1.73802L142.572 0.993997L142.477 0.249972C141.746 0.342834 141.015 0.400116 140.284 0.421878L140.307 1.17155ZM142.572 0.993997L142.666 1.73802C143.391 1.64602 144.115 1.59932 144.838 1.59781L144.837 0.847814L144.835 0.0978153C144.049 0.0994564 143.263 0.15021 142.477 0.249972L142.572 0.993997ZM144.837 0.847814L144.838 1.59781C145.571 1.59628 146.304 1.62746 147.037 1.69137L147.102 0.9442L147.167 0.197035C146.39 0.129275 145.613 0.0961928 144.835 0.0978153L144.837 0.847814ZM147.102 0.9442L147.037 1.69137C147.804 1.75828 148.571 1.80601 149.339 1.83457L149.367 1.08509L149.395 0.335605C148.652 0.307979 147.91 0.261791 147.167 0.197035L147.102 0.9442ZM149.367 1.08509L149.339 1.83457C150.098 1.86281 150.857 1.88495 151.616 1.90101L151.632 1.15118L151.648 0.401344C150.897 0.38546 150.146 0.363547 149.395 0.335605L149.367 1.08509ZM151.632 1.15118L151.616 1.90101C152.37 1.91697 153.125 1.93362 153.88 1.95096L153.897 1.20116L153.914 0.451356C153.159 0.433993 152.403 0.417322 151.648 0.401344L151.632 1.15118ZM153.897 1.20116L153.88 1.95096C154.651 1.96869 155.422 1.96102 156.194 1.92796L156.162 1.17864L156.13 0.429333C155.391 0.460989 154.653 0.46833 153.914 0.451356L153.897 1.20116ZM156.162 1.17864L156.194 1.92796C156.934 1.89623 157.674 1.8864 158.415 1.89846L158.427 1.14856L158.439 0.398664C157.669 0.386115 156.899 0.396339 156.13 0.429333L156.162 1.17864ZM158.427 1.14856L158.415 1.89846C159.16 1.91062 159.906 1.93657 160.652 1.9763L160.692 1.22736L160.732 0.478425C159.967 0.43771 159.203 0.411123 158.439 0.398664L158.427 1.14856ZM160.692 1.22736L160.652 1.9763C161.456 2.01917 162.261 1.98238 163.064 1.86624L162.957 1.12395L162.849 0.381674C162.143 0.483853 161.437 0.516006 160.732 0.478425L160.692 1.22736ZM162.957 1.12395L163.064 1.86624C163.802 1.75959 164.539 1.67939 165.276 1.62562L165.222 0.877608L165.167 0.129595C164.394 0.185961 163.622 0.269998 162.849 0.381674L162.957 1.12395ZM165.222 0.877608L165.276 1.62562C165.999 1.57289 166.722 1.56681 167.445 1.60737L167.487 0.858545L167.529 0.109723C166.742 0.0655556 165.954 0.0721847 165.167 0.129595L165.222 0.877608ZM167.487 0.858545L167.445 1.60737C168.21 1.65031 168.976 1.67717 169.741 1.68794L169.752 0.938015L169.762 0.188089C169.018 0.177617 168.273 0.151496 167.529 0.109723L167.487 0.858545ZM169.752 0.938015L169.741 1.68794C170.504 1.69868 171.268 1.69692 172.031 1.68267L172.017 0.932801L172.003 0.182932C171.256 0.196875 170.509 0.198594 169.762 0.188089L169.752 0.938015ZM172.017 0.932801L172.031 1.68267C172.782 1.66864 173.534 1.65966 174.286 1.65574L174.282 0.90575L174.278 0.15576C173.519 0.159716 172.761 0.168773 172.003 0.182932L172.017 0.932801ZM174.282 0.90575L174.286 1.65574C175.007 1.65197 175.729 1.69602 176.452 1.788L176.547 1.04401L176.641 0.300011C175.854 0.199771 175.066 0.151648 174.278 0.15576L174.282 0.90575ZM176.547 1.04401L176.452 1.788C177.265 1.89152 178.079 1.89916 178.893 1.81085L178.812 1.06523L178.731 0.319607C178.034 0.395216 177.338 0.388718 176.641 0.300011L176.547 1.04401ZM178.812 1.06523L178.893 1.81085C179.615 1.73244 180.337 1.70213 181.058 1.71983L181.077 0.970051L181.095 0.220276C180.307 0.200944 179.519 0.234082 178.731 0.319607L178.812 1.06523ZM181.077 0.970051L181.058 1.71983C181.822 1.73854 182.585 1.74459 183.348 1.73797L183.342 0.988001L183.335 0.238029C182.589 0.244505 181.842 0.238587 181.095 0.220276L181.077 0.970051ZM183.342 0.988001L183.348 1.73797C184.097 1.73148 184.845 1.73458 185.594 1.74726L185.607 0.997367L185.619 0.247475C184.858 0.234574 184.097 0.231426 183.335 0.238029L183.342 0.988001ZM185.607 0.997367L185.594 1.74726C186.332 1.75976 187.07 1.7974 187.808 1.86017L187.872 1.11287L187.935 0.365564C187.163 0.299923 186.392 0.260556 185.619 0.247475L185.607 0.997367ZM187.872 1.11287L187.808 1.86017C188.638 1.9307 189.466 1.87831 190.291 1.70434L190.137 0.970465L189.982 0.236591C189.297 0.380899 188.616 0.423448 187.935 0.365564L187.872 1.11287ZM190.137 0.970465L190.291 1.70434C190.939 1.56777 191.584 1.57394 192.233 1.72352L192.402 0.992663L192.57 0.261808C191.709 0.0635079 190.844 0.0548776 189.982 0.236591L190.137 0.970465ZM192.402 0.992663L192.233 1.72352C193.045 1.91043 193.859 2.00015 194.675 1.99143L194.667 1.24147L194.659 0.491517C193.965 0.498933 193.269 0.422774 192.57 0.261808L192.402 0.992663ZM194.667 1.24147L194.675 1.99143C195.453 1.98312 196.231 1.93908 197.008 1.85935L196.932 1.11326L196.855 0.367171C196.123 0.44226 195.391 0.483696 194.659 0.491517L194.667 1.24147ZM196.932 1.11326L197.008 1.85935C197.759 1.78242 198.509 1.71267 199.259 1.65008L199.197 0.902679L199.134 0.155275C198.375 0.218647 197.615 0.289279 196.855 0.367171L196.932 1.11326ZM199.197 0.902679L199.259 1.65008C199.993 1.58885 200.727 1.55853 201.461 1.55912L201.462 0.809121L201.462 0.059121C200.686 0.0584991 199.91 0.0905573 199.134 0.155275L199.197 0.902679ZM201.462 0.809121L201.461 1.55912C202.22 1.55973 202.978 1.55481 203.737 1.54435L203.727 0.794424L203.716 0.0444947C202.965 0.0548478 202.214 0.0597232 201.462 0.059121L201.462 0.809121ZM203.727 0.794424L203.737 1.54435C204.439 1.53468 205.142 1.59909 205.846 1.73829L205.992 1.00251L206.137 0.266722C205.332 0.107669 204.525 0.033358 203.716 0.0444947L203.727 0.794424ZM205.992 1.00251L205.846 1.73829C206.667 1.90028 207.489 1.95101 208.313 1.88948L208.257 1.14157L208.201 0.393654C207.514 0.444959 206.827 0.402986 206.137 0.266722L205.992 1.00251ZM208.257 1.14157L208.313 1.88948C209.037 1.83533 209.762 1.82529 210.487 1.85934L210.522 1.11017L210.557 0.360998C209.772 0.324083 208.986 0.334975 208.201 0.393654L208.257 1.14157ZM210.522 1.11017L210.487 1.85934C211.27 1.89616 212.053 1.88859 212.836 1.83665L212.787 1.08829L212.737 0.339936C212.01 0.388137 211.284 0.395154 210.557 0.360998L210.522 1.11017ZM212.787 1.08829L212.836 1.83665C213.568 1.78816 214.298 1.7748 215.029 1.79655L215.052 1.04689L215.074 0.297218C214.295 0.274035 213.516 0.288279 212.737 0.339936L212.787 1.08829ZM215.052 1.04689L215.029 1.79655C215.826 1.82025 216.622 1.7783 217.417 1.67089L217.317 0.927634L217.216 0.184382C216.502 0.280908 215.788 0.318455 215.074 0.297218L215.052 1.04689ZM217.317 0.927634L217.417 1.67089C218.085 1.58066 218.753 1.60683 219.425 1.75077L219.582 1.01743L219.739 0.284086C218.9 0.104361 218.058 0.0706689 217.216 0.184382L217.317 0.927634ZM219.582 1.01743L219.425 1.75077C220.269 1.93176 221.117 1.95571 221.964 1.82145L221.847 1.08069L221.729 0.339939C221.067 0.444991 220.405 0.426764 219.739 0.284086L219.582 1.01743ZM221.847 1.08069L221.964 1.82145C222.667 1.71007 223.368 1.67443 224.069 1.7141L224.112 0.965299L224.154 0.216496C223.345 0.170718 222.537 0.212006 221.729 0.339939L221.847 1.08069ZM224.112 0.965299L224.069 1.7141C224.834 1.75735 225.598 1.78641 226.362 1.80128L226.377 1.05142L226.391 0.301565C225.646 0.287053 224.9 0.258698 224.154 0.216496L224.112 0.965299ZM226.377 1.05142L226.362 1.80128C227.155 1.81671 227.948 1.77226 228.74 1.6681L228.642 0.924508L228.544 0.180912C227.826 0.275356 227.108 0.31552 226.391 0.301565L226.377 1.05142ZM228.642 0.924508L228.74 1.6681C229.436 1.57654 230.131 1.56816 230.827 1.64283L230.907 0.897119L230.987 0.151402C230.172 0.0639911 229.358 0.0738732 228.544 0.180912L228.642 0.924508ZM230.907 0.897119L230.827 1.64283C231.604 1.72627 232.381 1.77457 233.159 1.78769L233.172 1.0378L233.184 0.287907C232.452 0.275544 231.72 0.230056 230.987 0.151402L230.907 0.897119ZM233.172 1.0378L233.159 1.78769C233.893 1.80008 234.627 1.84336 235.361 1.91754L235.437 1.17134L235.512 0.42514C234.736 0.346761 233.96 0.301007 233.184 0.287907L233.172 1.0378ZM235.437 1.17134L235.361 1.91754C236.184 2.00063 237.006 1.97256 237.827 1.83388L237.702 1.09437L237.577 0.354854C236.887 0.471368 236.2 0.494613 235.512 0.42514L235.437 1.17134ZM237.702 1.09437L237.827 1.83388C238.537 1.71376 239.247 1.65908 239.956 1.66943L239.967 0.919509L239.978 0.169589C239.177 0.157888 238.376 0.219774 237.577 0.354854L237.702 1.09437ZM239.967 0.919509L239.956 1.66943C240.695 1.68022 241.433 1.71494 242.172 1.77361L242.232 1.02596L242.291 0.27831C241.52 0.217096 240.749 0.180852 239.978 0.169589L239.967 0.919509ZM242.232 1.02596L242.172 1.77361C242.936 1.83426 243.701 1.88097 244.465 1.91373L244.497 1.16442L244.529 0.41511C243.783 0.383131 243.037 0.337532 242.291 0.27831L242.232 1.02596ZM244.497 1.16442L244.465 1.91373C245.238 1.94687 246.011 1.95219 246.784 1.92968L246.762 1.18L246.74 0.430316C246.003 0.451777 245.266 0.44671 244.529 0.41511L244.497 1.16442Z" /> + + + From 05b265b674a89d0481f7057e083d2cc94a5332db Mon Sep 17 00:00:00 2001 From: soyeonLee <109227292+soyeonLee126@users.noreply.github.com> Date: Tue, 18 Aug 2026 18:07:34 +0900 Subject: [PATCH 13/20] =?UTF-8?q?fix:=20=EA=B0=90=EC=A0=95=20=ED=8C=A8?= =?UTF-8?q?=EB=84=90=EC=9D=B4=20=EC=85=B0=EB=B8=8C=EB=A1=A0=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=95=88=20=EC=A0=91=ED=9E=88=EB=8D=98=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=EC=99=80=20=EC=9C=84=EC=B9=98=EB=A5=BC=20=EB=B0=94?= =?UTF-8?q?=EB=A1=9C=EC=9E=A1=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Popup 은 dismissOnClickOutside 가 기본 켜져 있어 팝업 밖 탭에서도 onDismissRequest 를 부른다. 셰브론을 누르면 닫기와 토글이 함께 들어와, 닫힌 상태를 토글이 되짚어 다시 열었다. 바깥 탭과 뒤로가기는 화면 쪽에서 이미 처리하므로 Popup 의 닫기는 끄고, 제스처마다 주인을 하나로 둔다. 패널 위치는 화면 오른쪽 끝에서 61dp 라는 고정 인셋 대신 토글 라벨의 실측 좌표에 맞춘다. 402dp 시안 기준 상수여서 폭이 다른 기기에서 어긋났고, 여는 버튼과 패널이 화면 반대편에 있었다. alignment 는 여백을 포함한 부모 경계에 붙어 기준이 모호하므로 창 좌표를 직접 넘긴다. --- .../gamss/android/feature/home/HomeScreen.kt | 56 ++++++++++++++----- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt b/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt index 3df205dc..4314a886 100644 --- a/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt +++ b/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt @@ -23,14 +23,15 @@ import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.layout.boundsInWindow +import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalFocusManager @@ -38,8 +39,13 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp 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.window.Popup +import androidx.compose.ui.window.PopupPositionProvider +import androidx.compose.ui.window.PopupProperties import androidx.hilt.navigation.compose.hiltViewModel import com.gamss.android.core.designsystem.component.GamssCharacterPicker import com.gamss.android.core.designsystem.component.GamssCharacterPickerItem @@ -58,6 +64,7 @@ import com.gamss.android.core.designsystem.theme.GamssTheme import com.gamss.android.domain.emotion.EmotionCharacter import org.orbitmvi.orbit.compose.collectAsState import org.orbitmvi.orbit.compose.collectSideEffect +import kotlin.math.roundToInt @Composable fun HomeScreen( @@ -159,7 +166,10 @@ private fun HomeInputSection( actions: HomeActions, ) { val pickerItems = remember(state.selectedCharacters) { state.selectedCharacters.toPickerItems() } - var inputBarHeightPx by remember { mutableIntStateOf(0) } + // 패널 왼쪽 끝을 토글 라벨이 시작하는 지점에 맞춘다. 입력창 안쪽 여백이 바뀌어도 따라가도록 + // 상수로 두지 않고 실측한다. 입력창은 높이가 100dp/149dp+ 로 달라져 아래 끝도 함께 잰다. + var toggleLeftInWindow by remember { mutableFloatStateOf(0f) } + var inputBarBottomInWindow by remember { mutableFloatStateOf(0f) } // 패널은 Popup 으로 띄운다. 흐름에 넣으면 펼칠 때마다 인사말과 입력바가 위로 밀린다. Box(modifier = Modifier.fillMaxWidth().padding(horizontal = InputBarHorizontalPadding)) { @@ -175,25 +185,32 @@ private fun HomeInputSection( label = stringResource(R.string.home_character_picker), expanded = state.isEmotionPickerExpanded, onClick = actions.onEmotionPickerToggle, + modifier = Modifier.onGloballyPositioned { + toggleLeftInWindow = it.boundsInWindow().left + }, ) }, modifier = Modifier .fillMaxWidth() - .onSizeChanged { inputBarHeightPx = it.height }, + .onGloballyPositioned { inputBarBottomInWindow = it.boundsInWindow().bottom }, ) if (state.isEmotionPickerExpanded) { - val pickerOffset = with(LocalDensity.current) { - // 입력창 높이가 100dp/149dp+ 로 달라지므로 겹침 폭만 고정해 실측 높이 기준으로 잡는다. - IntOffset( - x = PickerHorizontalInset.roundToPx(), - y = (inputBarHeightPx.toDp() - PickerOverlapHeight).roundToPx(), + val overlapPx = with(LocalDensity.current) { PickerOverlapHeight.roundToPx() } + val pickerPosition = remember(toggleLeftInWindow, inputBarBottomInWindow, overlapPx) { + PickerPositionProvider( + leftInWindow = toggleLeftInWindow, + topInWindow = inputBarBottomInWindow - overlapPx, ) } Popup( - alignment = Alignment.TopEnd, - offset = pickerOffset, - onDismissRequest = actions.onEmotionPickerDismiss, + popupPositionProvider = pickerPosition, + // 바깥 탭과 뒤로가기는 화면 쪽에서만 처리한다. Popup 에도 맡기면 셰브론을 누를 때 + // 닫기와 토글이 함께 들어와, 닫힌 상태를 토글이 되짚어 다시 열어 버린다. + properties = PopupProperties( + dismissOnBackPress = false, + dismissOnClickOutside = false, + ), ) { GamssCharacterPicker(items = pickerItems, onToggle = actions.onEmotionToggle) } @@ -201,6 +218,19 @@ private fun HomeInputSection( } } +/** 창 좌표로 직접 놓는다. Popup 의 alignment 는 여백을 포함한 부모 경계에 붙어 기준이 모호하다. */ +private class PickerPositionProvider( + private val leftInWindow: Float, + private val topInWindow: Float, +) : PopupPositionProvider { + override fun calculatePosition( + anchorBounds: IntRect, + windowSize: IntSize, + layoutDirection: LayoutDirection, + popupContentSize: IntSize, + ): IntOffset = IntOffset(leftInWindow.roundToInt(), topInWindow.roundToInt()) +} + /** * 입력창 바깥을 누르면 포커스를 내려놓아야 collapsed 로 돌아가고 키보드도 닫힌다. 캐릭터 패널은 * 포커스와 무관한 별도 창이라 [onDismiss] 로 같이 닫는다. @@ -295,8 +325,6 @@ private val GreetingToInputGap = 22.dp // Figma 입력창은 402dp 화면에서 366dp 폭이므로 좌우 여백은 각각 18dp다. private val InputBarHorizontalPadding: Dp = 18.dp -private val PickerHorizontalInset = (-61).dp - // 100dp/149dp 두 입력창 높이 모두에서 Figma가 보여 준 9~10dp 겹침 폭. private val PickerOverlapHeight = 9.dp From b51883d1d7fbf775f0e3ff231da54f2252ae57d5 Mon Sep 17 00:00:00 2001 From: soyeonLee <109227292+soyeonLee126@users.noreply.github.com> Date: Tue, 18 Aug 2026 18:37:10 +0900 Subject: [PATCH 14/20] =?UTF-8?q?refactor:=20=EC=B5=9C=EC=86=8C=20?= =?UTF-8?q?=ED=84=B0=EC=B9=98=20=ED=81=AC=EA=B8=B0=EB=A5=BC=20=EB=94=94?= =?UTF-8?q?=EC=9E=90=EC=9D=B8=EC=8B=9C=EC=8A=A4=ED=85=9C=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=EC=9C=BC=EB=A1=9C=20=EB=AA=A8=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 같은 48dp 가 컴포넌트마다 제 이름의 상수로 흩어져 있었다. 접근성 기준이 바뀌면 한 곳만 고쳐지고 나머지는 남는다. spacing800 에 얹지 않은 이유는 간격과 최소 터치 크기가 같은 값일 뿐 다른 의미라, 한쪽을 조정할 때 다른 쪽이 끌려가면 안 되기 때문이다. --- .../android/core/designsystem/theme/GamssTouchTarget.kt | 8 ++++++++ .../com/gamss/android/core/designsystem/theme/Theme.kt | 3 +++ .../feature/calendar/component/CardDetailDialog.kt | 6 +++--- .../android/feature/chat/component/ChattingListTopBar.kt | 3 ++- 4 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 core/designsystem/src/main/java/com/gamss/android/core/designsystem/theme/GamssTouchTarget.kt diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/theme/GamssTouchTarget.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/theme/GamssTouchTarget.kt new file mode 100644 index 00000000..6ab45aa6 --- /dev/null +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/theme/GamssTouchTarget.kt @@ -0,0 +1,8 @@ +package com.gamss.android.core.designsystem.theme + +import androidx.compose.ui.unit.dp + +/** 시안이 이보다 작게 그려져 있어도 터치 영역은 [minimum] 아래로 내려가지 않게 한다. */ +object GamssTouchTarget { + val minimum = 48.dp +} diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/theme/Theme.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/theme/Theme.kt index 0b52eb6b..f13c6e07 100644 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/theme/Theme.kt +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/theme/Theme.kt @@ -38,6 +38,9 @@ object GamssTheme { val radius: GamssRadius get() = GamssRadius + val touchTarget: GamssTouchTarget + get() = GamssTouchTarget + val sticker: GamssStickerColors get() = GamssStickerColors } diff --git a/feature/calendar/src/main/java/com/gamss/android/feature/calendar/component/CardDetailDialog.kt b/feature/calendar/src/main/java/com/gamss/android/feature/calendar/component/CardDetailDialog.kt index 0f09b516..532e718e 100644 --- a/feature/calendar/src/main/java/com/gamss/android/feature/calendar/component/CardDetailDialog.kt +++ b/feature/calendar/src/main/java/com/gamss/android/feature/calendar/component/CardDetailDialog.kt @@ -22,6 +22,7 @@ import androidx.compose.ui.window.DialogProperties import com.gamss.android.core.designsystem.card.GamssEmotionCard import com.gamss.android.core.designsystem.card.GamssEmotionCardCharacter import com.gamss.android.core.designsystem.theme.GamssTheme +import com.gamss.android.core.designsystem.theme.GamssTouchTarget import com.gamss.android.core.ui.card.cardTitleRes import com.gamss.android.core.ui.card.toGamssEmotionCardCharacter import com.gamss.android.domain.card.Card @@ -84,7 +85,7 @@ private fun CardCloseButton(onClick: () -> Unit) { Box( modifier = Modifier .offset(x = CloseButtonCenteringInset, y = -CloseButtonCenteringInset) - .size(CloseButtonTouchSize) + .size(GamssTouchTarget.minimum) .clickable(role = Role.Button, onClick = onClick), contentAlignment = Alignment.Center, ) { @@ -99,8 +100,7 @@ private fun CardCloseButton(onClick: () -> Unit) { /** Figma 우상단 닫기 아이콘 크기. */ private val CloseIconSize = 20.dp -private val CloseButtonTouchSize = 48.dp -private val CloseButtonCenteringInset = (CloseButtonTouchSize - CloseIconSize) / 2 +private val CloseButtonCenteringInset = (GamssTouchTarget.minimum - CloseIconSize) / 2 private val CardDetailDateFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yy.MM.dd") diff --git a/feature/chat/src/main/java/com/gamss/android/feature/chat/component/ChattingListTopBar.kt b/feature/chat/src/main/java/com/gamss/android/feature/chat/component/ChattingListTopBar.kt index 533ba66c..eca005e6 100644 --- a/feature/chat/src/main/java/com/gamss/android/feature/chat/component/ChattingListTopBar.kt +++ b/feature/chat/src/main/java/com/gamss/android/feature/chat/component/ChattingListTopBar.kt @@ -13,6 +13,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.gamss.android.core.designsystem.theme.GamssTheme +import com.gamss.android.core.designsystem.theme.GamssTouchTarget import com.gamss.android.core.designsystem.topnavigation.GamssTopNavigation import com.gamss.android.core.designsystem.topnavigation.GamssTopNavigationContent import com.gamss.android.core.designsystem.topnavigation.GamssTopNavigationIcon @@ -57,7 +58,7 @@ internal fun ChattingListTopBar( modifier = Modifier .align(Alignment.CenterEnd) .offset(x = (-42).dp, y = 3.dp) - .size(48.dp), + .size(GamssTouchTarget.minimum), ) { Icon( imageVector = Icons.Default.Search, From bd114ca6b7dc1c3f19284d1167d553f4e5affce8 Mon Sep 17 00:00:00 2001 From: soyeonLee <109227292+soyeonLee126@users.noreply.github.com> Date: Tue, 18 Aug 2026 18:37:22 +0900 Subject: [PATCH 15/20] =?UTF-8?q?refactor:=20=EC=9E=85=EB=A0=A5=EC=B0=BD?= =?UTF-8?q?=20=EB=86=92=EC=9D=B4=EB=A5=BC=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=8B=A4=EC=B8=A1=EC=9C=BC=EB=A1=9C=20=EA=B3=84=EC=82=B0?= =?UTF-8?q?=ED=95=B4=20lineCount=20=EB=A5=BC=20=EC=97=86=EC=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit onTextLayout 으로 줄 수를 받아 lineHeight 를 곱해 높이를 되짚던 왕복을 없앤다. 기준 높이는 텍스트 자리의 최소값으로만 주고, 줄이 넘치면 텍스트가 재는 만큼 바가 자란다. 배치 좌표는 collapsed 100dp, focus 149dp, 6줄 184dp 로 이전과 같다. 자연 높이만 쓰면 1줄에서 탭 영역이 36dp 에서 20dp 로 줄어드므로 minHeight 로 준다. 딸려 나온 것: 글꼴 배율용 sp/dp 환산, clipToBounds, height 모디파이어, textStyle 배선이 모두 필요 없어졌다. 최소 터치 크기는 토큰을 쓴다. --- .../designsystem/component/GamssInputBar.kt | 77 ++++++------------- 1 file changed, 23 insertions(+), 54 deletions(-) diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt index 4ee8354b..b7446c40 100644 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt @@ -7,36 +7,32 @@ import androidx.compose.foundation.layout.Box 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.size import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.layout.Layout -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role -import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.constrainHeight import androidx.compose.ui.unit.constrainWidth import androidx.compose.ui.unit.dp import com.gamss.android.core.designsystem.modifier.noRippleClickable import com.gamss.android.core.designsystem.theme.GamssTheme +import com.gamss.android.core.designsystem.theme.GamssTouchTarget -// Figma default(4001:8200) 366x100, focus(3489:4923) 366x149. 둘 다 위 16dp 텍스트 → -// 컨트롤 행 32dp → 아래 16dp 로 구성이 같고, 텍스트에 주는 높이만 다르다. +// Figma default 4001:8200, focus 3489:4923. private val CollapsedInputBarHeight = 100.dp private val ExpandedInputBarHeight = 149.dp private val InputBarTopPadding = 16.dp @@ -45,25 +41,14 @@ private val ControlsRowHeight = 32.dp private val InputBarHorizontalPadding = 20.dp -// 배경까지 담긴 에셋이라 tint 하지 않는다. private val SendButtonSize = 32.dp -// 시안의 컨트롤 행은 32dp 지만 그대로 두면 터치 영역이 Android 권장(48dp)에 못 미친다. -// 행을 48dp 로 재고 32dp 밴드 중앙에 맞춰 놓으면, 보이는 위치는 그대로고 터치만 넓어진다. -// 넘치는 위쪽은 빈 텍스트 자리, 아래·오른쪽은 여백이라 겹쳐도 가려지는 것이 없다. -private val ControlsTouchHeight = 48.dp -private val SendButtonTouchSize = 48.dp -private val ControlsTouchOverhang = (ControlsTouchHeight - ControlsRowHeight) / 2 +private val ControlsTouchOverhang = (GamssTouchTarget.minimum - ControlsRowHeight) / 2 private const val INPUT_MAX_LINES = 6 private const val HEIGHT_ANIMATION_DURATION_MS = 350 -/** - * 텍스트가 위, 감정 선택과 전송 버튼이 아래 행에 놓인다. 포커스가 들어가면 텍스트 자리만 넓어진다. - * - * Row/Column 을 갈아 끼우면 BasicTextField 가 새 노드로 다시 만들어져 포커스를 잡자마자 키보드가 - * 닫힌다. 자식은 한 번만 컴포즈하고 배치만 [Layout] 으로 다시 계산하는 이유다. - */ +/** Row/Column 을 갈아 끼우면 BasicTextField 가 다시 만들어져 포커스를 잡자마자 키보드가 닫힌다. */ @Composable fun GamssInputBar( value: String, @@ -76,43 +61,27 @@ fun GamssInputBar( beforeSendSlot: @Composable (() -> Unit)? = null, ) { val canSubmit = enabled && value.isNotBlank() - var lineCount by remember { mutableIntStateOf(1) } var isExpanded by remember { mutableStateOf(false) } - // 줄 높이는 sp 라 시스템 글꼴 배율을 따라간다. dp 상수로 굳히면 배율을 올렸을 때 마지막 줄이 잘린다. - val textStyle = GamssTheme.typography.body4Medium - val lineHeight = with(LocalDensity.current) { textStyle.lineHeight.toDp() } - val baseHeight = if (isExpanded) ExpandedInputBarHeight else CollapsedInputBarHeight - // 포커스로 기준 높이가 바뀔 때만 애니메이션한다. 줄이 늘어난 만큼은 곧바로 반영해야 - // 방금 친 줄이 애니메이션이 끝날 때까지 잘려 보이지 않는다. + // 포커스로 바뀌는 기준 높이만 애니메이션한다. 늘어난 줄까지 태우면 방금 친 줄이 끝날 때까지 잘려 보인다. val animatedBaseHeight by animateDpAsState( targetValue = baseHeight, animationSpec = tween(durationMillis = HEIGHT_ANIMATION_DURATION_MS), label = "GamssInputBarHeight", ) - val textAreaHeight = - animatedBaseHeight - InputBarTopPadding - InputBarBottomPadding - ControlsRowHeight - val barHeight = - animatedBaseHeight + (lineHeight * lineCount - textAreaHeight).coerceAtLeast(0.dp) Layout( - modifier = modifier - .height(barHeight) - .gamssSketchyBox() - // 확장 첫 프레임엔 텍스트 자리가 0dp 로 잡혀 컨트롤 행 위로 넘친다. - .clipToBounds(), + modifier = modifier.gamssSketchyBox(), content = { InputTextField( value = value, onValueChange = onValueChange, enabled = enabled, placeholder = placeholder, - textStyle = textStyle, canSubmit = canSubmit, onSubmit = onSend, onFocusChanged = { isExpanded = it }, - onLineCountChanged = { lineCount = it }, ) ControlsRow( canSubmit = canSubmit, @@ -129,21 +98,26 @@ fun GamssInputBar( if (constraints.hasBoundedWidth) constraints.maxWidth else constraints.minWidth, ) val availableWidth = (barWidth - sidePad * 2).coerceAtLeast(0) - val totalHeight = constraints.maxHeight val topPad = InputBarTopPadding.roundToPx() + val bottomPad = InputBarBottomPadding.roundToPx() val controlsHeight = ControlsRowHeight.roundToPx() - val textHeight = - (totalHeight - topPad - InputBarBottomPadding.roundToPx() - controlsHeight).coerceAtLeast(0) + // 기준 높이는 텍스트 자리의 최소값으로만 쓴다. 줄이 넘치면 텍스트가 재는 만큼 바가 자란다. + val minTextHeight = + (animatedBaseHeight.roundToPx() - topPad - bottomPad - controlsHeight).coerceAtLeast(0) + val text = textFieldMeasurable.measure( + Constraints(minWidth = availableWidth, maxWidth = availableWidth, minHeight = minTextHeight), + ) val overhang = ControlsTouchOverhang.roundToPx() val controls = controlsMeasurable.measure( - Constraints.fixed(availableWidth + overhang, ControlsTouchHeight.roundToPx()), + Constraints.fixed(availableWidth + overhang, GamssTouchTarget.minimum.roundToPx()), ) - val text = textFieldMeasurable.measure(Constraints.fixed(availableWidth, textHeight)) - layout(barWidth, totalHeight) { + val barHeight = constraints.constrainHeight(topPad + text.height + controlsHeight + bottomPad) + + layout(barWidth, barHeight) { text.place(sidePad, topPad) - controls.place(sidePad, topPad + textHeight - overhang) + controls.place(sidePad, barHeight - bottomPad - controlsHeight - overhang) } } } @@ -154,14 +128,11 @@ private fun InputTextField( onValueChange: (String) -> Unit, enabled: Boolean, placeholder: String, - textStyle: TextStyle, canSubmit: Boolean, onSubmit: () -> Unit, onFocusChanged: (Boolean) -> Unit, - onLineCountChanged: (Int) -> Unit, ) { - // 입력바 SVG는 라이트 배경이므로 시스템 다크 모드와 무관하게 전경을 검정으로 고정한다. - val filledStyle = textStyle.copy(color = GamssTheme.colors.black) + val textStyle = GamssTheme.typography.body4Medium BasicTextField( value = value, onValueChange = onValueChange, @@ -170,13 +141,12 @@ private fun InputTextField( singleLine = false, minLines = 1, maxLines = INPUT_MAX_LINES, - onTextLayout = { onLineCountChanged(it.lineCount.coerceIn(1, INPUT_MAX_LINES)) }, - textStyle = filledStyle, + // 입력바 SVG는 라이트 배경이므로 시스템 다크 모드와 무관하게 전경을 검정으로 고정한다. + textStyle = textStyle.copy(color = GamssTheme.colors.black), cursorBrush = SolidColor(GamssTheme.colors.black), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Send), keyboardActions = KeyboardActions(onSend = { if (canSubmit) onSubmit() }), decorationBox = { innerTextField -> - // 자리표시자와 입력칸을 겹쳐 놓아야 한다. 컨테이너 없이 나란히 두면 입력칸이 밀려난다. Box( modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.TopStart, @@ -203,7 +173,6 @@ private fun ControlsRow( ) { Row(verticalAlignment = Alignment.CenterVertically) { beforeSendSlot?.invoke() - // 폭이 고정 측정되므로 밀어내지 않으면 전송 버튼이 왼쪽 끝에 붙는다. Spacer(modifier = Modifier.weight(1f)) SendButton(canSubmit = canSubmit, contentDescription = sendContentDescription, onClick = onSend) } @@ -217,7 +186,7 @@ private fun SendButton( ) { Box( modifier = Modifier - .size(SendButtonTouchSize) + .size(GamssTouchTarget.minimum) .noRippleClickable(enabled = canSubmit, role = Role.Button, onClick = onClick), contentAlignment = Alignment.Center, ) { From 405144926946898ba1f7d67d6a34c5a8b77520e9 Mon Sep 17 00:00:00 2001 From: soyeonLee <109227292+soyeonLee126@users.noreply.github.com> Date: Tue, 18 Aug 2026 18:37:30 +0900 Subject: [PATCH 16/20] =?UTF-8?q?refactor:=20=ED=86=A0=EA=B8=80=EC=9D=98?= =?UTF-8?q?=20=EC=A0=84=EA=B2=BD=EC=83=89=20=EA=B0=95=EC=A0=9C=EB=A5=BC=20?= =?UTF-8?q?contentColor=20=EB=A1=9C=20=EB=BA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 라이트 전용 아트 위에 놓인다는 이유로 라벨과 셰브론이 검정을 박고 있었다. 범용 컴포넌트가 색을 강제하면 다른 배경에는 못 쓴다. 기본값은 테마를 따르는 gray900 으로 두고, 제약을 아는 호출부에서 검정을 넘긴다. --- .../component/GamssDisclosureToggle.kt | 21 +++++++------------ .../gamss/android/feature/home/HomeScreen.kt | 11 +++------- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssDisclosureToggle.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssDisclosureToggle.kt index 449a1b8e..dc304f64 100644 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssDisclosureToggle.kt +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssDisclosureToggle.kt @@ -8,6 +8,7 @@ import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.res.painterResource @@ -15,33 +16,28 @@ import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp import com.gamss.android.core.designsystem.modifier.noRippleClickable import com.gamss.android.core.designsystem.theme.GamssTheme +import com.gamss.android.core.designsystem.theme.GamssTouchTarget -private val ToggleMinTouchHeight = 48.dp private val LabelChevronGap = 4.dp private val ChevronSize = 16.dp private const val CHEVRON_ROTATION = 90f -// ic_right_chevron 글리프의 실제 중심. 24 뷰포트에서 stroke 포함 x 12.4~21.3, y 3.9~20.2 다. -// 회전축을 글리프 중심으로 옮기지 않으면 펼칠 때마다 위아래로 튄다. +// ic_right_chevron 글리프는 24 뷰포트 안에서 치우쳐 있다. 박스 중심으로 돌리면 펼칠 때 위아래로 튄다. private const val CHEVRON_CENTER_X = 16.85f / 24f private const val CHEVRON_CENTER_Y = 12.03f / 24f -/** - * 라벨과 펼침 상태를 가리키는 셰브론을 나란히 둔 토글. 아래위 셰브론 에셋이 없어 오른쪽 셰브론을 돌려 쓴다. - * - * 터치 영역은 [ToggleMinTouchHeight] 까지 넓히지만, 높이가 고정 측정되는 곳에 놓이면 그만큼만 잡힌다. - * 가로는 라벨과 셰브론 폭만으로 이미 권장치를 넘어, 여백을 더 주면 라벨만 안쪽으로 밀린다. - */ +/** 아래위 셰브론 에셋이 없어 오른쪽 셰브론을 돌려 쓴다. */ @Composable fun GamssDisclosureToggle( label: String, expanded: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, + contentColor: Color = GamssTheme.colors.gray900, ) { Row( modifier = modifier - .heightIn(min = ToggleMinTouchHeight) + .heightIn(min = GamssTouchTarget.minimum) .noRippleClickable(role = Role.DropdownList, onClick = onClick), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(LabelChevronGap), @@ -49,14 +45,13 @@ fun GamssDisclosureToggle( GamssText( text = label, style = GamssTheme.typography.body4Medium, - // 흰 입력바 위에서 다크 테마의 흰 전경색이 사라지지 않게 Figma 전경색을 유지한다. - color = GamssTheme.colors.black, + color = contentColor, maxLines = 1, ) Icon( painter = painterResource(GamssIcons.RightChevron), contentDescription = null, - tint = GamssTheme.colors.black, + tint = contentColor, modifier = Modifier .size(ChevronSize) .graphicsLayer { diff --git a/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt b/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt index 4314a886..775e8c5c 100644 --- a/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt +++ b/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt @@ -96,8 +96,7 @@ fun HomeScreen( ) } - // Popup 은 별도 창이라 뒤로가기를 받지 못한다. 화면 쪽에서 직접 닫아 준다. - // BackHandler 는 dispatcher 소유자를 요구해 프리뷰에서 터지므로 HomeContent 밖에 둔다. + // Popup 은 별도 창이라 뒤로가기를 못 받는다. BackHandler 는 프리뷰에서 터지므로 HomeContent 밖에 둔다. BackHandler(enabled = state.isEmotionPickerExpanded, onBack = actions.onEmotionPickerDismiss) HomeContent(state = state, actions = actions, modifier = modifier) @@ -166,8 +165,6 @@ private fun HomeInputSection( actions: HomeActions, ) { val pickerItems = remember(state.selectedCharacters) { state.selectedCharacters.toPickerItems() } - // 패널 왼쪽 끝을 토글 라벨이 시작하는 지점에 맞춘다. 입력창 안쪽 여백이 바뀌어도 따라가도록 - // 상수로 두지 않고 실측한다. 입력창은 높이가 100dp/149dp+ 로 달라져 아래 끝도 함께 잰다. var toggleLeftInWindow by remember { mutableFloatStateOf(0f) } var inputBarBottomInWindow by remember { mutableFloatStateOf(0f) } @@ -188,6 +185,8 @@ private fun HomeInputSection( modifier = Modifier.onGloballyPositioned { toggleLeftInWindow = it.boundsInWindow().left }, + // 입력바 아트가 라이트 전용이라 다크에서도 전경을 검정으로 둔다. + contentColor = GamssTheme.colors.black, ) }, modifier = Modifier @@ -231,10 +230,6 @@ private class PickerPositionProvider( ): IntOffset = IntOffset(leftInWindow.roundToInt(), topInWindow.roundToInt()) } -/** - * 입력창 바깥을 누르면 포커스를 내려놓아야 collapsed 로 돌아가고 키보드도 닫힌다. 캐릭터 패널은 - * 포커스와 무관한 별도 창이라 [onDismiss] 로 같이 닫는다. - */ @Composable private fun Modifier.dismissOnTapOutside(onDismiss: () -> Unit): Modifier { val focusManager = LocalFocusManager.current From 07f043f929d54884ff6cc279cd0d5bd8a0eb29e3 Mon Sep 17 00:00:00 2001 From: soyeonLee <109227292+soyeonLee126@users.noreply.github.com> Date: Tue, 18 Aug 2026 18:37:42 +0900 Subject: [PATCH 17/20] =?UTF-8?q?refactor:=20=EA=B8=B8=EC=9D=B4=20?= =?UTF-8?q?=EC=B4=88=EA=B3=BC=20=EC=95=88=EB=82=B4=EB=A5=BC=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EC=97=90=EC=84=9C=20=EC=9C=A0=EB=8F=84=ED=95=B4=20len?= =?UTF-8?q?gthWarned=20=EB=A5=BC=20=EC=97=86=EC=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 잘린 결과가 넣어 둔 값과 같으면 상한에 붙은 채 계속 치는 중이다. 이 조건은 상한 계산식과 무관해서, UTF-16 기준에 경계 글자를 통째로 버리는 takeWithinMessageLimit 에서도 성립한다. input.length 를 140 과 비교하면 ZWJ 이모지가 경계에 올 때 오판한다. 플래그가 사라지면서 onSubmit 이 입력을 비울 때 함께 풀어 줘야 한다는 숨은 규칙도 없어진다. 그 동작을 테스트로 잠근다. 상한에 붙은 상태에서 가운데를 고치거나 전체를 붙여넣으면 이제 안내가 나온다. 저장된 내용이 바뀌면서 잘렸으므로 알리는 쪽이 맞다고 봤다. --- .../android/feature/home/HomeViewModel.kt | 16 +++------------ .../android/feature/home/HomeViewModelTest.kt | 20 +++++++++++++++++++ 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt b/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt index d4f23ba2..ceaf43dc 100644 --- a/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt +++ b/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt @@ -33,21 +33,13 @@ class HomeViewModel @Inject constructor( postSideEffect(HomeSideEffect.NavigateToSetting) } - // 이어 치는 동안 토스트가 쌓이지 않게, 넘긴 상태에서 벗어날 때까지 한 번만 알린다. - private var lengthWarned = false - fun onInputChange(text: String) = blockingIntent { val limited = text.takeWithinMessageLimit() + // 잘린 결과가 넣어 둔 값과 같으면 상한에 붙은 채 계속 치는 중이다. 넘어서는 순간에만 알린다. + val crossedLimit = limited != text && limited != state.input reduce { state.copy(input = limited) } - if (limited != text) { - if (!lengthWarned) { - lengthWarned = true - postSideEffect(HomeSideEffect.ShowToast(MESSAGE_LENGTH_EXCEEDED)) - } - } else { - lengthWarned = false - } + if (crossedLimit) postSideEffect(HomeSideEffect.ShowToast(MESSAGE_LENGTH_EXCEEDED)) } fun onEmotionPickerToggle() = intent { @@ -104,8 +96,6 @@ class HomeViewModel @Inject constructor( // applicationScope 로 돌려서 이 화면을 벗어나도 끊기지 않는다. viewModelScope.launch { session.finishSend() } reduce { state.copy(input = "") } - // 비운 입력은 상한과 무관해졌으므로, 다시 넘기면 새로 알린다. - lengthWarned = false postSideEffect(HomeSideEffect.OpenConversation(result.data.message.conversationId)) } // 입력은 남겨 둔다. 실패한 문구를 다시 치게 하면 안 된다. diff --git a/feature/home/src/test/java/com/gamss/android/feature/home/HomeViewModelTest.kt b/feature/home/src/test/java/com/gamss/android/feature/home/HomeViewModelTest.kt index d34705d2..e8ef53dd 100644 --- a/feature/home/src/test/java/com/gamss/android/feature/home/HomeViewModelTest.kt +++ b/feature/home/src/test/java/com/gamss/android/feature/home/HomeViewModelTest.kt @@ -191,6 +191,26 @@ class HomeViewModelTest { } } + @Test + fun `보내서 입력을 비운 뒤 다시 넘기면 또 알린다`() = runTest { + viewModel().test(this) { + val filled = "가".repeat(MAX_MESSAGE_LENGTH) + containerHost.onInputChange(filled + "가") + expectState { copy(input = filled) } + expectSideEffect(HomeSideEffect.ShowToast(MESSAGE_LENGTH_EXCEEDED)) + + containerHost.onSubmit() + expectState { copy(isSending = true) } + expectState { copy(isSending = false) } + expectState { copy(input = "") } + expectSideEffect(HomeSideEffect.OpenConversation(NEW_ROOM_ID)) + + containerHost.onInputChange(filled + "나") + expectState { copy(input = filled) } + expectSideEffect(HomeSideEffect.ShowToast(MESSAGE_LENGTH_EXCEEDED)) + } + } + @Test fun `공백만 적힌 입력은 보내도 대화를 시작하지 않는다`() = runTest { viewModel().test(this) { From 9415ac324cd3edad5e9bc3d113708d3d49279d6b Mon Sep 17 00:00:00 2001 From: soyeonLee <109227292+soyeonLee126@users.noreply.github.com> Date: Tue, 18 Aug 2026 18:37:49 +0900 Subject: [PATCH 18/20] =?UTF-8?q?chore:=20=EC=BD=94=EB=93=9C=EA=B0=80=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=20=EB=A7=90=ED=95=98=EB=8A=94=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=EC=9D=84=20=EA=B1=B7=EC=96=B4=EB=83=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기법과 동작을 되풀이하는 주석을 지우고, 고치면 깨지는 제약과 재보지 않으면 알 수 없는 숫자의 출처만 남긴다. --- .../core/designsystem/component/GamssSketchyBox.kt | 9 --------- .../android/core/designsystem/modifier/Clickable.kt | 5 +---- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBox.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBox.kt index b5ccdeb9..b2fd1ed8 100644 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBox.kt +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBox.kt @@ -18,19 +18,13 @@ import androidx.compose.ui.unit.dp import com.gamss.android.core.designsystem.R import com.gamss.android.core.designsystem.theme.GamssTheme -// 모서리는 늘리지 않고 원본 크기로 찍어 낸다. 굴곡이 가장 심한 구간이라 넉넉히 잡는다. private val ArtCorner = 12.dp // 아트의 모서리가 둥글어, 배경을 직각으로 칠하면 흰 모서리가 선 밖으로 삐져나온다. private val BackgroundCorner = 4.dp -// 링 안쪽(가운데 칸)은 비어 있어 그릴 것이 없다. 그리기마다 새로 만들지 않게 한 번만 계산한다. private val NineSliceCells = (0..2).flatMap { col -> (0..2).map { row -> col to row } } - (1 to 1) -/** - * 손그림 사각 테두리를 9-slice 로 그린다. 모서리는 원본 크기로 두고 직선 구간만 늘리므로, - * 어떤 크기에 올려도 선 굵기가 변하지 않는다. 통째로 늘리면 늘린 방향의 선만 두꺼워진다. - */ @Composable fun Modifier.gamssSketchyBox(background: Color = GamssTheme.colors.white): Modifier { val art = painterResource(R.drawable.bg_input_box) @@ -40,12 +34,10 @@ fun Modifier.gamssSketchyBox(background: Color = GamssTheme.colors.white): Modif } private fun DrawScope.drawNineSlice(art: Painter) { - // 에셋 크기를 상수로 복제하면 에셋을 갈아 끼울 때 조용히 어긋난다. val artSize = art.intrinsicSize if (artSize.isUnspecified) return val corner = ArtCorner.toPx() - // 세 구간의 경계: 원본과 대상 각각. 가운데 구간만 늘어난다. val srcX = floatArrayOf(0f, corner, artSize.width - corner, artSize.width) val srcY = floatArrayOf(0f, corner, artSize.height - corner, artSize.height) val dstX = floatArrayOf(0f, corner, size.width - corner, size.width) @@ -66,7 +58,6 @@ private fun DrawScope.drawNineSlice(art: Painter) { } } -/** 대상이 원본보다 작아 구간이 뒤집히면 0 을 돌려 그리지 않게 한다. */ private fun sliceScale(src: FloatArray, dst: FloatArray, index: Int): Float { val srcLength = src[index + 1] - src[index] val dstLength = dst[index + 1] - dst[index] diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/modifier/Clickable.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/modifier/Clickable.kt index bc97b876..35f4f9ab 100644 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/modifier/Clickable.kt +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/modifier/Clickable.kt @@ -5,10 +5,7 @@ import androidx.compose.foundation.combinedClickable import androidx.compose.ui.Modifier import androidx.compose.ui.semantics.Role -/** - * ripple 없이 클릭을 받습니다. 디자인에 눌림 표현이 없어, 기본 indication 을 두면 손그림 배경 위에 - * 회색 사각형이 덧그려집니다. - */ +/** 기본 indication 을 두면 손그림 배경 위에 회색 사각형이 덧그려집니다. */ fun Modifier.noRippleClickable( enabled: Boolean = true, role: Role? = null, From 2d5158066dcad82c4a3d871e78efbb154f224c97 Mon Sep 17 00:00:00 2001 From: soyeonLee <109227292+soyeonLee126@users.noreply.github.com> Date: Wed, 19 Aug 2026 15:15:19 +0900 Subject: [PATCH 19/20] =?UTF-8?q?design:=20=ED=8F=AC=EC=BB=A4=EC=8A=A4=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=ED=85=8C=EB=91=90=EB=A6=AC=20=EC=95=84?= =?UTF-8?q?=ED=8A=B8=EB=A5=BC=20=EB=84=A3=EA=B3=A0=20=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=EC=B0=BD=20=EB=86=92=EC=9D=B4=EB=A5=BC=20=EC=8B=9C=EC=95=88=20?= =?UTF-8?q?=EA=B0=92=EC=9C=BC=EB=A1=9C=20=EB=A7=9E=EC=B6=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 두 상태의 손그림은 앞 23개 서브패스만 같고 나머지는 다르다. 같은 그림을 늘린 것이 아니라 따로 그린 것이라 에셋을 나눠 두고, gamssSketchyBox 가 아트를 받게 한다. 높이는 두 SVG 의 흰 영역이 각각 366x112, 366x150 이고 Figma input 인스턴스와도 맞는다. 100dp/149dp 로 두면 기본 상태에서 세로 0.84배로 눌려 그려졌다. 이제 두 상태 모두 0.98배다. 9-slice 는 어느 높이에서도 선 굵기를 유지하므로, 아트를 한 장으로 합쳤던 이유는 더 이상 성립하지 않는다. 대신 두 원본의 늘어나는 구간이 90dp 와 128dp 로 달라, 교체 순간에 잔떨림 밀도가 한 번 바뀐다. --- .../designsystem/component/GamssInputBar.kt | 10 ++++++--- .../designsystem/component/GamssSketchyBox.kt | 8 +++++-- .../main/res/drawable/bg_input_box_focus.xml | 22 +++++++++++++++++++ 3 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 core/designsystem/src/main/res/drawable/bg_input_box_focus.xml diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt index b7446c40..d298c65a 100644 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssInputBar.kt @@ -28,13 +28,14 @@ import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.constrainHeight import androidx.compose.ui.unit.constrainWidth import androidx.compose.ui.unit.dp +import com.gamss.android.core.designsystem.R import com.gamss.android.core.designsystem.modifier.noRippleClickable import com.gamss.android.core.designsystem.theme.GamssTheme import com.gamss.android.core.designsystem.theme.GamssTouchTarget // Figma default 4001:8200, focus 3489:4923. -private val CollapsedInputBarHeight = 100.dp -private val ExpandedInputBarHeight = 149.dp +private val CollapsedInputBarHeight = 112.dp +private val ExpandedInputBarHeight = 150.dp private val InputBarTopPadding = 16.dp private val InputBarBottomPadding = 16.dp private val ControlsRowHeight = 32.dp @@ -72,7 +73,10 @@ fun GamssInputBar( ) Layout( - modifier = modifier.gamssSketchyBox(), + // 두 상태의 손그림이 별개로 그려져 있어 아트도 함께 갈아 끼운다. + modifier = modifier.gamssSketchyBox( + artRes = if (isExpanded) R.drawable.bg_input_box_focus else R.drawable.bg_input_box, + ), content = { InputTextField( value = value, diff --git a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBox.kt b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBox.kt index b2fd1ed8..95e10f3b 100644 --- a/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBox.kt +++ b/core/designsystem/src/main/java/com/gamss/android/core/designsystem/component/GamssSketchyBox.kt @@ -1,5 +1,6 @@ package com.gamss.android.core.designsystem.component +import androidx.annotation.DrawableRes import androidx.compose.foundation.background import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable @@ -26,8 +27,11 @@ private val BackgroundCorner = 4.dp private val NineSliceCells = (0..2).flatMap { col -> (0..2).map { row -> col to row } } - (1 to 1) @Composable -fun Modifier.gamssSketchyBox(background: Color = GamssTheme.colors.white): Modifier { - val art = painterResource(R.drawable.bg_input_box) +fun Modifier.gamssSketchyBox( + @DrawableRes artRes: Int = R.drawable.bg_input_box, + background: Color = GamssTheme.colors.white, +): Modifier { + val art = painterResource(artRes) return this .background(background, RoundedCornerShape(BackgroundCorner)) .drawBehind { drawNineSlice(art) } diff --git a/core/designsystem/src/main/res/drawable/bg_input_box_focus.xml b/core/designsystem/src/main/res/drawable/bg_input_box_focus.xml new file mode 100644 index 00000000..93333ed0 --- /dev/null +++ b/core/designsystem/src/main/res/drawable/bg_input_box_focus.xml @@ -0,0 +1,22 @@ + + + + + + + + From 586689d9816fd92202ca174bf940d7c70752d1ee Mon Sep 17 00:00:00 2001 From: soyeonLee <109227292+soyeonLee126@users.noreply.github.com> Date: Wed, 19 Aug 2026 15:15:26 +0900 Subject: [PATCH 20/20] =?UTF-8?q?fix:=20=ED=99=88=20=EC=84=B8=EB=A1=9C=20?= =?UTF-8?q?=EB=B0=B0=EC=B9=98=20=EA=B0=80=EC=A4=91=EC=B9=98=EB=A5=BC=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=EC=B0=BD=20112dp=20=EA=B8=B0=EC=A4=80?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=8B=A4=EC=8B=9C=20=EC=9E=A1=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 351f 는 입력창이 64dp 이던 시절에 잰 값이라, 그 뒤 높이가 바뀌는 동안 입력창 블록이 시안보다 아래에 놓여 있었다. Figma 3483:16376(402x874) 기준으로 다시 센다. 콘텐츠 727 = 헤더 62 + 위 스페이서 170 + 인사말·입력창 블록 198 + 아래 스페이서 297. --- .../main/java/com/gamss/android/feature/home/HomeScreen.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt b/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt index 775e8c5c..b35c09b6 100644 --- a/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt +++ b/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt @@ -305,9 +305,9 @@ private fun BoxScope.HomeDecorations() { ) } -// 헤더 아래(111) ~ 인사말(281), 입력바 아래(418) ~ 탭바 위(769) 간격 비율을 그대로 옮긴 값. +// Figma 3483:16376 기준. 헤더 아래(111) ~ 인사말(281), 입력창 아래(479) ~ 탭바 위(776). private const val GREETING_TOP_WEIGHT = 170f -private const val GREETING_BOTTOM_WEIGHT = 351f +private const val GREETING_BOTTOM_WEIGHT = 297f private val HeaderStartPadding = 20.dp @@ -320,7 +320,7 @@ private val GreetingToInputGap = 22.dp // Figma 입력창은 402dp 화면에서 366dp 폭이므로 좌우 여백은 각각 18dp다. private val InputBarHorizontalPadding: Dp = 18.dp -// 100dp/149dp 두 입력창 높이 모두에서 Figma가 보여 준 9~10dp 겹침 폭. +// 112dp/150dp 두 입력창 높이 모두에서 Figma가 보여 준 9~10dp 겹침 폭. private val PickerOverlapHeight = 9.dp @Preview(name = "Home - Light", showBackground = true, widthDp = 402, heightDp = 720)