feat(live): 라이브룸 게스트 상단에 팔로우 버튼과 알림 옵션을 추가한다
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
@@ -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: {}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
20
docs/20260305_라이브룸팔로우버튼추가.md
Normal file
20
docs/20260305_라이브룸팔로우버튼추가.md
Normal 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 **`
|
||||||
Reference in New Issue
Block a user