알림을 끌 수 없는 알림 설정
Oftway 에는 저녁에 한 번 오는 하루 정리 알림이 있습니다. 기본은 처음 간 자리가 있는 날에만 저녁 9시에 "오늘 새로운 곳 n곳에 들렀어요"를 보내고, 설정에서 "새 장소에 간 날만 · 매일 · 끄기" 중 하나와 19시부터 23시까지의 시각을 고릅니다(day-summary intent). 서버 푸시가 아니라 기기 안에서 예약하는 로컬 알림이고, 잠금 화면에 보이니 문구에는 장소 이름 없이 개수만 씁니다.
오늘 제 폰에서 이 설정 시트를 열었다가 intent 에 남긴 발의는 짧습니다. "ui ux 수정 및, 아래 매일, 끄기 버튼 동작 안함." 화면에서 본 증상은 셋이었습니다. 시각 칩 줄(19시~23시)이 시트 맨 위 제목 위에 겹쳐 그려졌고, 시트 아래쪽은 비어서 바탕색 띠가 보였고, "매일"과 "끄기"는 눌러도 바뀌지 않았습니다. 알림 설정 화면에서 알림을 끌 수 없었던 셈입니다.
원인은 intent 에 한 문장으로 적었습니다. 시각 칩 줄이 가로 ScrollView 였고, 이 시트는 높이를 내용에 맞추는 formSheet(sheetAllowedDetents: 'fitToContents')라 그 안에서 배치가 깨졌다는 것. 그리고 그렇게 늘어난 ScrollView 가 아래에 있는 모드 칩의 터치를 가로챘다는 것입니다. 10월 7일 설정을 묶음 여섯과 한 줄 항목으로 줄이면서(settings-compact) 칩 줄을 이 시트 안으로 옮겼는데, 그때 시각 칩 다섯이 가로 스크롤로 들어간 채였습니다.
스크롤을 걷고, 칸을 같은 너비로
고친 방향은 시트 안에서 ScrollView 를 아예 없애는 것이었습니다. 위에서 아래로 제목·설명, "알림 받을 날", "알림 시각" 순서로 겹침 없이 놓입니다.
알림 받을 날은 칩 대신 설정 화면의 다른 줄과 같은 모양의 한 줄 항목 셋으로 바꿨습니다. 줄 높이는 최소 48이고, 고른 줄 오른쪽에만 체크가 붙습니다. 알림 시각은 다섯 칸이 같은 너비로 한 줄을 나눠 씁니다. 다섯 개뿐이라 가로로 밀어 볼 이유가 처음부터 없었습니다.
/** 알림 시각 한 칸. 다섯 칸이 같은 너비로 한 줄을 채운다(가로 스크롤 없이 — 시트 안 ScrollView는 배치를 깨뜨린다). */
function HourCell({ label, selected, disabled, onPress }: { label: string; selected: boolean; disabled: boolean; onPress: () => void }) {
return (
<Pressable
accessibilityRole="radio"
accessibilityState={{ checked: selected, disabled }}
disabled={disabled}
onPress={onPress}
className={`min-h-11 flex-1 items-center justify-center rounded-sm ${selected ? 'bg-accent-weak' : 'bg-field'}`}>
<Text variant="callout" tone={selected ? 'accent' : 'mid'} numberOfLines={1} className="tabular-nums">
{label}
</Text>
</Pressable>
);
}
flex-1 로 칸 너비를 똑같이 나누고, 숫자는 tabular-nums 로 폭을 맞췄습니다. 칸 높이 min-h-11 은 44로, DESIGN.md 의 터치 영역 최소 44×44 에 맞춘 값입니다. 예전 칩에 있던 accessibilityRole="radio" 와 묶음의 radiogroup 은 새 줄과 칸에도 그대로 옮겼고, 끈 상태의 칸에는 disabled 를 접근성 상태로도 함께 넘깁니다. 묶음 위에는 "알림 받을 날", "알림 시각"이라는 작은 머리말 두 줄을 새로 넣었고, 영어로는 "Which days", "Time" 입니다.
끄면 숨기지 않고 흐리게
전에는 "끄기"를 고르면 시각 칩 줄 자체를 그리지 않았습니다. 이번에는 자리를 그대로 두고 흐리게만 합니다.
{/* 끄기여도 자리를 지킨다 — 시트 높이가 바뀌지 않게 흐리게만 */}
<View className="gap-2" style={{ opacity: off ? 0.4 : 1 }}>
<Text variant="caption" tone="low" className="px-4" accessibilityRole="header">
{t('settings.notify.hourLabel')}
</Text>
<View className="flex-row gap-2" accessibilityRole="radiogroup">
{SUMMARY_HOURS.map((hour) => (
<HourCell key={hour} label={t('settings.notify.hour', { hour })} selected={prefs.hour === hour} disabled={off} onPress={() => change({ hour })} />
))}
</View>
</View>
이유는 커밋 본문과 intent 에 같은 말로 남아 있습니다. 시트 높이를 지키기 위해서입니다. 내용에 높이를 맞추는 시트에서는 줄 하나가 생겼다 사라지면 시트 전체의 높이가 따라 바뀝니다. intent 의 기대 결과에도 "끄기를 골라도 시트 높이가 바뀌지 않는다(시각 칸은 흐리게 남고 누를 수 없다)"고 적어 두었고, 코드는 그 문장대로 투명도 0.4 와 disabled 로 갔습니다. 저장하는 값과 알림 예약 로직은 건드리지 않았습니다. 바꾸면 바로 다시 예약하는 동작도 그대로입니다.
이번이 처음은 아니었습니다. intent 에 "pro 시트에서도 확인된 문제"라고 적은 것은 10월 19일의 sheet-fit 작업입니다. Pro 시트도 같은 fitToContents 시트인데, 혜택이 다섯 줄로 늘면서 화면 높이를 넘었습니다. 그때 spec 에는 "그래도 모자라면 혜택 목록만 그 자리에서 스크롤한다"고 적었지만, 커밋 db991dd 에 들어간 것은 스크롤이 아니라 혜택 설명을 접는 길이었고, 코드 주석에 "시트는 ScrollView를 넣으면 배치가 깨진다"는 말이 남았습니다. 같은 판단이 Pro 시트 파일의 주석 한 줄에만 있었고, 하루 정리 시트는 그 전부터 가로 스크롤을 쓰던 채로 남아 있었습니다.
그래서 이번에는 규칙을 DESIGN.md 의 시트 항목으로 올렸습니다. "높이를 내용에 맞추는 시트 안에는 ScrollView를 넣지 않는다 — 배치가 깨지고 아래 버튼의 터치를 가로챈다." 이 규칙이 닿는 범위는 fitToContents 시트까지입니다. 장소 이름 고르기 시트처럼 높이를 0.6·1로 정해 둔 시트는 지금도 안에 ScrollView 를 쓰고 있고, 이번 변경의 대상이 아닙니다. 칩 줄을 다른 방식으로 고칠 수 있었는지, 예를 들어 ScrollView 는 두고 높이를 고정하는 길을 따져 본 기록은 남아 있지 않습니다.
확인한 것과 남은 것
수정은 오늘 PR #79 로 main 에 합쳐졌습니다. 바뀐 파일은 시트 화면 하나와 DESIGN.md, 한국어·영어 문구 두 줄씩입니다. intent 의 제약에 네이티브 변경이 없어 OTA 로 낼 수 있다고 적었습니다.
intent 에 적은 성공 판정은 "내 폰에서 매일·끄기가 바뀌고, 겹침·바탕색 띠가 없다"입니다. 이 작업에는 plan.md 가 따로 없고, 그 판정을 실제로 확인했다는 결과와 OTA 발행 기록은 아직 레포에 없습니다. 오늘까지의 기록은 원인을 적고 고친 코드를 합친 데까지입니다. Open questions 는 제품·기술 모두 "없음"으로 닫혀 있습니다.
Oftway 는 따로 기록하지 않아도 다닌 길이 지도에 남고, 자주 다닌 길일수록 짙어지는 발자취 일기입니다. 위치는 폰 밖으로 나가지 않습니다.