feat(live): 라이브룸 게스트 상단에 팔로우 버튼과 알림 옵션을 추가한다

This commit is contained in:
Yu Sung
2026-03-05 10:55:55 +09:00
parent f0763d75c2
commit ca565a2b5f
4 changed files with 84 additions and 2 deletions

View File

@@ -1278,7 +1278,12 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
.store(in: &subscription) .store(in: &subscription)
} }
func creatorFollow(creatorId: Int? = nil, isGetUserProfile: Bool = false) { func creatorFollow(
creatorId: Int? = nil,
follow: Bool = true,
notify: Bool = true,
isGetUserProfile: Bool = false
) {
var userId = 0 var userId = 0
if let creatorId = creatorId { if let creatorId = creatorId {
@@ -1290,7 +1295,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
if userId > 0 { if userId > 0 {
isLoading = true isLoading = true
userRepository.creatorFollow(creatorId: userId) userRepository.creatorFollow(creatorId: userId, follow: follow, notify: notify)
.sink { result in .sink { result in
switch result { switch result {
case .finished: case .finished:

View File

@@ -24,6 +24,7 @@ struct LiveRoomInfoGuestView: View {
let creatorId: Int let creatorId: Int
let creatorNickname: String let creatorNickname: String
let creatorProfileUrl: String let creatorProfileUrl: String
let followButtonType: FollowButtonImageType
let speakerList: [LiveRoomMember] let speakerList: [LiveRoomMember]
let muteSpeakerList: [UInt] let muteSpeakerList: [UInt]
let activeSpeakerList: [UInt] let activeSpeakerList: [UInt]
@@ -38,6 +39,7 @@ struct LiveRoomInfoGuestView: View {
let onClickMenuPan: () -> Void let onClickMenuPan: () -> Void
let onClickTotalHeart: () -> Void let onClickTotalHeart: () -> Void
let onClickTotalDonation: () -> Void let onClickTotalDonation: () -> Void
let onClickFollow: () -> Void
let onClickChangeListener: () -> Void let onClickChangeListener: () -> Void
let onClickToggleV2VCaption: () -> Void let onClickToggleV2VCaption: () -> Void
let onClickToggleSignature: () -> Void let onClickToggleSignature: () -> Void
@@ -210,6 +212,13 @@ struct LiveRoomInfoGuestView: View {
.stroke(Color.graybb, lineWidth: 1) .stroke(Color.graybb, lineWidth: 1)
) )
.onTapGesture { onClickTotalDonation() } .onTapGesture { onClickTotalDonation() }
if creatorId != UserDefaults.int(forKey: .userId) {
let asset = FollowButtonImageAsset(type: followButtonType)
asset.imageView(defaultSize: CGSize(width: 83.3, height: 26.7))
.contentShape(Rectangle())
.onTapGesture { onClickFollow() }
}
} }
} }
@@ -245,6 +254,7 @@ struct LiveRoomInfoGuestView_Previews: PreviewProvider {
creatorId: 1, creatorId: 1,
creatorNickname: "도화", creatorNickname: "도화",
creatorProfileUrl: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320", creatorProfileUrl: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320",
followButtonType: .follow,
speakerList: [ speakerList: [
LiveRoomMember( LiveRoomMember(
id: 1, id: 1,
@@ -276,6 +286,7 @@ struct LiveRoomInfoGuestView_Previews: PreviewProvider {
onClickMenuPan: {}, onClickMenuPan: {},
onClickTotalHeart: {}, onClickTotalHeart: {},
onClickTotalDonation: {}, onClickTotalDonation: {},
onClickFollow: {},
onClickChangeListener: {}, onClickChangeListener: {},
onClickToggleV2VCaption: {}, onClickToggleV2VCaption: {},
onClickToggleSignature: {} onClickToggleSignature: {}

View File

@@ -24,6 +24,8 @@ struct LiveRoomViewV2: View {
@State private var showWaterHeart: Bool = false @State private var showWaterHeart: Bool = false
@State private var waterProgress: CGFloat = 0 @State private var waterProgress: CGFloat = 0
@State private var wavePhase: CGFloat = 0 @State private var wavePhase: CGFloat = 0
@State private var isShowFollowNotifyDialog: Bool = false
@State private var guestFollowButtonTypeOverride: FollowButtonImageType? = nil
let heartWaveTimer = Timer.publish(every: 1/60, on: .main, in: .common).autoconnect() let heartWaveTimer = Timer.publish(every: 1/60, on: .main, in: .common).autoconnect()
var body: some View { var body: some View {
@@ -101,6 +103,7 @@ struct LiveRoomViewV2: View {
creatorId: liveRoomInfo.creatorId, creatorId: liveRoomInfo.creatorId,
creatorNickname: liveRoomInfo.creatorNickname, creatorNickname: liveRoomInfo.creatorNickname,
creatorProfileUrl: liveRoomInfo.creatorProfileUrl, creatorProfileUrl: liveRoomInfo.creatorProfileUrl,
followButtonType: guestFollowButtonType(liveRoomInfo: liveRoomInfo),
speakerList: liveRoomInfo.speakerList, speakerList: liveRoomInfo.speakerList,
muteSpeakerList: viewModel.muteSpeakers, muteSpeakerList: viewModel.muteSpeakers,
activeSpeakerList: viewModel.activeSpeakers, activeSpeakerList: viewModel.activeSpeakers,
@@ -131,6 +134,16 @@ struct LiveRoomViewV2: View {
onClickTotalDonation: { onClickTotalDonation: {
viewModel.isShowDonationRankingPopup = true viewModel.isShowDonationRankingPopup = true
}, },
onClickFollow: {
let buttonType = guestFollowButtonType(liveRoomInfo: liveRoomInfo)
if buttonType == .follow {
guestFollowButtonTypeOverride = .following
viewModel.creatorFollow(follow: true, notify: true)
} else {
isShowFollowNotifyDialog = true
}
},
onClickChangeListener: { onClickChangeListener: {
viewModel.setListener() viewModel.setListener()
}, },
@@ -735,6 +748,26 @@ struct LiveRoomViewV2: View {
} }
} }
} }
if isShowFollowNotifyDialog,
let liveRoomInfo = viewModel.liveRoomInfo,
liveRoomInfo.creatorId != UserDefaults.int(forKey: .userId) {
CreatorFollowNotifyDialog(
isShowing: $isShowFollowNotifyDialog,
onClickNotifyAll: {
guestFollowButtonTypeOverride = .following
viewModel.creatorFollow(follow: true, notify: true)
},
onClickNotifyNone: {
guestFollowButtonTypeOverride = .followingNoAlarm
viewModel.creatorFollow(follow: true, notify: false)
},
onClickUnFollow: {
guestFollowButtonTypeOverride = .follow
viewModel.creatorFollow(follow: false, notify: false)
}
)
}
} }
if viewModel.isShowRouletteSettings { if viewModel.isShowRouletteSettings {
@@ -890,6 +923,11 @@ struct LiveRoomViewV2: View {
.sheet(isPresented: $viewModel.isShowDonationMessagePopup) { .sheet(isPresented: $viewModel.isShowDonationMessagePopup) {
LiveRoomDonationMessageDialog(viewModel: viewModel, isShowing: $viewModel.isShowDonationMessagePopup) LiveRoomDonationMessageDialog(viewModel: viewModel, isShowing: $viewModel.isShowDonationMessagePopup)
} }
.onChange(of: viewModel.liveRoomInfo?.isFollowing) { isFollowing in
if isFollowing == false {
guestFollowButtonTypeOverride = nil
}
}
} }
private func estimatedHeight(for text: String, width: CGFloat) -> CGFloat { private func estimatedHeight(for text: String, width: CGFloat) -> CGFloat {
@@ -941,6 +979,14 @@ struct LiveRoomViewV2: View {
} }
private extension LiveRoomViewV2 { private extension LiveRoomViewV2 {
func guestFollowButtonType(liveRoomInfo: GetRoomInfoResponse) -> FollowButtonImageType {
if liveRoomInfo.isFollowing {
return guestFollowButtonTypeOverride ?? .following
}
return .follow
}
var isV2VCaptionVisible: Bool { var isV2VCaptionVisible: Bool {
viewModel.isV2VCaptionOn && viewModel.isV2VCaptionOn &&
!viewModel.v2vCaptionText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty !viewModel.v2vCaptionText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty

View File

@@ -0,0 +1,20 @@
# 20260305 라이브룸 팔로우 버튼 추가
## 구현 체크리스트
- [x] LiveRoom 상단 참여자 수 영역 구조 확인
- [x] UserProfileView의 팔로우 버튼 이미지/다이얼로그 패턴 확인
- [x] LiveRoomViewV2에 팔로우/팔로잉 버튼 노출 조건 추가 (방장 본인 제외)
- [x] 팔로잉 상태에서 언팔로우 다이얼로그 노출 및 액션 연결
- [x] 관련 파일 진단/빌드 검증 수행
## 검증 기록
- 무엇/왜/어떻게: 라이브룸 V2 게스트 상단에 `FollowButtonImageAsset` 기반 팔로우/팔로잉 이미지 버튼을 추가하고, 팔로잉 상태 탭 시 `CreatorFollowNotifyDialog`를 통해 알림 전체/알림 끔/언팔로우를 선택하도록 연결했다. 방장 본인은 버튼이 보이지 않도록 조건을 유지했다.
- 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build`
- 결과: `** BUILD SUCCEEDED **`
- 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test`
- 결과: `Scheme SodaLive is not currently configured for the test action.` (현재 스킴 테스트 액션 미구성으로 실행 불가)
- 실행 명령: `lsp_diagnostics` (수정 파일 3개)
- 결과: SourceKit 환경에서 외부 모듈(`Kingfisher`, `Moya`) 해석 불가로 오탐 오류 다수 발생. 실제 컴파일은 `xcodebuild` 성공 기준으로 검증.
- 무엇/왜/어떻게: 후속 이슈로 "알림 없음" 선택 후 버튼 이미지가 `followingNoAlarm`으로 유지되지 않는 문제를 수정했다. `GetRoomInfoResponse`에 notify 상태 필드가 없어, 게스트 상단 버튼에 로컬 override 상태를 두고 `notify=false` 선택 시 `FollowButtonImageType.followingNoAlarm`을 우선 표시하도록 반영했다.
- 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build`
- 결과: `** BUILD SUCCEEDED **`