fix(main): 현재 라이브 전체보기를 보정한다
This commit is contained in:
@@ -87,7 +87,7 @@ enum AppStep {
|
||||
onSuccess: (CreateLiveRoomResponse) -> Void
|
||||
)
|
||||
|
||||
case liveNowAll(onClickParticipant: (Int) -> Void)
|
||||
case liveNowAll
|
||||
|
||||
case liveReservationAll(
|
||||
onClickReservation: (Int) -> Void,
|
||||
|
||||
@@ -216,8 +216,8 @@ struct AppStepLayerView: View {
|
||||
onSuccess: onSuccess
|
||||
)
|
||||
|
||||
case .liveNowAll(let onClickParticipant):
|
||||
LiveNowAllView(onClickParticipant: onClickParticipant)
|
||||
case .liveNowAll:
|
||||
LiveNowAllView()
|
||||
|
||||
case .liveReservationAll(let onClickReservation, let onClickStart, let onClickCancel, let onTapCreateLive):
|
||||
LiveReservationAllView(
|
||||
|
||||
@@ -2367,7 +2367,7 @@ enum I18n {
|
||||
|
||||
enum LiveNow {
|
||||
static var allTitle: String {
|
||||
pick(ko: "지금 라이브 중 전체보기", en: "Live Now - All", ja: "ライブ配信中(全て)")
|
||||
pick(ko: "On Air", en: "On Air", ja: "On Air")
|
||||
}
|
||||
|
||||
static var sectionTitle: String {
|
||||
|
||||
@@ -60,13 +60,6 @@ struct LiveView: View {
|
||||
LazyVStack(spacing: 48) {
|
||||
SectionLiveNowView(
|
||||
items: viewModel.liveNowItems,
|
||||
onClickParticipant: {
|
||||
if !token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
viewModel.enterLiveRoom(roomId: $0)
|
||||
} else {
|
||||
AppState.shared.setAppStep(step: .login)
|
||||
}
|
||||
},
|
||||
onTapItem: { item in
|
||||
onTapLiveNowItem(item.roomId, item.isAdult)
|
||||
},
|
||||
|
||||
@@ -14,117 +14,92 @@ struct LiveNowAllItemView: View {
|
||||
let itemWidth: CGFloat
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
ZStack {
|
||||
KFImage(URL(string: item.coverImageUrl))
|
||||
.cancelOnDisappear(true)
|
||||
.downsampling(
|
||||
size: CGSize(
|
||||
width: itemWidth,
|
||||
height: itemWidth * 144 / 102
|
||||
)
|
||||
)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: itemWidth, height: itemWidth * 144 / 102, alignment: .center)
|
||||
.cornerRadius(4.7)
|
||||
.clipped()
|
||||
HStack(alignment: .center, spacing: 14) {
|
||||
KFImage(URL(string: item.creatorProfileImage))
|
||||
.cancelOnDisappear(true)
|
||||
.downsampling(size: CGSize(width: 75, height: 75))
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 75, height: 75)
|
||||
.clipShape(Circle())
|
||||
|
||||
LinearGradient(
|
||||
colors: [Color.black.opacity(0.1), Color.black.opacity(0.8)],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack(spacing: 8) {
|
||||
liveTag
|
||||
|
||||
VStack(alignment: .trailing, spacing: 0) {
|
||||
HStack(spacing: 0) {
|
||||
HStack(spacing: 1) {
|
||||
if item.price > 0 {
|
||||
Image("ic_can_white")
|
||||
}
|
||||
|
||||
Text(item.price > 0 ? "\(item.price)" : I18n.CreateContent.free)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color.white)
|
||||
}
|
||||
.padding(.horizontal, 7.3)
|
||||
.padding(.vertical, 4)
|
||||
.background(item.price > 0 ? Color.mainRed3 : Color.gray11)
|
||||
.cornerRadius(13.3)
|
||||
|
||||
Spacer()
|
||||
|
||||
if item.isPrivateRoom {
|
||||
Image("ic_lock")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
.padding(2.7)
|
||||
.background(Color.gray33.opacity(0.7))
|
||||
.clipShape(Circle())
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 3.3)
|
||||
.padding(.top, 3.3)
|
||||
|
||||
Spacer()
|
||||
|
||||
if item.numberOfPeople - item.numberOfParticipate < 3 {
|
||||
HStack(spacing: 0) {
|
||||
Spacer()
|
||||
|
||||
HStack(spacing: 2) {
|
||||
Text(I18n.LiveNow.remaining)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
Text("\(item.numberOfPeople - item.numberOfParticipate)")
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color.button)
|
||||
}
|
||||
.padding(.horizontal, 4)
|
||||
.padding(.vertical, 3)
|
||||
.background(Color.gray33.opacity(0.7))
|
||||
.cornerRadius(13.3)
|
||||
}
|
||||
.padding(.horizontal, 3.3)
|
||||
.padding(.bottom, 3.3)
|
||||
}
|
||||
Text(startTimeText)
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color.gray500)
|
||||
}
|
||||
|
||||
Text(item.title)
|
||||
.appFont(size: 18, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
|
||||
Text(item.creatorNickname)
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color.gray500)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
}
|
||||
.frame(width: itemWidth, height: itemWidth * 144 / 102)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
Text("\(item.title)")
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.lineLimit(2)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
if item.tags.count > 0 {
|
||||
Text("\(item.tags.map { "#\($0)" }.joined(separator: " "))")
|
||||
.appFont(size: 11, weight: .medium)
|
||||
.foregroundColor(Color.button)
|
||||
}
|
||||
|
||||
HStack(spacing: 5.3) {
|
||||
KFImage(URL(string: item.creatorProfileImage))
|
||||
.cancelOnDisappear(true)
|
||||
.downsampling(
|
||||
size: CGSize(
|
||||
width: 21.3,
|
||||
height: 21.3
|
||||
)
|
||||
)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 21.3, height: 21.3, alignment: .center)
|
||||
.clipShape(Circle())
|
||||
|
||||
Text("\(item.creatorNickname)")
|
||||
.appFont(size: 10, weight: .medium)
|
||||
.foregroundColor(Color.gray77)
|
||||
}
|
||||
priceView
|
||||
}
|
||||
.frame(width: itemWidth)
|
||||
.frame(width: itemWidth, height: 99)
|
||||
}
|
||||
|
||||
private var liveTag: some View {
|
||||
HStack(spacing: 4) {
|
||||
Circle()
|
||||
.fill(Color.red400)
|
||||
.frame(width: 8, height: 8)
|
||||
|
||||
Text("LIVE")
|
||||
.appFont(size: 12, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
.padding(.horizontal, 4)
|
||||
.padding(.vertical, 2)
|
||||
.frame(height: 18)
|
||||
.background(Color.black)
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var priceView: some View {
|
||||
if item.price > 0 {
|
||||
HStack(spacing: 2) {
|
||||
Image("ic_bar_cash")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 18, height: 18)
|
||||
|
||||
Text("\(item.price)")
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
.lineLimit(1)
|
||||
}
|
||||
.padding(.trailing, 7)
|
||||
.frame(width: 60, alignment: .trailing)
|
||||
} else {
|
||||
Text(I18n.CreateContent.free)
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
.lineLimit(1)
|
||||
.padding(.trailing, 7)
|
||||
.frame(width: 60, alignment: .trailing)
|
||||
}
|
||||
}
|
||||
|
||||
private var startTimeText: String {
|
||||
guard let date = DateParser.parse(item.beginDateTimeUtc) else {
|
||||
return item.beginDateTimeUtc
|
||||
}
|
||||
|
||||
return date.convertDateFormat(dateFormat: "HH:mm")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import BootpayUI
|
||||
struct LiveNowAllView: View {
|
||||
|
||||
@StateObject var viewModel = LiveViewModel()
|
||||
@StateObject var liveAllViewModel = LiveAllViewModel()
|
||||
@StateObject var mypageViewModel = MyPageViewModel()
|
||||
|
||||
let spacing: CGFloat = 16
|
||||
@@ -26,15 +25,6 @@ struct LiveNowAllView: View {
|
||||
@State private var pendingAction: (() -> Void)? = nil
|
||||
@State private var payload = Payload()
|
||||
|
||||
var columns: [GridItem] {
|
||||
[
|
||||
GridItem(.flexible(), spacing: spacing, alignment: .top),
|
||||
GridItem(.flexible(), spacing: spacing, alignment: .top)
|
||||
]
|
||||
}
|
||||
|
||||
let onClickParticipant: (Int) -> Void
|
||||
|
||||
var body: some View {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
ZStack {
|
||||
@@ -50,13 +40,13 @@ struct LiveNowAllView: View {
|
||||
viewModel.getLiveNowList()
|
||||
},
|
||||
content: {
|
||||
let itemWidth = (proxy.size.width - (spacing * 3)) / 2
|
||||
let itemWidth = proxy.size.width - (spacing * 2)
|
||||
|
||||
LazyVGrid(columns: columns, spacing: spacing) {
|
||||
LazyVStack(spacing: spacing) {
|
||||
ForEach(0..<viewModel.liveNowItems.count, id: \.self) { index in
|
||||
let item = viewModel.liveNowItems[index]
|
||||
|
||||
LiveNowItemView(item: item, itemWidth: itemWidth)
|
||||
LiveNowAllItemView(item: item, itemWidth: itemWidth)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
handleLiveNowItemTap(
|
||||
@@ -85,24 +75,6 @@ struct LiveNowAllView: View {
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
}
|
||||
|
||||
if liveAllViewModel.isShowLiveDetail {
|
||||
LiveDetailView(
|
||||
roomId: liveAllViewModel.selectedRoomId,
|
||||
onClickParticipant: {
|
||||
AppState.shared.isShowPlayer = false
|
||||
onClickParticipant(liveAllViewModel.selectedRoomId)
|
||||
},
|
||||
onClickReservation: {},
|
||||
onClickStart: {},
|
||||
onClickCancel: {},
|
||||
onClickClose: {
|
||||
withAnimation {
|
||||
liveAllViewModel.isShowLiveDetail = false
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if isShowAuthConfirmView {
|
||||
SodaDialog(
|
||||
title: I18n.Main.Auth.dialogTitle,
|
||||
@@ -120,6 +92,28 @@ struct LiveNowAllView: View {
|
||||
textAlignment: .center
|
||||
)
|
||||
}
|
||||
|
||||
if viewModel.isShowPaymentDialog {
|
||||
LivePaymentDialog(
|
||||
title: viewModel.paymentDialogTitle,
|
||||
desc: viewModel.paymentDialogDesc,
|
||||
desc2: viewModel.paymentDialogDesc2,
|
||||
confirmButtonTitle: viewModel.paymentDialogConfirmTitle,
|
||||
confirmButtonAction: viewModel.paymentDialogConfirmAction,
|
||||
cancelButtonTitle: viewModel.paymentDialogCancelTitle,
|
||||
cancelButtonAction: viewModel.hidePopup,
|
||||
startDateTime: viewModel.liveStartDate,
|
||||
nowDateTime: viewModel.nowDate
|
||||
)
|
||||
}
|
||||
|
||||
if viewModel.isShowPasswordDialog {
|
||||
LiveRoomPasswordDialog(
|
||||
isShowing: $viewModel.isShowPasswordDialog,
|
||||
can: viewModel.secretOrPasswordDialogCan,
|
||||
confirmAction: viewModel.passwordDialogConfirmAction
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
@@ -160,6 +154,7 @@ struct LiveNowAllView: View {
|
||||
isShowAuthView = false
|
||||
}
|
||||
}
|
||||
.sodaToast(isPresented: $viewModel.isShowPopup, message: viewModel.errorMessage, autohideIn: 2)
|
||||
}
|
||||
|
||||
private func handleLiveNowItemTap(roomId: Int, isAdult: Bool) {
|
||||
@@ -191,13 +186,11 @@ struct LiveNowAllView: View {
|
||||
}
|
||||
}
|
||||
|
||||
openLiveDetail(roomId: roomId)
|
||||
enterLiveRoom(roomId: roomId)
|
||||
}
|
||||
|
||||
private func openLiveDetail(roomId: Int) {
|
||||
liveAllViewModel.selectedRoomId = roomId
|
||||
withAnimation {
|
||||
liveAllViewModel.isShowLiveDetail = true
|
||||
}
|
||||
private func enterLiveRoom(roomId: Int) {
|
||||
AppState.shared.isShowPlayer = false
|
||||
viewModel.enterLiveRoom(roomId: roomId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ struct SectionLiveNowView: View {
|
||||
|
||||
let items: [GetRoomListResponse]
|
||||
|
||||
let onClickParticipant: (Int) -> Void
|
||||
let onTapItem: (GetRoomListResponse) -> Void
|
||||
let onTapCreateLive: () -> Void
|
||||
let onClickRefresh: () -> Void
|
||||
@@ -29,7 +28,7 @@ struct SectionLiveNowView: View {
|
||||
Text(I18n.Common.viewAll)
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "78909C"))
|
||||
.onTapGesture { AppState.shared.setAppStep(step: .liveNowAll(onClickParticipant: onClickParticipant)) }
|
||||
.onTapGesture { AppState.shared.setAppStep(step: .liveNowAll) }
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
@@ -95,7 +94,6 @@ struct SectionLiveNowView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SectionLiveNowView(
|
||||
items: [],
|
||||
onClickParticipant: { _ in },
|
||||
onTapItem: { _ in },
|
||||
onTapCreateLive: {},
|
||||
onClickRefresh: {}
|
||||
|
||||
@@ -519,7 +519,7 @@ struct MainView: View {
|
||||
}
|
||||
|
||||
private func handleRecommendationLiveAllTap() {
|
||||
appState.setAppStep(step: .liveNowAll(onClickParticipant: handleRecommendationLiveTap))
|
||||
appState.setAppStep(step: .liveNowAll)
|
||||
}
|
||||
|
||||
private func handleRecommendationCreatorTap(creatorId: Int) {
|
||||
|
||||
@@ -21,6 +21,56 @@
|
||||
- 기존 `SodaLive/Sources/Home/HomeApi.swift`에는 `/api/v2/home/recommendations`를 추가하지 않는다.
|
||||
- 외부 라이브러리는 추가하지 않는다.
|
||||
|
||||
### 2026-08-03 현재 라이브 전체보기 입장 로딩 표시 완료
|
||||
|
||||
- 목적: `LiveNowAllView`에서 라이브 항목 탭 후 방 상세 조회/입장 통신 중 멈춘 것처럼 보이지 않도록 기존 로딩 UI 표시
|
||||
- TDD 예외 사유:
|
||||
- SwiftUI 화면 내부 입장 흐름이며 앱 테스트 번들 타깃 검색 결과가 없어 production View를 직접 import하는 영구 UI/unit test를 이번 Task에서 추가할 수 없다.
|
||||
- 수행 내용:
|
||||
- `LiveNowAllView`가 기존 `LiveViewModel.enterLiveRoom(roomId:)`를 직접 호출하도록 변경
|
||||
- `BaseView(isLoading: $viewModel.isLoading)`를 통해 방 상세 조회/입장 요청 중 기존 `LoadingView` 표시
|
||||
- 입장 전 결제 확인 dialog, 비밀방 password dialog, 입장 실패 toast를 `LiveNowAllView`에 연결
|
||||
- `AppStep.liveNowAll`과 `SectionLiveNowView`에서 더 이상 필요 없는 입장 콜백을 제거
|
||||
- 검증:
|
||||
- `rg -n "PBXNativeTarget.*Test|UITests|Tests|xctest|SodaLiveTests|SodaLiveUITests" SodaLive.xcodeproj/project.pbxproj` 실행, 테스트 타깃 없음 확인
|
||||
- `rg -n "liveNowAll\(|liveNowAll\)|case \.liveNowAll|LiveNowAllView\(|onClickParticipant: onClickParticipant|let onClickParticipant" SodaLive/Sources` 실행
|
||||
- `rg -n "BaseView\(isLoading: \$viewModel\.isLoading\)|viewModel\.enterLiveRoom\(roomId: roomId\)|LivePaymentDialog\(|LiveRoomPasswordDialog\(|sodaToast\(isPresented: \$viewModel\.isShowPopup" SodaLive/Sources/Live/Now/All/LiveNowAllView.swift` 실행
|
||||
- `git diff --check` 실행
|
||||
- `plutil -lint SodaLive.xcodeproj/project.pbxproj` 실행
|
||||
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` 실행
|
||||
|
||||
### 2026-08-03 현재 라이브 전체보기 즉시 입장 변경 완료
|
||||
|
||||
- 목적: 추천 탭 `전체` 진입 화면에서 라이브 항목 탭 시 상세 오버레이를 거치지 않고 기존 라이브 입장 흐름으로 바로 연결
|
||||
- TDD 예외 사유:
|
||||
- SwiftUI 화면 내부 탭/가드 흐름이며 앱 테스트 번들 타깃이 없어 production View를 직접 import하는 영구 UI/unit test를 이번 Task에서 추가할 수 없다.
|
||||
- 수행 내용:
|
||||
- `LiveNowAllView`의 로그인 guard, 한국 사용자 본인인증 guard, 성인 콘텐츠 설정 guard는 유지
|
||||
- guard 통과 후 `LiveDetailView` 표시 대신 `onClickParticipant(roomId)`를 직접 호출하도록 변경
|
||||
- 기존 상세 Join 버튼 경로와 동일하게 직접 입장 직전 `AppState.shared.isShowPlayer = false` 처리 유지
|
||||
- 검증:
|
||||
- 변경 전 `rg -n "openLiveDetail\(roomId: roomId\)|LiveDetailView\(|liveAllViewModel|onClickParticipant\(liveAllViewModel.selectedRoomId\)" SodaLive/Sources/Live/Now/All/LiveNowAllView.swift` 실행, 상세 오버레이 경유 상태 확인
|
||||
- 변경 후 `rg -n "LiveDetailView\(|liveAllViewModel|openLiveDetail\(" SodaLive/Sources/Live/Now/All/LiveNowAllView.swift` 실행, 출력 없음으로 상세 오버레이 경유 제거 확인
|
||||
- 변경 후 `rg -n "enterLiveRoom\(roomId: roomId\)|onClickParticipant\(roomId\)|setAppStep\(step: \.login\)|contentViewSettings|isShowAuthConfirmView|pendingAction = \{ handleLiveNowItemTap" SodaLive/Sources/Live/Now/All/LiveNowAllView.swift` 실행, 즉시 입장 호출과 guard 유지 확인
|
||||
- `git diff --check` 실행, 출력 없이 성공
|
||||
- `plutil -lint SodaLive.xcodeproj/project.pbxproj` 실행, `OK`
|
||||
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` 실행, `BUILD SUCCEEDED`
|
||||
|
||||
### 2026-08-03 현재 라이브 전체보기 리스트 UI 보정 완료
|
||||
|
||||
- 목적: 추천 탭 `전체` 진입 화면을 Figma `185:4509` 기준의 On Air 리스트 UI로 표시
|
||||
- 수행 내용:
|
||||
- `I18n.LiveNow.allTitle`을 `On Air`로 변경
|
||||
- `LiveNowAllView`의 2열 `LiveNowItemView` 그리드를 단일 `LazyVStack` + `LiveNowAllItemView` 리스트로 변경
|
||||
- `LiveNowAllItemView`를 `75x75` 원형 프로필, `LIVE` 태그, `HH:mm` 시작 시간, 18pt bold 제목, 14pt regular 크리에이터명, 우측 가격 영역 구조로 보정
|
||||
- 유료 라이브는 `ic_bar_cash`와 가격, 무료 라이브는 가격/캔 아이콘 대신 `무료`만 표시
|
||||
- 검증:
|
||||
- `rg -n "LiveNowItemView\\(|LiveNowAllItemView\\(|DetailNavigationBar\\(title: I18n\\.LiveNow\\.allTitle\\)|static var allTitle|ic_bar_cash|ic_can_white" SodaLive/Sources/Live/Now SodaLive/Sources/I18n/I18n.swift` 실행, 변경 전 전체보기 화면의 2열 카드/기존 아이콘 상태 확인
|
||||
- `rg -n "pick\\(ko: \\"On Air\\"|LazyVStack|LiveNowAllItemView\\(item: item|Image\\(\\"ic_bar_cash\\"\\)|I18n.CreateContent.free|frame\\(width: 75, height: 75\\)|appFont\\(size: 18, weight: \\.bold\\)|DateParser.parse" SodaLive/Sources/I18n/I18n.swift SodaLive/Sources/Live/Now/All` 실행, 변경 후 제목/리스트/유료/무료/시간/타이포그래피 근거 확인
|
||||
- `git diff --check` 실행, 출력 없이 성공
|
||||
- `plutil -lint SodaLive.xcodeproj/project.pbxproj` 실행, `OK`
|
||||
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` 실행, `BUILD SUCCEEDED`
|
||||
|
||||
### 2026-08-03 Phase 8.2 현재 라이브 전체 항목 스타일 보정 완료
|
||||
|
||||
- 목적: 메인 홈 추천 탭 현재 라이브 섹션 마지막 `전체` 항목을 요청한 텍스트 전용 스타일로 표시
|
||||
|
||||
@@ -190,6 +190,7 @@ enum class RecommendedActivityType(val code: String) {
|
||||
- 민감 콘텐츠 보기 설정이 꺼져 있으면 `AppState.shared.setPendingContentSettingsGuideMessage(I18n.Settings.adultContentEnableGuide)`로 안내 문구를 예약하고 `AppState.shared.setAppStep(step: .contentViewSettings)`로 이동한다.
|
||||
- 위 조건을 통과한 경우에만 `liveDetail`, `creatorDetail`, `contentDetail`, `characterDetail` 등 실제 상세 이동을 실행한다.
|
||||
- 추천 탭 현재 라이브 아이템은 `roomId`가 항상 존재하는 값으로 취급하고, 아이템 탭 시 상세 화면을 거치지 않고 기존 `LiveViewModel.enterLiveRoom(roomId:)` 흐름을 호출해 방 상세 조회, 입장 전 결제 확인, 비밀방 비밀번호 dialog, `LiveRoomViewV2` 입장을 재사용한다.
|
||||
- 현재 라이브 전체보기에서 라이브 입장 통신 중에는 기존 `LoadingView`로 진행 중임을 표시한다.
|
||||
- 최근 활동 크리에이터에서 서버 원본 `activityType == LIVE`이고 `targetId != null`이면 `targetId`를 라이브룸 `roomId`로 사용해 기존 라이브룸 입장 흐름을 실행한다.
|
||||
- 최근 활동 크리에이터에서 서버 원본 `activityType == LIVE`이고 `targetId == null`이면 `creatorId`를 사용해 크리에이터 채널로 이동한다.
|
||||
- 최근 활동 크리에이터의 `creatorId`는 서버에서 항상 유효한 양수로 제공하지만, 앱에서도 `creatorId > 0`을 확인한 뒤에만 크리에이터 채널 이동을 실행한다. 검사를 통과하지 못하면 이동하지 않는다.
|
||||
@@ -339,6 +340,8 @@ MainHomeView
|
||||
- 추천 탭에서 `GET /api/v2/home/recommendations`를 호출하고 `success == true` 응답 데이터를 섹션별로 렌더링한다.
|
||||
- 응답 배열이 비어 있는 섹션은 화면에 표시하지 않는다.
|
||||
- 현재 라이브 섹션의 마지막 `전체` 항목은 배경 없이 medium 14 텍스트, `soda400` 색상, 세로 가운데 정렬로 표시된다.
|
||||
- 현재 라이브 `전체` 진입 화면의 상단 제목은 `On Air`로 표시하고, 라이브 아이템은 Figma `185:4509`의 리스트형 구조를 따른다.
|
||||
- 현재 라이브 `전체` 진입 화면의 유료 라이브는 `ic_bar_cash`와 가격을 표시하고, 무료 라이브는 가격과 캔 아이콘 대신 `무료`만 표시한다.
|
||||
- `추천 필모그래피`, `또 다른 모습`은 코드와 화면에 포함하지 않는다.
|
||||
- `LIVE`, `LIVE_REPLAY`, `AUDIO`, `COMMUNITY` 활동 타입이 I18n 문구로 표시된다.
|
||||
- `FeedCommunityView`에서 키워드 영역이 제거되고, 이미지/유료 미구매/유료 구매함 또는 무료 상태가 조건에 맞게 표시된다.
|
||||
|
||||
Reference in New Issue
Block a user