From 0844c6f4d7b7440e152d71bea54d6d6d8f71d293 Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Tue, 24 Mar 2026 13:36:10 +0900 Subject: [PATCH] =?UTF-8?q?fix(live-room):=20=EB=9D=BC=EC=9D=B4=EB=B8=8C?= =?UTF-8?q?=20=EC=83=81=EC=84=B8=20SNS=20=EB=A7=81=ED=81=AC=20=EC=95=84?= =?UTF-8?q?=EC=9D=B4=EC=BD=98=20=EB=A7=A4=ED=95=91=EC=9D=84=20=EC=8B=A0?= =?UTF-8?q?=EA=B7=9C=20=ED=95=84=EB=93=9C=EC=97=90=20=EB=A7=9E=EC=B6=98?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Room/Detail/GetRoomDetailResponse.swift | 5 +- .../Live/Room/Detail/LiveDetailView.swift | 95 ++++++++++++------- docs/20260324_라이브상세SNS아이콘적용.md | 70 ++++++++++++++ 3 files changed, 135 insertions(+), 35 deletions(-) create mode 100644 docs/20260324_라이브상세SNS아이콘적용.md diff --git a/SodaLive/Sources/Live/Room/Detail/GetRoomDetailResponse.swift b/SodaLive/Sources/Live/Room/Detail/GetRoomDetailResponse.swift index 0459146..daf42f7 100644 --- a/SodaLive/Sources/Live/Room/Detail/GetRoomDetailResponse.swift +++ b/SodaLive/Sources/Live/Room/Detail/GetRoomDetailResponse.swift @@ -32,8 +32,9 @@ struct GetRoomDetailManager: Decodable { let introduce: String let youtubeUrl: String? let instagramUrl: String? - let websiteUrl: String? - let blogUrl: String? + let fancimmUrl: String? + let xUrl: String? + let kakaoOpenChatUrl: String? let profileImageUrl: String let isCreator: Bool } diff --git a/SodaLive/Sources/Live/Room/Detail/LiveDetailView.swift b/SodaLive/Sources/Live/Room/Detail/LiveDetailView.swift index 6397934..8a4021d 100644 --- a/SodaLive/Sources/Live/Room/Detail/LiveDetailView.swift +++ b/SodaLive/Sources/Live/Room/Detail/LiveDetailView.swift @@ -152,46 +152,20 @@ struct LiveDetailView: View { .clipShape(Circle()) VStack(spacing: 16.7) { - HStack(spacing: 6.7) { + HStack(spacing: 8) { Text(manager.nickname) .appFont(size: 16.7, weight: .medium) .foregroundColor(Color(hex: "eeeeee")) - + Spacer() - - if let websiteUrl = manager.websiteUrl, let url = URL(string: websiteUrl), UIApplication.shared.canOpenURL(url) { - Image("ic_website_blue") + + ForEach(makeSnsItems(from: manager)) { item in + Image(item.iconName) .resizable() .frame(width: 33.3, height: 33.3) + .contentShape(Rectangle()) .onTapGesture { - UIApplication.shared.open(url) - } - } - - if let blogUrl = manager.blogUrl, let url = URL(string: blogUrl), UIApplication.shared.canOpenURL(url) { - Image("ic_blog_blue") - .resizable() - .frame(width: 33.3, height: 33.3) - .onTapGesture { - UIApplication.shared.open(url) - } - } - - if let instagramUrl = manager.instagramUrl, let url = URL(string: instagramUrl), UIApplication.shared.canOpenURL(url) { - Image("ic_instagram_blue") - .resizable() - .frame(width: 33.3, height: 33.3) - .onTapGesture { - UIApplication.shared.open(url) - } - } - - if let youtubeUrl = manager.youtubeUrl, let url = URL(string: youtubeUrl), UIApplication.shared.canOpenURL(url) { - Image("ic_youtube_play_blue") - .resizable() - .frame(width: 33.3, height: 33.3) - .onTapGesture { - UIApplication.shared.open(url) + openSnsLink(item.url) } } } @@ -494,4 +468,59 @@ struct LiveDetailView: View { AppState.shared.back() } } + + private func makeSnsItems(from manager: GetRoomDetailManager) -> [LiveDetailSnsItem] { + var items = [LiveDetailSnsItem]() + + appendSnsItem(items: &items, iconName: "ic_sns_youtube", url: manager.youtubeUrl) + appendSnsItem(items: &items, iconName: "ic_sns_instagram", url: manager.instagramUrl) + appendSnsItem(items: &items, iconName: "ic_sns_x", url: manager.xUrl) + appendSnsItem(items: &items, iconName: "ic_sns_fancimm", url: manager.fancimmUrl) + appendSnsItem(items: &items, iconName: "ic_sns_kakao", url: manager.kakaoOpenChatUrl) + + return items + } + + private func appendSnsItem(items: inout [LiveDetailSnsItem], iconName: String, url: String?) { + guard let url else { + return + } + + let trimmed = url.trimmingCharacters(in: .whitespacesAndNewlines) + + guard !trimmed.isEmpty else { + return + } + + items.append(LiveDetailSnsItem(iconName: iconName, url: trimmed)) + } + + private func openSnsLink(_ urlString: String) { + guard let url = normalizedUrl(urlString), UIApplication.shared.canOpenURL(url) else { + return + } + + UIApplication.shared.open(url) + } + + private func normalizedUrl(_ urlString: String) -> URL? { + let trimmed = urlString.trimmingCharacters(in: .whitespacesAndNewlines) + + guard !trimmed.isEmpty else { + return nil + } + + if trimmed.hasPrefix("http://") || trimmed.hasPrefix("https://") { + return URL(string: trimmed) + } + + return URL(string: "https://\(trimmed)") + } +} + +private struct LiveDetailSnsItem: Identifiable { + let iconName: String + let url: String + + var id: String { "\(iconName)-\(url)" } } diff --git a/docs/20260324_라이브상세SNS아이콘적용.md b/docs/20260324_라이브상세SNS아이콘적용.md new file mode 100644 index 0000000..ece47e8 --- /dev/null +++ b/docs/20260324_라이브상세SNS아이콘적용.md @@ -0,0 +1,70 @@ +# 20260324 라이브 상세 SNS 아이콘 적용 + +## 작업 항목 +- [x] `LiveDetailView` SNS 영역 아이콘을 `CreatorDetailDialogView`에서 사용하는 SNS 아이콘 에셋으로 변경 + - QA: `instagram`, `fancimm`, `x`, `youtube`, `kakaoOpenChat` URL이 유효할 때만 아이콘이 표시된다. +- [x] `GetRoomDetailManager` 신규 SNS 필드(`youtube`, `instagram`, `x`, `fancimm`, `kakaoOpenChat`)를 모두 라이브 상세 SNS 영역에 반영 + - QA: 5개 SNS가 누락 없이 표시 대상에 포함된다. +- [x] SNS 아이콘 크기를 기존과 동일하게 유지 + - QA: 아이콘 `frame(width: 33.3, height: 33.3)`를 유지한다. + +## 검증 항목 +- [x] `lsp_diagnostics`로 변경 파일 오류 확인 +- [x] `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build` 실행 + +## 검증 기록 +- 일시: 2026-03-24 + - 무엇: `LiveDetailView` SNS 아이콘 영역 변경 후 정적 진단 + - 왜: 변경 파일의 컴파일/타입 문제 확인 + - 어떻게: LSP 진단 실행 + - 실행 명령: `lsp_diagnostics(filePath: SodaLive/Sources/Live/Room/Detail/LiveDetailView.swift)` + - 결과: `No such module 'Kingfisher'` 1건 확인(로컬 SourceKit 인덱싱 환경 이슈로 판단, 코드 문법 오류는 확인되지 않음) + +- 일시: 2026-03-24 + - 무엇: 앱 빌드 검증 + - 왜: 수정 사항이 실제 프로젝트 빌드에 문제 없는지 확인 + - 어떻게: Debug 빌드 수행 + - 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build` + - 결과: `** BUILD SUCCEEDED **` + +- 일시: 2026-03-24 + - 무엇: 테스트 실행 가능 여부 확인 + - 왜: 변경 후 회귀 테스트 수행 시도 + - 어떻게: 스킴 test 액션 실행 + - 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test` + - 결과: `Scheme SodaLive is not currently configured for the test action.` (현재 스킴 테스트 미구성) + +- 일시: 2026-03-24 + - 무엇: 최종 수정(들여쓰기 정리) 후 재빌드 확인 + - 왜: 최종 상태 기준으로 컴파일 성공 재확인 + - 어떻게: Debug 빌드 재실행 + - 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build` + - 결과: `** BUILD SUCCEEDED **` + +- 일시: 2026-03-24 + - 무엇: 최종 수정 후 테스트 액션 재확인 + - 왜: 최종 상태 기준 테스트 가능 여부 재확인 + - 어떻게: 스킴 test 액션 재실행 + - 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test` + - 결과: `Scheme SodaLive is not currently configured for the test action.` (현재 스킴 테스트 미구성) + +- 일시: 2026-03-24 + - 무엇: SNS 매핑/크기 수동 QA(정적 확인) + - 왜: 요청한 5개 SNS 반영 및 아이콘 크기 유지 여부를 최종 확인 + - 어떻게: 소스 패턴 검색으로 매핑 개수와 뷰 프레임 확인 + - 실행 명령: `grep count: appendSnsItem(... "ic_sns_", ...)`, `grep content: ForEach(makeSnsItems...), .frame(width: 33.3, height: 33.3)` + - 결과: `appendSnsItem` 5건 확인, SNS 렌더링 `ForEach` + `frame(width: 33.3, height: 33.3)` 확인 + +- 일시: 2026-03-24 + - 무엇: SNS 순서 정리 후 최종 빌드 + - 왜: 최종 코드 기준 컴파일 성공 여부 확정 + - 어떻게: Debug 빌드 재실행 + - 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build` + - 결과: `** BUILD SUCCEEDED **` + +- 일시: 2026-03-24 + - 무엇: SNS 순서 정리 후 최종 테스트 액션 확인 + - 왜: 최종 코드 기준 테스트 실행 가능 여부 확정 + - 어떻게: 스킴 test 액션 재실행 + - 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test` + - 결과: `Scheme SodaLive is not currently configured for the test action.` (현재 스킴 테스트 미구성)