diff --git a/Projects/Feature/SettingFeature/CLAUDE.md b/Projects/Feature/SettingFeature/CLAUDE.md index 3fb36dacf..34343041d 100644 --- a/Projects/Feature/SettingFeature/CLAUDE.md +++ b/Projects/Feature/SettingFeature/CLAUDE.md @@ -47,7 +47,7 @@ - **완결 알림/휴재 복귀 알림 목록(`NovelNotificationListView`, #188)** — `NotificationSettingView`의 두 row에서 push. **화면 구조가 완전히 같아**(사용자 확정) 별도 View 두 개를 만들지 않고 `NovelNotificationType`(`.completion`/`.hiatusReturn`)만 다르게 주입해 같은 View/VM을 재사용한다 — `SettingFeatureFactory`에 `makeCompletionNotificationListView`/`makeHiatusReturnNotificationListView` 두 진입점은 있지만 내부에서 같은 `NovelNotificationListView`(제목만 다름)를 만든다. - **툴바 우측은 "수정"↔"삭제" 전환 버튼 하나**(`state.isEditing`, 사용자 확정) — 기본은 "수정"(gray300, 항상 탭 가능)이고, 누르면 "삭제"로 바뀌며 그때부터 행 선택이 가능해진다(`NovelNotificationRow`는 행 전체가 탭 영역이지만 `isEditing`이 아니면 `toggleSelection`이 VM에서 no-op). **체크 버튼 자체가 `isEditing`이 아니면 렌더되지 않는다**(숨김이 아니라 조건부 렌더 — "수정"을 눌러야 비로소 나타남). "삭제"는 하나 이상 선택해야 활성(primary100)이고, 선택 없이는 비활성(gray200) — "수정"의 gray300과는 다른 톤이라 섞어 쓰지 말 것. 편집 모드를 취소할 방법은 따로 없다(뒤로가기로 화면을 나가면 초기화). 삭제 성공 시 `isEditing`도 함께 꺼져 다음 진입은 항상 "수정"부터 다시 시작한다. - **`NovelNotificationRow`는 `Button`이 아니라 `onTapGesture`**(사용자 확정) — `.disabled`를 얹은 `Button`은 비편집 상태에서 행 전체가 옅게 흐려 보이는 부작용이 있었다(no-op이어야 할 상태가 "비활성화된 것처럼" 보임). 체크 아이콘은 `WSSComponent`의 `WSSSelectionCheckIcon`(#188 — 여러 화면에 흩어져 있던 크로스페이드+스케일 스프링 구현을 공용 컴포넌트로 승격)을 그대로 쓴다 — 새 화면에서 같은 아이콘 쌍이 필요하면 직접 구현하지 말고 이 컴포넌트를 재사용할 것. - - 삭제는 `WSSAlertType.deleteNovelNotificationSubscriptions(summary:)`(신규, `WSSComponent`) 확인 알럿을 거친다. `summary`는 **목록에 보이는 순서 기준 첫 선택 항목 제목**("{제목} 외 N작품", View의 `Presentation` 확장이 조합 — 알럿 컴포넌트는 문구를 모른다). + - 삭제는 `WSSAlertType.deleteNovelNotificationSubscriptions(summary:)`(신규, `WSSComponent`) 확인 알럿을 거친다. `summary`는 **목록에 보이는 순서 기준 첫 선택 항목 제목**으로 View의 `Presentation` 확장이 조합한다(알럿 컴포넌트는 문구를 모른다) — 단일 선택은 접두어 없이 제목만("{제목}"), **다중 선택은 "Re : {제목} 외 N작품"**(사용자 확정, #269 — 단일/다중 문구 규칙이 대칭이 아니다). - 페이지네이션은 서버가 명시적으로 내려주는 `nextSubscriptionID` 커서를 쓴다(`NotificationDomain`의 `Notification`처럼 마지막 항목 id로 유추하지 않음). - ⚠️ **현재 로드된 페이지를 통째로 선택 삭제하면 목록은 비지만 다음 페이지가 남아있을 수 있다** — `state.subscriptions.isEmpty`만 보고 빈 상태(`WSSEmptyView`)로 판정하면 서버에 남은 구독이 화면에서 사라진 것처럼 보인다. VM이 삭제 직후 `subscriptions.isEmpty && hasNextPage`면 자동으로 다음 페이지를 이어 로드하고(`loadMore()`), View는 그 과도기를 빈 상태 대신 `LoadingView()`로 가린다. 삭제 로직을 만질 땐 이 자동 이어받기를 빠뜨리지 말 것. - 목록이 비면 `WSSEmptyView(type: .novelNotification)`의 "작품 둘러보기" CTA가 뜨는데, 검색 화면은 다른 Feature 모듈이라 이 화면이 직접 못 연다 — `NovelNotificationListRoute.browseNovels`(#253 리뷰에서 Route로 편입 — 처음엔 "빈 상태 CTA"라 클로저로 남겼다가, App이 실제로 push하는 화면 전환 의도인데 기본값 no-op까지 있어 `MyLibraryRoute.search`와 판정이 어긋난다는 지적으로 정정)를 올리면 실제 이동은 App이 정한다(#201부터 `MypageRootView`/`LibraryRootView` 둘 다 일반 검색 화면으로 push해 배선 완료). diff --git a/Projects/Feature/SettingFeature/Sources/Setting/NotifiactionSetting/NovelNotificationList/NovelNotificationListView.swift b/Projects/Feature/SettingFeature/Sources/Setting/NotifiactionSetting/NovelNotificationList/NovelNotificationListView.swift index 0aa296b19..cbb90f0f9 100644 --- a/Projects/Feature/SettingFeature/Sources/Setting/NotifiactionSetting/NovelNotificationList/NovelNotificationListView.swift +++ b/Projects/Feature/SettingFeature/Sources/Setting/NotifiactionSetting/NovelNotificationList/NovelNotificationListView.swift @@ -163,11 +163,11 @@ private extension NovelNotificationListView { ) } - /// "{첫 선택 작품 제목} 외 N작품" — 목록에 보이는 순서 기준으로 첫 선택 항목을 고른다. + /// "Re : {첫 선택 작품 제목} 외 N작품" — 목록에 보이는 순서 기준으로 첫 선택 항목을 고른다. var deleteSummaryText: String { let selected = viewModel.state.subscriptions.filter { viewModel.state.selectedNovelIDs.contains($0.novelID) } guard let first = selected.first else { return "" } - return selected.count == 1 ? first.novelTitle : "\(first.novelTitle) 외 \(selected.count - 1)작품" + return selected.count == 1 ? first.novelTitle : "Re : \(first.novelTitle) 외 \(selected.count - 1)작품" } var toastBinding: Binding { diff --git a/Projects/UI/WSSComponent/Sources/Alert/WSSAlertStyle.swift b/Projects/UI/WSSComponent/Sources/Alert/WSSAlertStyle.swift index 87a020178..f1894df95 100644 --- a/Projects/UI/WSSComponent/Sources/Alert/WSSAlertStyle.swift +++ b/Projects/UI/WSSComponent/Sources/Alert/WSSAlertStyle.swift @@ -317,14 +317,14 @@ public extension WSSAlertType { case .deleteNovelNotificationSubscriptions(let summary): WSSAlertContent( - iconImage: nil, - title: "해당 작품 알림을 삭제할까요?", - titleFont: .title1, - titleBottomPadding: 10, + iconImage: WSSImage.icModalWarning.swiftUIImage, + title: "작품 알림을 삭제할까요?", + titleFont: .title2, + titleBottomPadding: 4, description: WSSAlertDescription( title: summary, - font: .body2, - titleColor: WSSColor.wssGray300.swiftUIColor, + font: .label1, + titleColor: WSSColor.wssGray200.swiftUIColor, bottomPadding: 24 ), buttons: [