Skip to content

feat: 홈 입력창 확장 동작과 손그림 테두리 9-slice 적용 - #46

Draft
soyeonLee126 wants to merge 21 commits into
developfrom
fix/home-ui
Draft

feat: 홈 입력창 확장 동작과 손그림 테두리 9-slice 적용#46
soyeonLee126 wants to merge 21 commits into
developfrom
fix/home-ui

Conversation

@soyeonLee126

@soyeonLee126 soyeonLee126 commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

작업 개요

홈 입력창을 Figma 시안대로 포커스 시 확장되는 형태로 바꿨습니다. 기본 100dp, 포커스 149dp 로 높이만 다르고 위 16dp 텍스트 / 컨트롤 32dp / 아래 16dp 구성은 같습니다.

작업하면서 입력창 주변 구성요소를 디자인시스템으로 올리고, 손그림 테두리를 9-slice 로 그리도록 바꿨습니다. 감정 선택 패널의 열림/닫힘과 위치도 함께 정리했습니다.

작업 유형

  • feat: 새 기능
  • fix: 버그 수정
  • refactor: 리팩토링 (동작 변경 없음)
  • build: 빌드/의존성 설정
  • chore: 기타 (문서, 설정 등)
  • test: 테스트 추가/수정

변경 사항

입력창 확장

  • Row/Column 을 갈아 끼우면 BasicTextField 가 새 노드로 다시 만들어져 포커스를 잡자마자 키보드가 닫힙니다. 자식은 한 번만 컴포즈하고 커스텀 Layout 으로 배치만 다시 계산합니다
  • 포커스로 기준 높이가 바뀔 때만 350ms tween 으로 애니메이션합니다. 줄이 늘어난 만큼은 곧바로 반영해야 방금 친 줄이 애니메이션이 끝날 때까지 잘려 보이지 않습니다
  • 줄 높이는 body4MediumlineHeight(sp)를 dp 로 환산해 씁니다. dp 상수로 굳히면 시스템 글꼴 배율을 올렸을 때 마지막 줄이 잘립니다
  • 컨트롤 행은 시안이 32dp 지만 터치 영역만 48dp 로 재고 32dp 밴드 중앙에 맞췄습니다. 보이는 위치는 그대로고 터치만 넓어집니다

손그림 테두리를 9-slice 로

  • bg_input_box.xml(368x114) 한 장을 Modifier.gamssSketchyBox() 로 9-slice 해서 그립니다. 모서리는 원본 크기로 두고 직선 구간만 늘리므로 어떤 크기에서도 선 굵기가 변하지 않습니다. 통째로 늘리면 늘린 방향의 선만 두꺼워집니다
  • 늘어나는 배율은 가로 0.99배, 세로 0.84~1.39배입니다. 원본이 시안 폭(366dp)과 거의 같아 가로는 사실상 1:1 입니다
  • aapt2 가 32767 바이트를 넘는 pathDataSTRING_TOO_LARGE 로 치환해 런타임 크래시가 납니다. 80KB 짜리 pathData 를 서브패스 경계에서 4개 <path> 로 나눠 담아 우회했습니다
  • 입력창과 감정 선택 패널이 같은 에셋 하나를 쓰게 되어, focus 전용 에셋과 코드로 그리던 테두리를 지웠습니다

감정 선택 패널

  • 제스처마다 주인을 하나로 뒀습니다. 셰브론 탭은 토글, 바깥 탭은 화면 루트, 뒤로가기는 BackHandler 가 각각 처리합니다
  • PopupdismissOnClickOutside 가 기본으로 켜져 있어 팝업 밖 탭에서도 onDismissRequest 를 부릅니다. 셰브론을 누르면 닫기와 토글이 함께 들어와 닫힌 상태를 토글이 되짚어 다시 열었습니다. Popup 쪽 닫기를 끄고 화면에서만 닫습니다
  • 패널 위치는 토글 라벨의 실측 좌표에 맞춥니다. 화면 오른쪽 끝에서 61dp 라는 고정 인셋은 402dp 시안 기준이어서 폭이 다른 기기에서 어긋났고, 여는 버튼과 패널이 화면 반대편에 있었습니다
  • alignment 는 여백을 포함한 부모 경계에 붙어 기준이 모호하므로 커스텀 PopupPositionProvider 로 창 좌표를 직접 넘깁니다

디자인시스템 정리

  • 라벨 + 회전 셰브론 토글을 GamssDisclosureToggle 로 옮겼습니다. 셰브론 회전축 좌표는 아이콘 에셋 옆에 둬야 에셋이 바뀔 때 함께 보입니다
  • ripple 없는 Modifier.noRippleClickablemodifier 패키지에 두고 감정 선택 토글과 전송 버튼에 씁니다
  • 하단 바가 리터럴로 갖고 있던 색을 팔레트 토큰(gray200/gray400/gray900/white)으로 되돌렸습니다
  • 길이 제한 정책을 ViewModel 로 일원화했습니다. 화면에서 한 번 더 자르던 중복과 경고 래치를 없앴습니다

그 외

  • 하단 바 배경을 인셋 바깥에 둬 내비게이션 영역까지 흰색이 깔리게 하고, 네 변에 그려지던 테두리를 위쪽 구분선만 남겼습니다. 화면 폭을 꽉 채우는 바라 좌우와 아래 선은 화면 테두리에 붙어 버립니다
  • 사용하지 않는 bg_tab_bar.png 를 제거했습니다
  • 상위 Scaffold 가 내비게이션 바를 소비하므로, 남은 키보드 높이만 피하도록 WindowInsets.ime.exclude(navigationBars) 를 적용했습니다
  • 140자 초과 안내가 매 입력마다 반복되던 문제를 고쳤습니다. 상한을 벗어날 때까지 한 번만 알립니다

관련 이슈

  • Closes #

관련 작업 (Notion)

스크린샷 / 동작 화면

체크리스트

  • base 브랜치가 develop으로 설정되어 있다
  • 로컬 빌드 및 실행을 확인했다
  • 커밋 메시지가 컨벤션(feat:, fix: 등)을 따른다
  • 불필요한 로그/주석/디버그 코드를 제거했다
  • 리뷰어가 이해할 수 있도록 설명을 작성했다

검증

  • detekt 지적 0건
  • 단위 테스트 418개 전부 통과 (홈 모듈 18개, 길이 제한 안내 3건 신설)
  • 실기기(SM-S911N) 설치 후 확장/축소, 감정 패널 열림/닫힘, 뒤로가기 동작 확인
  • 최신 develop(63424c2) 머지, 충돌 없음

리뷰 요청 사항

시안과 값이 어긋난 채 남아 있는 항목들입니다. 확인해 주시면 후속으로 맞추겠습니다.

  • 입력창 기본 높이: 코드는 100dp 인데 전달받은 state=default.svg 는 흰 영역이 366x112 입니다. 어느 쪽이 맞는지 확인이 필요합니다
  • 세로 배치 가중치: GREETING_TOP_WEIGHT = 170f, GREETING_BOTTOM_WEIGHT = 351f 는 입력창이 64dp 이던 시절에 잰 비율입니다. 지금은 100dp 라 입력창 블록 전체가 시안보다 내려가 있습니다
  • focus 상태 에셋: state=focus 아트가 없어 default 아트를 세로 1.39배 늘려 씁니다. focus 손그림이 default 와 눈에 띄게 다르면 별도 에셋이 필요합니다
  • 감정 선택 패널 테두리: 이전에는 1dp gray200 실선이었는데 손그림 테두리를 공유하도록 바꿨습니다. 시안이 실선이면 되돌려야 합니다
  • 하단 바: 손그림 배경을 흰 배경 + 1dp 구분선으로 바꿔서, 입력창과 패널만 손그림이고 탭바는 평면입니다. 의도한 방향인지 확인 부탁드립니다

포커스가 들어가면 입력창이 64dp 에서 149dp 로 늘고, 텍스트는 위에 남고 감정 선택과
전송 버튼은 하단 행으로 내려간다.

- Row/Column 을 갈아 끼우면 BasicTextField 가 다시 만들어져 포커스가 끊기므로,
  자식은 한 번만 컴포즈하고 커스텀 Layout 으로 배치만 다시 계산한다
- 입력창 배경을 손그림 SVG 기반 VectorDrawable 로 옮기고 PNG 를 지운다.
  aapt2 가 32KB 넘는 pathData 를 STRING_TOO_LARGE 로 치환해 크래시가 나므로
  독립 서브패스 단위로 나눠 담는다
- 두 에셋의 세로 비율이 달라 중간 높이에서는 테두리가 왜곡되므로,
  애니메이션 동안에는 굵기가 일정한 손그림 테두리를 대신 그린다
- 감정 선택 패널도 같은 손그림 테두리를 쓰고, 팝업은 입력창 실측 높이에 맞춰 겹친다
- 입력창 바깥을 누르면 포커스를 내려놓는다
- 140자 초과 안내가 매 입력마다 반복되던 문제를 고친다
- 하단 바 배경을 인셋 바깥에 둬 내비게이션 영역까지 흰색이 깔리게 하고,
  네 변에 그려지던 테두리를 위쪽 구분선만 남긴다
- 입력창 확장 첫 프레임에 텍스트 자리가 0dp 로 잡혀 컨트롤 행 위로 넘치던 것을 잘라 낸다
- expanded 컨트롤 행은 폭이 고정 측정되므로, trailingAction 이 없어도
  전송 버튼이 오른쪽 끝에 붙도록 남는 자리를 밀어낸다
- 라벨 + 회전 셰브론 토글을 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 로 옮긴다
자르기와 함께 토스트가 나가므로 기존 테스트가 사이드이펙트를 소비하지 않아 실패했다.
한 번만 알리는 래치와, 상한 아래로 줄였다 다시 넘길 때 다시 알리는 동작도 함께 검증한다.
Figma default(4001:8200) 가 focus 와 같은 2단 구성으로 바뀌었다. 기본 100dp, 포커스
149dp 로 높이만 다르고 위 16dp 텍스트 / 컨트롤 32dp / 아래 16dp 구성은 같다.

- 상태별 분기가 높이와 좌우 여백 두 값으로 줄어, 측정·배치가 한 벌로 합쳐진다
- 손그림 테두리를 9-slice 로 그린다. 모서리는 원본 크기로 두고 직선 구간만 늘려
  어떤 크기에서도 선 굵기가 변하지 않는다. 통째로 늘리면 늘린 방향 선만 두꺼워졌다
- 입력창과 감정 선택 패널이 같은 에셋 하나를 쓰게 되어, focus 전용 에셋과 코드로
  그리던 테두리를 지운다
- 포커스 전환만 애니메이션하고 늘어난 줄은 즉시 반영한다. 애니메이션 높이로 텍스트
  자리를 재면 방금 친 줄이 애니메이션 동안 잘려 보인다
- ripple 없는 noRippleClickable 을 modifier 에 두고 감정 선택 토글과 전송 버튼에 쓴다
- 보낸 뒤 입력을 비우면 길이 초과 안내 래치도 함께 푼다
시안이 default 20dp, focus 16dp 로 어긋나 있어 포커스가 들어갈 때 텍스트가 왼쪽으로
4dp 움직였다. 최근 노드(4001:8200)의 20dp 로 맞춘다.
선 굵기 1.5dp, 색 #303136 으로 바뀐다. 9-slice 로 그리므로 원본이 99x38 이어도
입력창(366x100/149)과 감정 선택 패널(211x94) 모두 굵기가 유지된다.
- 시안의 컨트롤 행은 32dp 라 터치 영역이 Android 권장에 못 미쳤다. 행을 48dp 로 재고
  32dp 밴드 중앙에 놓아, 보이는 위치는 그대로 두고 터치만 넓힌다. 전송 버튼도 48dp
  터치 박스에 32dp 아이콘을 중앙 배치하고 남는 폭은 오른쪽 여백으로 넘겨 위치를 지킨다
- 테두리 아트의 모서리가 둥글어 직각 배경이 선 밖으로 삐져나오던 것을 라운드로 맞춘다
Popup 은 별도 창이라 뒤로가기를 받지 못해 BackHandler 로 직접 닫는다.
토글과 나눈 닫기 인텐트를 둬, 바깥 탭과 Popup dismiss 가 겹쳐 들어와도 다시 열리지 않는다.
줄 높이를 20dp 상수로 굳혀 두면 시스템 글꼴 배율을 올렸을 때 마지막 줄이 잘린다.
sp 인 typography 값을 그대로 dp 로 환산해 쓴다.
가로 터치 영역은 라벨과 셰브론 폭만으로 이미 권장치를 넘어, 여백은 라벨만 안쪽으로 밀었다.
9-slice 칸 목록은 그리기마다 새로 만들지 않게 상수로 올린다.
99x38 버튼 아트를 9-slice 로 늘려 쓰다 보니 가로 4.6배, 세로 5.4~8.9배로 손그림 떨림이
펴져서 시안보다 매끈해 보였다. 368x114 원본은 가로 0.99배, 세로 0.84~1.39배로 거의 1:1 이다.
pathData 가 80KB 라 aapt2 의 32767 바이트 한도에 걸리므로 서브패스 경계에서 4개로 나눠 담는다.
Popup 은 dismissOnClickOutside 가 기본 켜져 있어 팝업 밖 탭에서도 onDismissRequest 를
부른다. 셰브론을 누르면 닫기와 토글이 함께 들어와, 닫힌 상태를 토글이 되짚어 다시 열었다.
바깥 탭과 뒤로가기는 화면 쪽에서 이미 처리하므로 Popup 의 닫기는 끄고, 제스처마다 주인을
하나로 둔다.

패널 위치는 화면 오른쪽 끝에서 61dp 라는 고정 인셋 대신 토글 라벨의 실측 좌표에 맞춘다.
402dp 시안 기준 상수여서 폭이 다른 기기에서 어긋났고, 여는 버튼과 패널이 화면 반대편에
있었다. alignment 는 여백을 포함한 부모 경계에 붙어 기준이 모호하므로 창 좌표를 직접 넘긴다.
@soyeonLee126 soyeonLee126 changed the title feat: 홈 입력창을 포커스에 따라 확장되는 형태로 바꿈 feat: 홈 입력창 확장 동작과 손그림 테두리 9-slice 적용 Aug 18, 2026
같은 48dp 가 컴포넌트마다 제 이름의 상수로 흩어져 있었다. 접근성 기준이 바뀌면 한 곳만
고쳐지고 나머지는 남는다. spacing800 에 얹지 않은 이유는 간격과 최소 터치 크기가 같은 값일
뿐 다른 의미라, 한쪽을 조정할 때 다른 쪽이 끌려가면 안 되기 때문이다.
onTextLayout 으로 줄 수를 받아 lineHeight 를 곱해 높이를 되짚던 왕복을 없앤다. 기준 높이는
텍스트 자리의 최소값으로만 주고, 줄이 넘치면 텍스트가 재는 만큼 바가 자란다. 배치 좌표는
collapsed 100dp, focus 149dp, 6줄 184dp 로 이전과 같다.

자연 높이만 쓰면 1줄에서 탭 영역이 36dp 에서 20dp 로 줄어드므로 minHeight 로 준다.

딸려 나온 것: 글꼴 배율용 sp/dp 환산, clipToBounds, height 모디파이어, textStyle 배선이
모두 필요 없어졌다. 최소 터치 크기는 토큰을 쓴다.
라이트 전용 아트 위에 놓인다는 이유로 라벨과 셰브론이 검정을 박고 있었다. 범용 컴포넌트가
색을 강제하면 다른 배경에는 못 쓴다. 기본값은 테마를 따르는 gray900 으로 두고, 제약을 아는
호출부에서 검정을 넘긴다.
잘린 결과가 넣어 둔 값과 같으면 상한에 붙은 채 계속 치는 중이다. 이 조건은 상한 계산식과
무관해서, UTF-16 기준에 경계 글자를 통째로 버리는 takeWithinMessageLimit 에서도 성립한다.
input.length 를 140 과 비교하면 ZWJ 이모지가 경계에 올 때 오판한다.

플래그가 사라지면서 onSubmit 이 입력을 비울 때 함께 풀어 줘야 한다는 숨은 규칙도 없어진다.
그 동작을 테스트로 잠근다. 상한에 붙은 상태에서 가운데를 고치거나 전체를 붙여넣으면 이제
안내가 나온다. 저장된 내용이 바뀌면서 잘렸으므로 알리는 쪽이 맞다고 봤다.
기법과 동작을 되풀이하는 주석을 지우고, 고치면 깨지는 제약과 재보지 않으면 알 수 없는 숫자의
출처만 남긴다.
두 상태의 손그림은 앞 23개 서브패스만 같고 나머지는 다르다. 같은 그림을 늘린 것이 아니라
따로 그린 것이라 에셋을 나눠 두고, gamssSketchyBox 가 아트를 받게 한다.

높이는 두 SVG 의 흰 영역이 각각 366x112, 366x150 이고 Figma input 인스턴스와도 맞는다.
100dp/149dp 로 두면 기본 상태에서 세로 0.84배로 눌려 그려졌다. 이제 두 상태 모두 0.98배다.

9-slice 는 어느 높이에서도 선 굵기를 유지하므로, 아트를 한 장으로 합쳤던 이유는 더 이상
성립하지 않는다. 대신 두 원본의 늘어나는 구간이 90dp 와 128dp 로 달라, 교체 순간에 잔떨림
밀도가 한 번 바뀐다.
351f 는 입력창이 64dp 이던 시절에 잰 값이라, 그 뒤 높이가 바뀌는 동안 입력창 블록이 시안보다
아래에 놓여 있었다.

Figma 3483:16376(402x874) 기준으로 다시 센다. 콘텐츠 727 = 헤더 62 + 위 스페이서 170 +
인사말·입력창 블록 198 + 아래 스페이서 297.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant