diff --git a/SodaLive/Resources/Assets.xcassets/ic_ice.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/ic_ice.imageset/Contents.json new file mode 100644 index 0000000..216712f --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/ic_ice.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_ice.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/ic_ice.imageset/ic_ice.png b/SodaLive/Resources/Assets.xcassets/ic_ice.imageset/ic_ice.png new file mode 100644 index 0000000..649c00f Binary files /dev/null and b/SodaLive/Resources/Assets.xcassets/ic_ice.imageset/ic_ice.png differ diff --git a/SodaLive/Sources/I18n/I18n.swift b/SodaLive/Sources/I18n/I18n.swift index f93f100..98830b6 100644 --- a/SodaLive/Sources/I18n/I18n.swift +++ b/SodaLive/Sources/I18n/I18n.swift @@ -784,8 +784,9 @@ enum I18n { static var participants: String { pick(ko: "참여자", en: "Participants", ja: "リスナー") } static var follow: String { pick(ko: "팔로우", en: "Follow", ja: "フォロー") } static var following: String { pick(ko: "팔로잉", en: "Following", ja: "フォロー中") } - static var chatFreezeOnStatusMessage: String { pick(ko: "채팅창을 얼렸습니다.", en: "Chat has been frozen.", ja: "チャットが凍結されました。") } - static var chatFreezeOffStatusMessage: String { pick(ko: "채팅창 얼림이 해제되었습니다.", en: "Chat freeze has been lifted.", ja: "チャット凍結が解除されました。") } + static var chatFreezeOnStatusMessageForCreator: String { pick(ko: "“🧊 모두들 얼음!” 채팅창을 얼렸습니다.", en: "\"🧊 Freeze, everyone!\" The chat has been frozen.", ja: "「🧊 みんなフリーズ!」チャットを凍結しました。") } + static var chatFreezeOnStatusMessageForListener: String { pick(ko: "“🧊 모두들 얼음!” 채팅창이 얼었습니다.", en: "\"🧊 Freeze, everyone!\" The chat is now frozen.", ja: "「🧊 みんなフリーズ!」チャットが凍結されました。") } + static var chatFreezeOffStatusMessage: String { pick(ko: "“💧땡! “ 채팅창 얼리기가 해제되었습니다.", en: "\"💧 Ding!\" Chat freeze has been lifted.", ja: "「💧 たん!」チャット凍結が解除されました。") } static var chatFreezeBlockedMessage: String { pick(ko: "채팅창이 얼려져 있어 채팅할 수 없습니다.", en: "You cannot chat while chat is frozen.", ja: "チャットが凍結中のため送信できません。") } static var chatDeleteTitle: String { pick(ko: "채팅 삭제", en: "Delete chat", ja: "チャット削除") } } diff --git a/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift b/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift index d6415df..043b7ae 100644 --- a/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift +++ b/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift @@ -2133,9 +2133,16 @@ final class LiveRoomViewModel: NSObject, ObservableObject { } private func appendChatFreezeStatusMessage(isChatFrozen: Bool) { - let statusMessage = isChatFrozen - ? I18n.LiveRoom.chatFreezeOnStatusMessage - : I18n.LiveRoom.chatFreezeOffStatusMessage + let statusMessage: String + + if isChatFrozen { + statusMessage = isCreator + ? I18n.LiveRoom.chatFreezeOnStatusMessageForCreator + : I18n.LiveRoom.chatFreezeOnStatusMessageForListener + } else { + statusMessage = I18n.LiveRoom.chatFreezeOffStatusMessage + } + messages.append(LiveRoomJoinChat(nickname: "", statusMessage: statusMessage)) } diff --git a/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomRightBottomButton.swift b/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomRightBottomButton.swift index 35ab1be..6d8b4a6 100644 --- a/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomRightBottomButton.swift +++ b/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomRightBottomButton.swift @@ -11,17 +11,20 @@ struct LiveRoomRightBottomButton: View { let imageName: String let onClick: () -> Void + let backgroundColor: Color? let onLongPress: (() -> Void)? let longPressDuration: Double init( imageName: String, onClick: @escaping () -> Void, + backgroundColor: Color? = nil, onLongPress: (() -> Void)? = nil, longPressDuration: Double = 2.0 ) { self.imageName = imageName self.onClick = onClick + self.backgroundColor = backgroundColor self.onLongPress = onLongPress self.longPressDuration = longPressDuration } @@ -31,7 +34,15 @@ struct LiveRoomRightBottomButton: View { .resizable() .frame(width: 24, height: 24) .padding(10) - .background(Color.gray52.opacity(0.6)) + .background( + backgroundColor ?? Color( + .sRGB, + red: 82 / 255, + green: 82 / 255, + blue: 82 / 255, + opacity: 0.6 + ) + ) .cornerRadius(10) .onTapGesture { onClick() } .onLongPressGesture(minimumDuration: longPressDuration) { diff --git a/SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInfoHostView.swift b/SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInfoHostView.swift index 6a085fc..052ec76 100644 --- a/SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInfoHostView.swift +++ b/SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInfoHostView.swift @@ -6,7 +6,6 @@ // import SwiftUI -import Kingfisher struct LiveRoomInfoHostView: View { @@ -19,7 +18,6 @@ struct LiveRoomInfoHostView: View { let isOnNotice: Bool let isOnMenuPan: Bool let isOnSignature: Bool - let isOnChatFreeze: Bool let isShowMenuPanButton: Bool let creatorId: Int @@ -41,7 +39,6 @@ struct LiveRoomInfoHostView: View { let onClickTotalHeart: () -> Void let onClickTotalDonation: () -> Void let onClickParticipants: () -> Void - let onClickToggleChatFreeze: () -> Void let onClickToggleSignature: () -> Void var body: some View { @@ -58,18 +55,6 @@ struct LiveRoomInfoHostView: View { Spacer() - LiveRoomOverlayStrokeTextToggleButton( - isOn: isOnChatFreeze, - onText: I18n.LiveRoom.chatFreezeOn, - onTextColor: Color.button, - onStrokeColor: Color.button, - offText: I18n.LiveRoom.chatFreezeOff, - offTextColor: Color.graybb, - offStrokeColor: Color.graybb, - strokeWidth: 1, - strokeCornerRadius: 5.3 - ) { onClickToggleChatFreeze() } - LiveRoomOverlayStrokeTextToggleButton( isOn: isOnSignature, onText: I18n.LiveRoom.signatureOn, @@ -254,7 +239,6 @@ struct LiveRoomInfoHostView_Previews: PreviewProvider { isOnNotice: true, isOnMenuPan: false, isOnSignature: false, - isOnChatFreeze: false, isShowMenuPanButton: false, creatorId: 1, creatorNickname: "도화", @@ -286,7 +270,6 @@ struct LiveRoomInfoHostView_Previews: PreviewProvider { onClickTotalHeart: {}, onClickTotalDonation: {}, onClickParticipants: {}, - onClickToggleChatFreeze: {}, onClickToggleSignature: {} ) } diff --git a/SodaLive/Sources/Live/Room/V2/LiveRoomViewV2.swift b/SodaLive/Sources/Live/Room/V2/LiveRoomViewV2.swift index 220e47f..73d230a 100644 --- a/SodaLive/Sources/Live/Room/V2/LiveRoomViewV2.swift +++ b/SodaLive/Sources/Live/Room/V2/LiveRoomViewV2.swift @@ -74,7 +74,6 @@ struct LiveRoomViewV2: View { isOnNotice: viewModel.isShowNotice, isOnMenuPan: viewModel.isShowMenuPan, isOnSignature: viewModel.isSignatureOn, - isOnChatFreeze: viewModel.isChatFrozen, isShowMenuPanButton: !liveRoomInfo.menuPan.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty, creatorId: liveRoomInfo.creatorId, creatorNickname: liveRoomInfo.creatorNickname, @@ -115,9 +114,6 @@ struct LiveRoomViewV2: View { onClickParticipants: { viewModel.isShowProfileList = true }, - onClickToggleChatFreeze: { - viewModel.setChatFreeze(isChatFrozen: !viewModel.isChatFrozen) - }, onClickToggleSignature: { viewModel.isSignatureOn.toggle() } @@ -248,11 +244,25 @@ struct LiveRoomViewV2: View { VStack(alignment: .trailing, spacing: 0) { Spacer() - - LiveRoomRightBottomButton( - imageName: viewModel.isSpeakerMute ? "ic_speaker_off" : "ic_speaker_on", - onClick: { viewModel.toggleSpeakerMute() } - ) + + VStack(spacing: 13.3) { + if liveRoomInfo.creatorId == UserDefaults.int(forKey: .userId) { + LiveRoomRightBottomButton( + imageName: "ic_ice", + onClick: { + viewModel.setChatFreeze(isChatFrozen: !viewModel.isChatFrozen) + }, + backgroundColor: viewModel.isChatFrozen + ? Color(hex: "3bb9f1").opacity(0.5) + : nil + ) + } + + LiveRoomRightBottomButton( + imageName: viewModel.isSpeakerMute ? "ic_speaker_off" : "ic_speaker_on", + onClick: { viewModel.toggleSpeakerMute() } + ) + } .padding(.bottom, 40) .padding(.trailing, 13.3) diff --git a/docs/20260320_채팅창얼림버튼및문구수정.md b/docs/20260320_채팅창얼림버튼및문구수정.md new file mode 100644 index 0000000..c8be46c --- /dev/null +++ b/docs/20260320_채팅창얼림버튼및문구수정.md @@ -0,0 +1,35 @@ +# 20260320 채팅창 얼림 버튼 및 문구 수정 + +## 작업 체크리스트 +- [x] `LiveRoomViewV2`에서 방장 전용 얼림 버튼을 스피커 음소거 버튼 위에 배치한다. +- [x] 얼림 버튼 OFF/ON 상태별 배경 스타일을 요구사항(기본 배경 / `#3bb9f1` 50%, corner radius 10)로 반영한다. +- [x] 얼림 ON/OFF 시 채팅 문구를 방장/리스너 조건으로 각각 지정된 문구로 수정한다. +- [x] 수정 파일 진단 및 빌드를 실행하고 결과를 기록한다. + +## 완료 기준 (Pass/Fail) +- [x] Pass: 방장 계정에서만 `ic_ice` 버튼이 보이고, 버튼이 스피커 음소거 버튼 바로 위에 위치한다. (QA: 화면 렌더링 코드 조건/배치 확인) +- [x] Pass: OFF 상태 배경은 우측 하단 기존 버튼과 동일하고, ON 상태는 `#3bb9f1` 50% + radius 10으로 적용된다. (QA: 버튼 스타일 코드 확인) +- [x] Pass: 얼림 ON/OFF 채팅 문구가 방장/리스너 조건에 맞게 정확히 분기된다. (QA: 얼림 메시지 생성 코드 확인) +- [x] Pass: 수정 파일 LSP 진단 에러 0, 빌드 명령 종료 코드 0. (QA: `lsp_diagnostics`, `xcodebuild`) + +## 검증 기록 +- 2026-03-20 (채팅창 얼림 버튼/문구 수정) + - 무엇/왜/어떻게: 얼림 토글을 상단 호스트 토글 영역에서 우측 하단 스피커 음소거 버튼 위로 이동하고, ON/OFF 배경 스펙 및 방장 전용 노출 조건을 반영했다. 동시에 얼림 상태 채팅 문구를 방장/리스너 역할 기준으로 분기되도록 `LiveRoomViewModel` + `I18n` 경로를 수정했다. + - 실행 명령/도구: + - `lsp_diagnostics`: + - `SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomRightBottomButton.swift` + - `SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInfoHostView.swift` + - `SodaLive/Sources/Live/Room/V2/LiveRoomViewV2.swift` + - `SodaLive/Sources/Live/Room/LiveRoomViewModel.swift` + - `SodaLive/Sources/I18n/I18n.swift` + - `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build` + - `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` + - `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test` + - `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test` + - `python3` 코드 QA 스크립트(버튼 위치/호스트 노출/문구 분기 문자열 정합성 PASS 체크) + - 결과: + - `lsp_diagnostics` 대상 5개 파일 모두 `No diagnostics found` 확인. + - `SodaLive` Debug build: `** BUILD SUCCEEDED **`. + - `SodaLive-dev` Debug build: `** BUILD SUCCEEDED **`. + - 테스트: 두 스킴 모두 `Scheme ... is not currently configured for the test action`으로 자동 테스트 실행 불가. + - 코드 QA 스크립트: `host_only_ice_button`, `ice_above_speaker`, `host_header_toggle_removed`, `on_message_creator`, `on_message_listener`, `off_message_common`, `viewmodel_role_branch` 전 항목 `PASS`.