refactor(navigation): 전역 경로 기반 단일 내비게이션 흐름으로 전환한다
This commit is contained in:
@@ -7,13 +7,23 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct AppRoute: Hashable {
|
||||
let id = UUID()
|
||||
}
|
||||
|
||||
class AppState: ObservableObject {
|
||||
static let shared = AppState()
|
||||
|
||||
private var appStepBackStack = [AppStep]()
|
||||
private var routeStepMap: [AppRoute: AppStep] = [:]
|
||||
|
||||
@Published var alreadyUpdatedMarketingInfo = false
|
||||
@Published private(set) var appStep: AppStep = .splash
|
||||
@Published private(set) var rootStep: AppStep = .splash
|
||||
@Published var navigationPath: [AppRoute] = [] {
|
||||
didSet {
|
||||
syncStepWithNavigationPath()
|
||||
}
|
||||
}
|
||||
|
||||
@Published var isShowPlayer = false {
|
||||
didSet {
|
||||
@@ -53,28 +63,52 @@ class AppState: ObservableObject {
|
||||
@Published var isShowErrorPopup = false
|
||||
@Published var errorMessage = ""
|
||||
|
||||
func setAppStep(step: AppStep) {
|
||||
switch step {
|
||||
case .splash, .main:
|
||||
appStepBackStack.removeAll()
|
||||
|
||||
default:
|
||||
appStepBackStack.append(appStep)
|
||||
}
|
||||
private func syncStepWithNavigationPath() {
|
||||
let validRoutes = Set(navigationPath)
|
||||
routeStepMap = routeStepMap.filter { validRoutes.contains($0.key) }
|
||||
|
||||
if let route = navigationPath.last,
|
||||
let step = routeStepMap[route] {
|
||||
appStep = step
|
||||
} else {
|
||||
appStep = rootStep
|
||||
}
|
||||
}
|
||||
|
||||
func appStep(for route: AppRoute) -> AppStep? {
|
||||
routeStepMap[route]
|
||||
}
|
||||
|
||||
func setAppStep(step: AppStep) {
|
||||
DispatchQueue.main.async {
|
||||
self.appStep = step
|
||||
switch step {
|
||||
case .splash, .main:
|
||||
self.rootStep = step
|
||||
self.routeStepMap.removeAll()
|
||||
self.navigationPath.removeAll()
|
||||
self.appStep = step
|
||||
|
||||
default:
|
||||
let route = AppRoute()
|
||||
self.routeStepMap[route] = step
|
||||
self.navigationPath.append(route)
|
||||
self.appStep = step
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func back() {
|
||||
if let step = appStepBackStack.popLast() {
|
||||
self.appStep = step
|
||||
} else {
|
||||
self.appStep = .main
|
||||
DispatchQueue.main.async {
|
||||
if self.navigationPath.isEmpty {
|
||||
self.rootStep = .main
|
||||
self.appStep = .main
|
||||
return
|
||||
}
|
||||
|
||||
_ = self.navigationPath.popLast()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 언어 적용 직후 앱을 소프트 재시작(스플래시 -> 메인)하여 전역 UI를 새로고침
|
||||
func softRestart() {
|
||||
isRestartApp = true
|
||||
|
||||
@@ -14,105 +14,100 @@ struct NewCharacterListView: View {
|
||||
private let gridSpacing: CGFloat = 12
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 8) {
|
||||
// Toolbar
|
||||
DetailNavigationBar(title: String(localized: "신규 캐릭터 전체보기"))
|
||||
Group { BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 8) {
|
||||
// Toolbar
|
||||
DetailNavigationBar(title: String(localized: "신규 캐릭터 전체보기"))
|
||||
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
// 전체 n개
|
||||
HStack(spacing: 0) {
|
||||
Text("전체")
|
||||
.appFont(size: 12, weight: .regular)
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
Text(" \(viewModel.totalCount)")
|
||||
.appFont(size: 12, weight: .regular)
|
||||
.foregroundColor(Color(hex: "ff5c49"))
|
||||
Text("개")
|
||||
.appFont(size: 12, weight: .regular)
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
// 전체 n개
|
||||
HStack(spacing: 0) {
|
||||
Text("전체")
|
||||
.appFont(size: 12, weight: .regular)
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
Text(" \(viewModel.totalCount)")
|
||||
.appFont(size: 12, weight: .regular)
|
||||
.foregroundColor(Color(hex: "ff5c49"))
|
||||
Text("개")
|
||||
.appFont(size: 12, weight: .regular)
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
// Grid 3열
|
||||
GeometryReader { geo in
|
||||
let width = (geo.size.width - (horizontalPadding * 2) - gridSpacing) / 2
|
||||
|
||||
// Grid 3열
|
||||
GeometryReader { geo in
|
||||
let width = (geo.size.width - (horizontalPadding * 2) - gridSpacing) / 2
|
||||
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
LazyVGrid(
|
||||
columns: Array(
|
||||
repeating: GridItem(
|
||||
.flexible(),
|
||||
spacing: gridSpacing,
|
||||
alignment: .topLeading
|
||||
),
|
||||
count: 2
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
LazyVGrid(
|
||||
columns: Array(
|
||||
repeating: GridItem(
|
||||
.flexible(),
|
||||
spacing: gridSpacing,
|
||||
alignment: .topLeading
|
||||
),
|
||||
alignment: .leading,
|
||||
spacing: gridSpacing
|
||||
) {
|
||||
ForEach(viewModel.items.indices, id: \.self) { idx in
|
||||
let item = viewModel.items[idx]
|
||||
|
||||
NavigationLink(value: item.characterId) {
|
||||
CharacterItemView(
|
||||
character: item,
|
||||
size: width,
|
||||
rank: 0,
|
||||
isShowRank: false
|
||||
)
|
||||
.onAppear { viewModel.loadMoreIfNeeded(currentIndex: idx) }
|
||||
}
|
||||
}
|
||||
count: 2
|
||||
),
|
||||
alignment: .leading,
|
||||
spacing: gridSpacing
|
||||
) {
|
||||
ForEach(viewModel.items.indices, id: \.self) { idx in
|
||||
let item = viewModel.items[idx]
|
||||
|
||||
CharacterItemView(
|
||||
character: item,
|
||||
size: width,
|
||||
rank: 0,
|
||||
isShowRank: false
|
||||
)
|
||||
.onAppear { viewModel.loadMoreIfNeeded(currentIndex: idx) }
|
||||
.onTapGesture { AppState.shared.setAppStep(step: .characterDetail(characterId: item.characterId)) }
|
||||
}
|
||||
.padding(.horizontal, horizontalPadding)
|
||||
|
||||
if viewModel.isLoadingMore {
|
||||
HStack {
|
||||
Spacer()
|
||||
ProgressView()
|
||||
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
||||
.padding(.vertical, 16)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, horizontalPadding)
|
||||
|
||||
if viewModel.isLoadingMore {
|
||||
HStack {
|
||||
Spacer()
|
||||
ProgressView()
|
||||
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
||||
.padding(.vertical, 16)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(minHeight: 0, maxHeight: .infinity)
|
||||
}
|
||||
.padding(.vertical, 12)
|
||||
.onAppear {
|
||||
// 최초 1회만 로드하여 상세 진입 후 복귀 시 스크롤 위치가 유지되도록 함
|
||||
if viewModel.items.isEmpty {
|
||||
viewModel.fetch()
|
||||
}
|
||||
}
|
||||
.frame(minHeight: 0, maxHeight: .infinity)
|
||||
}
|
||||
.background(Color.black)
|
||||
}
|
||||
.popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .top, autohideIn: 2) {
|
||||
GeometryReader { geo in
|
||||
HStack {
|
||||
Spacer()
|
||||
Text(viewModel.errorMessage)
|
||||
.padding(.vertical, 13.3)
|
||||
.frame(width: geo.size.width - 66.7, alignment: .center)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.background(Color.button)
|
||||
.foregroundColor(Color.white)
|
||||
.multilineTextAlignment(.center)
|
||||
.cornerRadius(20)
|
||||
.padding(.top, 66.7)
|
||||
Spacer()
|
||||
.padding(.vertical, 12)
|
||||
.onAppear {
|
||||
// 최초 1회만 로드하여 상세 진입 후 복귀 시 스크롤 위치가 유지되도록 함
|
||||
if viewModel.items.isEmpty {
|
||||
viewModel.fetch()
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationDestination(for: Int.self) { characterId in
|
||||
CharacterDetailView(characterId: characterId)
|
||||
.background(Color.black)
|
||||
}
|
||||
.popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .top, autohideIn: 2) {
|
||||
GeometryReader { geo in
|
||||
HStack {
|
||||
Spacer()
|
||||
Text(viewModel.errorMessage)
|
||||
.padding(.vertical, 13.3)
|
||||
.frame(width: geo.size.width - 66.7, alignment: .center)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.background(Color.button)
|
||||
.foregroundColor(Color.white)
|
||||
.multilineTextAlignment(.center)
|
||||
.cornerRadius(20)
|
||||
.padding(.top, 66.7)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,91 +15,87 @@ struct OriginalWorkDetailView: View {
|
||||
let originalId: Int
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
ZStack(alignment: .top) {
|
||||
if let imageUrl = viewModel.response?.imageUrl {
|
||||
KFImage(URL(string: imageUrl))
|
||||
.cancelOnDisappear(true)
|
||||
Group { BaseView(isLoading: $viewModel.isLoading) {
|
||||
ZStack(alignment: .top) {
|
||||
if let imageUrl = viewModel.response?.imageUrl {
|
||||
KFImage(URL(string: imageUrl))
|
||||
.cancelOnDisappear(true)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: screenSize().width, height: (168 * 288 / 306) + 56)
|
||||
.clipped()
|
||||
.blur(radius: 25)
|
||||
}
|
||||
|
||||
Color.black.opacity(0.5).ignoresSafeArea()
|
||||
|
||||
VStack(spacing: 0) {
|
||||
HStack(spacing: 0) {
|
||||
Image("ic_back")
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: screenSize().width, height: (168 * 288 / 306) + 56)
|
||||
.clipped()
|
||||
.blur(radius: 25)
|
||||
}
|
||||
|
||||
Color.black.opacity(0.5).ignoresSafeArea()
|
||||
|
||||
VStack(spacing: 0) {
|
||||
HStack(spacing: 0) {
|
||||
Image("ic_back")
|
||||
.resizable()
|
||||
.frame(width: 24, height: 24)
|
||||
.onTapGesture {
|
||||
AppState.shared.back()
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
.frame(height: 56)
|
||||
.frame(width: 24, height: 24)
|
||||
.onTapGesture {
|
||||
AppState.shared.back()
|
||||
}
|
||||
|
||||
if let response = viewModel.response {
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
VStack(spacing: 0) {
|
||||
OriginalWorkDetailHeaderView(item: response)
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.bottom, 24)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
SeriesDetailTabView(
|
||||
title: I18n.Tab.character,
|
||||
width: screenSize().width / 2,
|
||||
isSelected: viewModel.currentTab == .character
|
||||
) {
|
||||
if viewModel.currentTab != .character {
|
||||
viewModel.currentTab = .character
|
||||
}
|
||||
}
|
||||
|
||||
SeriesDetailTabView(
|
||||
title: I18n.Tab.workInfo,
|
||||
width: screenSize().width / 2,
|
||||
isSelected: viewModel.currentTab == .info
|
||||
) {
|
||||
if viewModel.currentTab != .info {
|
||||
viewModel.currentTab = .info
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
.frame(height: 56)
|
||||
|
||||
if let response = viewModel.response {
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
VStack(spacing: 0) {
|
||||
OriginalWorkDetailHeaderView(item: response)
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.bottom, 24)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
SeriesDetailTabView(
|
||||
title: I18n.Tab.character,
|
||||
width: screenSize().width / 2,
|
||||
isSelected: viewModel.currentTab == .character
|
||||
) {
|
||||
if viewModel.currentTab != .character {
|
||||
viewModel.currentTab = .character
|
||||
}
|
||||
}
|
||||
.background(Color.black)
|
||||
|
||||
Rectangle()
|
||||
.foregroundColor(Color.gray90.opacity(0.5))
|
||||
.frame(height: 1)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
switch(viewModel.currentTab) {
|
||||
case .info:
|
||||
OriginalWorkInfoView(response: response)
|
||||
default:
|
||||
OriginalWorkCharacterView(characters: viewModel.characters)
|
||||
SeriesDetailTabView(
|
||||
title: I18n.Tab.workInfo,
|
||||
width: screenSize().width / 2,
|
||||
isSelected: viewModel.currentTab == .info
|
||||
) {
|
||||
if viewModel.currentTab != .info {
|
||||
viewModel.currentTab = .info
|
||||
}
|
||||
}
|
||||
}
|
||||
.background(Color.black)
|
||||
|
||||
Rectangle()
|
||||
.foregroundColor(Color.gray90.opacity(0.5))
|
||||
.frame(height: 1)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
switch(viewModel.currentTab) {
|
||||
case .info:
|
||||
OriginalWorkInfoView(response: response)
|
||||
default:
|
||||
OriginalWorkCharacterView(characters: viewModel.characters)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if viewModel.response == nil {
|
||||
viewModel.originalId = originalId
|
||||
}
|
||||
}
|
||||
.navigationDestination(for: Int.self) { characterId in
|
||||
CharacterDetailView(characterId: characterId)
|
||||
}
|
||||
.onAppear {
|
||||
if viewModel.response == nil {
|
||||
viewModel.originalId = originalId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,14 +125,13 @@ struct OriginalWorkCharacterView: View {
|
||||
ForEach(characters.indices, id: \.self) { idx in
|
||||
let item = characters[idx]
|
||||
|
||||
NavigationLink(value: item.characterId) {
|
||||
CharacterItemView(
|
||||
character: item,
|
||||
size: width,
|
||||
rank: 0,
|
||||
isShowRank: false
|
||||
)
|
||||
}
|
||||
CharacterItemView(
|
||||
character: item,
|
||||
size: width,
|
||||
rank: 0,
|
||||
isShowRank: false
|
||||
)
|
||||
.onTapGesture { AppState.shared.setAppStep(step: .characterDetail(characterId: item.characterId)) }
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, horizontalPadding)
|
||||
|
||||
@@ -9,11 +9,12 @@ import SwiftUI
|
||||
|
||||
struct ContentAllByThemeView: View {
|
||||
@StateObject var viewModel = ContentAllByThemeViewModel()
|
||||
@State private var isInitialized = false
|
||||
|
||||
let themeId: Int
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Group {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
DetailNavigationBar(title: viewModel.theme)
|
||||
@@ -111,8 +112,11 @@ struct ContentAllByThemeView: View {
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
viewModel.themeId = themeId
|
||||
viewModel.getContentList()
|
||||
if !isInitialized || viewModel.themeId != themeId {
|
||||
viewModel.themeId = themeId
|
||||
viewModel.getContentList()
|
||||
isInitialized = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,13 @@ import SwiftUI
|
||||
struct ContentAllView: View {
|
||||
|
||||
@StateObject var viewModel = ContentAllViewModel()
|
||||
@State private var isInitialized = false
|
||||
|
||||
var isFree: Bool = false
|
||||
var isPointAvailableOnly: Bool = false
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Group {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 0) {
|
||||
DetailNavigationBar(title: isFree ? String(localized: "무료 콘텐츠 전체") : isPointAvailableOnly ? String(localized: "포인트 대여 전체") : String(localized: "콘텐츠 전체"))
|
||||
@@ -78,63 +79,63 @@ struct ContentAllView: View {
|
||||
ForEach(viewModel.contentList.indices, id: \.self) { idx in
|
||||
let item = viewModel.contentList[idx]
|
||||
|
||||
NavigationLink {
|
||||
ContentDetailView(contentId: item.contentId)
|
||||
} label: {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
ZStack(alignment: .top) {
|
||||
DownsampledKFImage(
|
||||
url: URL(string: item.coverImageUrl),
|
||||
size: CGSize(width: itemSize, height: itemSize)
|
||||
)
|
||||
.cornerRadius(16)
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
ZStack(alignment: .top) {
|
||||
DownsampledKFImage(
|
||||
url: URL(string: item.coverImageUrl),
|
||||
size: CGSize(width: itemSize, height: itemSize)
|
||||
)
|
||||
.cornerRadius(16)
|
||||
|
||||
HStack(alignment: .top, spacing: 0) {
|
||||
Spacer()
|
||||
|
||||
HStack(alignment: .top, spacing: 0) {
|
||||
Spacer()
|
||||
|
||||
if item.isPointAvailable {
|
||||
Image("ic_point")
|
||||
.padding(.top, 6)
|
||||
.padding(.trailing, 6)
|
||||
}
|
||||
if item.isPointAvailable {
|
||||
Image("ic_point")
|
||||
.padding(.top, 6)
|
||||
.padding(.trailing, 6)
|
||||
}
|
||||
}
|
||||
|
||||
Text(item.title)
|
||||
.appFont(size: 18, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
.multilineTextAlignment(.leading)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.lineLimit(1)
|
||||
.padding(.horizontal, 6)
|
||||
.padding(.top, 8)
|
||||
|
||||
|
||||
Text(item.creatorNickname)
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "78909C"))
|
||||
.lineLimit(1)
|
||||
.padding(.horizontal, 6)
|
||||
.padding(.top, 4)
|
||||
}
|
||||
.frame(width: itemSize)
|
||||
.contentShape(Rectangle())
|
||||
.onAppear {
|
||||
if idx == viewModel.contentList.count - 1 {
|
||||
viewModel.fetchData()
|
||||
}
|
||||
|
||||
Text(item.title)
|
||||
.appFont(size: 18, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
.multilineTextAlignment(.leading)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.lineLimit(1)
|
||||
.padding(.horizontal, 6)
|
||||
.padding(.top, 8)
|
||||
|
||||
|
||||
Text(item.creatorNickname)
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "78909C"))
|
||||
.lineLimit(1)
|
||||
.padding(.horizontal, 6)
|
||||
.padding(.top, 4)
|
||||
}
|
||||
.frame(width: itemSize)
|
||||
.contentShape(Rectangle())
|
||||
.onAppear {
|
||||
if idx == viewModel.contentList.count - 1 {
|
||||
viewModel.fetchData()
|
||||
}
|
||||
}
|
||||
.onTapGesture { AppState.shared.setAppStep(step: .contentDetail(contentId: item.contentId)) }
|
||||
}
|
||||
}
|
||||
.padding(horizontalPadding)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
viewModel.isFree = isFree
|
||||
viewModel.isPointAvailableOnly = isPointAvailableOnly
|
||||
viewModel.getThemeList()
|
||||
viewModel.fetchData()
|
||||
if !isInitialized || viewModel.isFree != isFree || viewModel.isPointAvailableOnly != isPointAvailableOnly {
|
||||
viewModel.isFree = isFree
|
||||
viewModel.isPointAvailableOnly = isPointAvailableOnly
|
||||
viewModel.getThemeList()
|
||||
viewModel.fetchData()
|
||||
isInitialized = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,95 +14,92 @@ struct ContentNewAllItemView: View {
|
||||
let item: GetAudioContentMainItem
|
||||
|
||||
var body: some View {
|
||||
NavigationLink {
|
||||
ContentDetailView(contentId: item.contentId)
|
||||
} label: {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
ZStack(alignment: .bottom) {
|
||||
KFImage(URL(string: item.coverImageUrl))
|
||||
.cancelOnDisappear(true)
|
||||
.downsampling(
|
||||
size: CGSize(
|
||||
width: width,
|
||||
height: width
|
||||
)
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
ZStack(alignment: .bottom) {
|
||||
KFImage(URL(string: item.coverImageUrl))
|
||||
.cancelOnDisappear(true)
|
||||
.downsampling(
|
||||
size: CGSize(
|
||||
width: width,
|
||||
height: width
|
||||
)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: width, height: width, alignment: .top)
|
||||
.cornerRadius(2.7)
|
||||
)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: width, height: width, alignment: .top)
|
||||
.cornerRadius(2.7)
|
||||
|
||||
VStack(spacing: 0) {
|
||||
Spacer()
|
||||
|
||||
VStack(spacing: 0) {
|
||||
Spacer()
|
||||
|
||||
HStack(spacing: 0) {
|
||||
HStack(spacing: 2) {
|
||||
if item.price > 0 {
|
||||
Image("ic_card_can_gray")
|
||||
|
||||
Text("\(item.price)")
|
||||
.appFont(size: 8.5, weight: .medium)
|
||||
.foregroundColor(Color.white)
|
||||
} else {
|
||||
Text("무료")
|
||||
.appFont(size: 8.5, weight: .medium)
|
||||
.foregroundColor(Color.white)
|
||||
}
|
||||
}
|
||||
.padding(3)
|
||||
.background(Color(hex: "333333").opacity(0.7))
|
||||
.cornerRadius(10)
|
||||
.padding(.leading, 2.7)
|
||||
.padding(.bottom, 2.7)
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack(spacing: 2) {
|
||||
Text(item.duration)
|
||||
HStack(spacing: 0) {
|
||||
HStack(spacing: 2) {
|
||||
if item.price > 0 {
|
||||
Image("ic_card_can_gray")
|
||||
|
||||
Text("\(item.price)")
|
||||
.appFont(size: 8.5, weight: .medium)
|
||||
.foregroundColor(Color.white)
|
||||
} else {
|
||||
Text("무료")
|
||||
.appFont(size: 8.5, weight: .medium)
|
||||
.foregroundColor(Color.white)
|
||||
}
|
||||
.padding(3)
|
||||
.background(Color(hex: "333333").opacity(0.7))
|
||||
.cornerRadius(10)
|
||||
.padding(.trailing, 2.7)
|
||||
.padding(.bottom, 2.7)
|
||||
}
|
||||
.padding(3)
|
||||
.background(Color(hex: "333333").opacity(0.7))
|
||||
.cornerRadius(10)
|
||||
.padding(.leading, 2.7)
|
||||
.padding(.bottom, 2.7)
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack(spacing: 2) {
|
||||
Text(item.duration)
|
||||
.appFont(size: 8.5, weight: .medium)
|
||||
.foregroundColor(Color.white)
|
||||
}
|
||||
.padding(3)
|
||||
.background(Color(hex: "333333").opacity(0.7))
|
||||
.cornerRadius(10)
|
||||
.padding(.trailing, 2.7)
|
||||
.padding(.bottom, 2.7)
|
||||
}
|
||||
}
|
||||
.frame(width: width, height: width)
|
||||
|
||||
Text(item.title)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color(hex: "d2d2d2"))
|
||||
.frame(width: width, alignment: .leading)
|
||||
.multilineTextAlignment(.leading)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.lineLimit(2)
|
||||
|
||||
HStack(spacing: 5.3) {
|
||||
KFImage(URL(string: item.creatorProfileImageUrl))
|
||||
.cancelOnDisappear(true)
|
||||
.downsampling(
|
||||
size: CGSize(
|
||||
width: 21.3,
|
||||
height: 21.3
|
||||
)
|
||||
)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 21.3, height: 21.3)
|
||||
.clipShape(Circle())
|
||||
.onTapGesture { AppState.shared.setAppStep(step: .creatorDetail(userId: item.creatorId)) }
|
||||
|
||||
Text(item.creatorNickname)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
.lineLimit(1)
|
||||
}
|
||||
.padding(.bottom, 10)
|
||||
}
|
||||
.frame(width: width)
|
||||
.frame(width: width, height: width)
|
||||
|
||||
Text(item.title)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color(hex: "d2d2d2"))
|
||||
.frame(width: width, alignment: .leading)
|
||||
.multilineTextAlignment(.leading)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.lineLimit(2)
|
||||
|
||||
HStack(spacing: 5.3) {
|
||||
KFImage(URL(string: item.creatorProfileImageUrl))
|
||||
.cancelOnDisappear(true)
|
||||
.downsampling(
|
||||
size: CGSize(
|
||||
width: 21.3,
|
||||
height: 21.3
|
||||
)
|
||||
)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 21.3, height: 21.3)
|
||||
.clipShape(Circle())
|
||||
.onTapGesture { AppState.shared.setAppStep(step: .creatorDetail(userId: item.creatorId)) }
|
||||
|
||||
Text(item.creatorNickname)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
.lineLimit(1)
|
||||
}
|
||||
.padding(.bottom, 10)
|
||||
}
|
||||
.frame(width: width)
|
||||
.onTapGesture { AppState.shared.setAppStep(step: .contentDetail(contentId: item.contentId)) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,12 @@ import SwiftUI
|
||||
struct ContentNewAllView: View {
|
||||
|
||||
@StateObject var viewModel = ContentNewAllViewModel()
|
||||
@State private var isInitialized = false
|
||||
|
||||
let isFree: Bool
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Group {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(alignment: .leading, spacing: 13.3) {
|
||||
DetailNavigationBar(title: isFree ? "최신 무료 콘텐츠" : "최신 콘텐츠")
|
||||
@@ -82,9 +83,12 @@ struct ContentNewAllView: View {
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
viewModel.isFree = isFree
|
||||
viewModel.getThemeList()
|
||||
viewModel.getNewContentList()
|
||||
if !isInitialized || viewModel.isFree != isFree {
|
||||
viewModel.isFree = isFree
|
||||
viewModel.getThemeList()
|
||||
viewModel.getNewContentList()
|
||||
isInitialized = true
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationBarHidden(true)
|
||||
|
||||
@@ -11,9 +11,10 @@ import Kingfisher
|
||||
struct ContentRankingAllView: View {
|
||||
|
||||
@StateObject var viewModel = ContentRankingAllViewModel()
|
||||
@State private var isInitialized = false
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Group {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 0) {
|
||||
DetailNavigationBar(title: "인기 콘텐츠")
|
||||
@@ -44,97 +45,94 @@ struct ContentRankingAllView: View {
|
||||
LazyVStack(spacing: 20) {
|
||||
ForEach(0..<viewModel.contentRankingItemList.count, id: \.self) { index in
|
||||
let item = viewModel.contentRankingItemList[index]
|
||||
NavigationLink {
|
||||
ContentDetailView(contentId: item.contentId)
|
||||
} label: {
|
||||
HStack(spacing: 0) {
|
||||
KFImage(URL(string: item.coverImageUrl))
|
||||
.cancelOnDisappear(true)
|
||||
.downsampling(
|
||||
size: CGSize(
|
||||
width: 66.7,
|
||||
height: 66.7
|
||||
)
|
||||
HStack(spacing: 0) {
|
||||
KFImage(URL(string: item.coverImageUrl))
|
||||
.cancelOnDisappear(true)
|
||||
.downsampling(
|
||||
size: CGSize(
|
||||
width: 66.7,
|
||||
height: 66.7
|
||||
)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 66.7, height: 66.7, alignment: .top)
|
||||
.clipped()
|
||||
.cornerRadius(5.3)
|
||||
|
||||
Text("\(index + 1)")
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color(hex: "3bb9f1"))
|
||||
.padding(.horizontal, 12)
|
||||
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
HStack(spacing: 8) {
|
||||
Text(item.themeStr)
|
||||
.appFont(size: 8, weight: .medium)
|
||||
.foregroundColor(Color(hex: "3bac6a"))
|
||||
.padding(2.6)
|
||||
.background(Color(hex: "28312b"))
|
||||
.cornerRadius(2.6)
|
||||
|
||||
Text(item.duration)
|
||||
.appFont(size: 8, weight: .medium)
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
.padding(2.6)
|
||||
.background(Color(hex: "222222"))
|
||||
.cornerRadius(2.6)
|
||||
|
||||
if item.isPointAvailable {
|
||||
Text("포인트")
|
||||
.appFont(size: 8, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
.padding(2.6)
|
||||
.background(Color(hex: "7849bc"))
|
||||
.cornerRadius(2.6)
|
||||
}
|
||||
}
|
||||
|
||||
Text(item.creatorNickname)
|
||||
.appFont(size: 10.7, weight: .medium)
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
.padding(.vertical, 8)
|
||||
|
||||
Text(item.title)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color(hex: "d2d2d2"))
|
||||
.lineLimit(2)
|
||||
.padding(.top, 2.7)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if item.price > 0 {
|
||||
HStack(spacing: 8) {
|
||||
Image("ic_can")
|
||||
.resizable()
|
||||
.frame(width: 17, height: 17)
|
||||
|
||||
Text("\(item.price)")
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color(hex: "909090"))
|
||||
}
|
||||
} else {
|
||||
Text("무료")
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color(hex: "ffffff"))
|
||||
.padding(.horizontal, 5.3)
|
||||
.padding(.vertical, 2.7)
|
||||
.background(Color(hex: "cf5c37"))
|
||||
)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 66.7, height: 66.7, alignment: .top)
|
||||
.clipped()
|
||||
.cornerRadius(5.3)
|
||||
|
||||
Text("\(index + 1)")
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color(hex: "3bb9f1"))
|
||||
.padding(.horizontal, 12)
|
||||
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
HStack(spacing: 8) {
|
||||
Text(item.themeStr)
|
||||
.appFont(size: 8, weight: .medium)
|
||||
.foregroundColor(Color(hex: "3bac6a"))
|
||||
.padding(2.6)
|
||||
.background(Color(hex: "28312b"))
|
||||
.cornerRadius(2.6)
|
||||
|
||||
Text(item.duration)
|
||||
.appFont(size: 8, weight: .medium)
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
.padding(2.6)
|
||||
.background(Color(hex: "222222"))
|
||||
.cornerRadius(2.6)
|
||||
|
||||
if item.isPointAvailable {
|
||||
Text("포인트")
|
||||
.appFont(size: 8, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
.padding(2.6)
|
||||
.background(Color(hex: "7849bc"))
|
||||
.cornerRadius(2.6)
|
||||
}
|
||||
}
|
||||
|
||||
Text(item.creatorNickname)
|
||||
.appFont(size: 10.7, weight: .medium)
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
.padding(.vertical, 8)
|
||||
|
||||
Text(item.title)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color(hex: "d2d2d2"))
|
||||
.lineLimit(2)
|
||||
.padding(.top, 2.7)
|
||||
}
|
||||
.frame(height: 66.7)
|
||||
.contentShape(Rectangle())
|
||||
.onAppear {
|
||||
if index == viewModel.contentRankingItemList.count - 1 {
|
||||
viewModel.getContentRanking()
|
||||
|
||||
Spacer()
|
||||
|
||||
if item.price > 0 {
|
||||
HStack(spacing: 8) {
|
||||
Image("ic_can")
|
||||
.resizable()
|
||||
.frame(width: 17, height: 17)
|
||||
|
||||
Text("\(item.price)")
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color(hex: "909090"))
|
||||
}
|
||||
} else {
|
||||
Text("무료")
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color(hex: "ffffff"))
|
||||
.padding(.horizontal, 5.3)
|
||||
.padding(.vertical, 2.7)
|
||||
.background(Color(hex: "cf5c37"))
|
||||
.cornerRadius(2.6)
|
||||
}
|
||||
}
|
||||
.frame(height: 66.7)
|
||||
.contentShape(Rectangle())
|
||||
.onAppear {
|
||||
if index == viewModel.contentRankingItemList.count - 1 {
|
||||
viewModel.getContentRanking()
|
||||
}
|
||||
}
|
||||
.onTapGesture { AppState.shared.setAppStep(step: .contentDetail(contentId: item.contentId)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,8 +163,11 @@ struct ContentRankingAllView: View {
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
viewModel.getContentRankingSortType()
|
||||
viewModel.getContentRanking()
|
||||
if !isInitialized {
|
||||
viewModel.getContentRankingSortType()
|
||||
viewModel.getContentRanking()
|
||||
isInitialized = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ struct ContentBoxView: View {
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
NavigationView {
|
||||
Group {
|
||||
VStack(spacing: 13.3) {
|
||||
DetailNavigationBar(title: I18n.ContentBox.title)
|
||||
|
||||
|
||||
@@ -11,9 +11,10 @@ struct ContentListView: View {
|
||||
|
||||
let userId: Int
|
||||
@StateObject var viewModel = ContentListViewModel()
|
||||
@State private var isInitialized = false
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Group {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 0) {
|
||||
HStack(spacing: 0) {
|
||||
@@ -128,17 +129,14 @@ struct ContentListView: View {
|
||||
|
||||
ForEach(0..<viewModel.audioContentList.count, id: \.self) { index in
|
||||
let audioContent = viewModel.audioContentList[index]
|
||||
NavigationLink {
|
||||
ContentDetailView(contentId: audioContent.contentId)
|
||||
} label: {
|
||||
ContentListItemView(item: audioContent)
|
||||
.contentShape(Rectangle())
|
||||
.onAppear {
|
||||
if index == viewModel.audioContentList.count - 1 {
|
||||
viewModel.getAudioContentList()
|
||||
}
|
||||
ContentListItemView(item: audioContent)
|
||||
.contentShape(Rectangle())
|
||||
.onAppear {
|
||||
if index == viewModel.audioContentList.count - 1 {
|
||||
viewModel.getAudioContentList()
|
||||
}
|
||||
}
|
||||
}
|
||||
.onTapGesture { AppState.shared.setAppStep(step: .contentDetail(contentId: audioContent.contentId)) }
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 13.3)
|
||||
@@ -147,9 +145,12 @@ struct ContentListView: View {
|
||||
.padding(.top, 13.3)
|
||||
}
|
||||
.onAppear {
|
||||
viewModel.userId = userId
|
||||
viewModel.getCategoryList()
|
||||
viewModel.getAudioContentList()
|
||||
if !isInitialized || viewModel.userId != userId {
|
||||
viewModel.userId = userId
|
||||
viewModel.getCategoryList()
|
||||
viewModel.getAudioContentList()
|
||||
isInitialized = true
|
||||
}
|
||||
}
|
||||
.popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .bottom, autohideIn: 2) {
|
||||
HStack {
|
||||
|
||||
@@ -10,6 +10,7 @@ import SwiftUI
|
||||
struct ContentCurationView: View {
|
||||
|
||||
@StateObject var viewModel = ContentCurationViewModel()
|
||||
@State private var isInitialized = false
|
||||
|
||||
let title: String
|
||||
let curationId: Int
|
||||
@@ -21,7 +22,7 @@ struct ContentCurationView: View {
|
||||
]
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Group {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 0) {
|
||||
DetailNavigationBar(title: title)
|
||||
@@ -119,8 +120,11 @@ struct ContentCurationView: View {
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
viewModel.curationId = curationId
|
||||
viewModel.getContentList()
|
||||
if !isInitialized || viewModel.curationId != curationId {
|
||||
viewModel.curationId = curationId
|
||||
viewModel.getContentList()
|
||||
isInitialized = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ struct AudioContentCommentListView: View {
|
||||
@State private var isShowMemberProfilePopup: Bool = false
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Group {
|
||||
ZStack {
|
||||
VStack(spacing: 0) {
|
||||
HStack(spacing: 0) {
|
||||
|
||||
@@ -422,12 +422,15 @@ struct ContentDetailView: View {
|
||||
.sheet(
|
||||
isPresented: $isShowCommentListView,
|
||||
content: {
|
||||
AudioContentCommentListView(
|
||||
isPresented: $isShowCommentListView,
|
||||
creatorId: viewModel.audioContent!.creator.creatorId,
|
||||
audioContentId: viewModel.audioContent!.contentId,
|
||||
isShowSecret: viewModel.audioContent!.existOrdered
|
||||
)
|
||||
NavigationStack {
|
||||
AudioContentCommentListView(
|
||||
isPresented: $isShowCommentListView,
|
||||
creatorId: viewModel.audioContent!.creator.creatorId,
|
||||
audioContentId: viewModel.audioContent!.contentId,
|
||||
isShowSecret: viewModel.audioContent!.existOrdered
|
||||
)
|
||||
}
|
||||
.toolbar(.hidden, for: .navigationBar)
|
||||
}
|
||||
)
|
||||
.popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .top, autohideIn: 2) {
|
||||
|
||||
@@ -10,9 +10,10 @@ import SwiftUI
|
||||
struct ContentMainAlarmAllView: View {
|
||||
|
||||
@StateObject var viewModel = ContentMainAlarmAllViewModel()
|
||||
@State private var isInitialized = false
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Group {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(alignment: .leading, spacing: 13.3) {
|
||||
DetailNavigationBar(title: "새로운 알람")
|
||||
@@ -81,7 +82,10 @@ struct ContentMainAlarmAllView: View {
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
viewModel.getContentMainAlarmAll()
|
||||
if !isInitialized {
|
||||
viewModel.getContentMainAlarmAll()
|
||||
isInitialized = true
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationBarHidden(true)
|
||||
|
||||
@@ -10,9 +10,10 @@ import SwiftUI
|
||||
struct ContentMainAsmrAllView: View {
|
||||
|
||||
@StateObject var viewModel = ContentNewAllViewModel()
|
||||
@State private var isInitialized = false
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Group {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(alignment: .leading, spacing: 13.3) {
|
||||
DetailNavigationBar(title: "새로운 ASMR")
|
||||
@@ -72,7 +73,14 @@ struct ContentMainAsmrAllView: View {
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
viewModel.selectedTheme = "ASMR"
|
||||
if !isInitialized {
|
||||
if viewModel.selectedTheme != "ASMR" {
|
||||
viewModel.selectedTheme = "ASMR"
|
||||
} else if viewModel.newContentList.isEmpty {
|
||||
viewModel.getNewContentList()
|
||||
}
|
||||
isInitialized = true
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationBarHidden(true)
|
||||
|
||||
@@ -46,7 +46,7 @@ struct ContentMainViewV2: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Group {
|
||||
ZStack {
|
||||
Color.black.ignoresSafeArea()
|
||||
|
||||
|
||||
@@ -10,9 +10,10 @@ import SwiftUI
|
||||
struct ContentMainIntroduceCreatorAllView: View {
|
||||
|
||||
@StateObject var viewModel = ContentMainIntroduceCreatorAllViewModel()
|
||||
@State private var isInitialized = false
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Group {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 13.3) {
|
||||
DetailNavigationBar(title: "크리에이터 소개")
|
||||
@@ -48,7 +49,10 @@ struct ContentMainIntroduceCreatorAllView: View {
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
viewModel.getIntroduceCreatorList()
|
||||
if !isInitialized {
|
||||
viewModel.getIntroduceCreatorList()
|
||||
isInitialized = true
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationBarHidden(true)
|
||||
|
||||
@@ -10,9 +10,10 @@ import SwiftUI
|
||||
struct ContentMainReplayAllView: View {
|
||||
|
||||
@StateObject var viewModel = ContentNewAllViewModel()
|
||||
@State private var isInitialized = false
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Group {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(alignment: .leading, spacing: 13.3) {
|
||||
DetailNavigationBar(title: "새로운 라이브 다시듣기")
|
||||
@@ -72,7 +73,14 @@ struct ContentMainReplayAllView: View {
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
viewModel.selectedTheme = "다시듣기"
|
||||
if !isInitialized {
|
||||
if viewModel.selectedTheme != "다시듣기" {
|
||||
viewModel.selectedTheme = "다시듣기"
|
||||
} else if viewModel.newContentList.isEmpty {
|
||||
viewModel.getNewContentList()
|
||||
}
|
||||
isInitialized = true
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationBarHidden(true)
|
||||
|
||||
@@ -10,6 +10,7 @@ import SwiftUI
|
||||
struct SeriesMainByGenreView: View {
|
||||
|
||||
@StateObject var viewModel = SeriesMainByGenreViewModel()
|
||||
@State private var isInitialized = false
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
@@ -41,17 +42,16 @@ struct SeriesMainByGenreView: View {
|
||||
) {
|
||||
ForEach(viewModel.seriesList.indices, id: \.self) { index in
|
||||
let item = viewModel.seriesList[index]
|
||||
NavigationLink {
|
||||
SeriesDetailView(seriesId: item.seriesId)
|
||||
} label: {
|
||||
SeriesMainItemView(item: item, width: width, height: width * 227 / 160)
|
||||
.contentShape(Rectangle())
|
||||
.onAppear {
|
||||
if index == viewModel.seriesList.count - 1 {
|
||||
viewModel.getSeriesListByGenre()
|
||||
}
|
||||
SeriesMainItemView(item: item, width: width, height: width * 227 / 160)
|
||||
.contentShape(Rectangle())
|
||||
.onAppear {
|
||||
if index == viewModel.seriesList.count - 1 {
|
||||
viewModel.getSeriesListByGenre()
|
||||
}
|
||||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
AppState.shared.setAppStep(step: .seriesDetail(seriesId: item.seriesId))
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, horizontalPadding)
|
||||
@@ -73,7 +73,10 @@ struct SeriesMainByGenreView: View {
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
viewModel.getGenreList()
|
||||
if !isInitialized {
|
||||
viewModel.getGenreList()
|
||||
isInitialized = true
|
||||
}
|
||||
}
|
||||
|
||||
if viewModel.isLoading {
|
||||
|
||||
@@ -75,17 +75,14 @@ struct SeriesMainDayOfWeekView: View {
|
||||
) {
|
||||
ForEach(viewModel.seriesList.indices, id: \.self) { index in
|
||||
let item = viewModel.seriesList[index]
|
||||
NavigationLink {
|
||||
SeriesDetailView(seriesId: item.seriesId)
|
||||
} label: {
|
||||
SeriesMainItemView(item: item, width: width, height: width * 227 / 160)
|
||||
.contentShape(Rectangle())
|
||||
.onAppear {
|
||||
if index == viewModel.seriesList.count - 1 {
|
||||
viewModel.getDayOfWeekSeriesList(dayOfWeek: dayOfWeek)
|
||||
}
|
||||
SeriesMainItemView(item: item, width: width, height: width * 227 / 160)
|
||||
.contentShape(Rectangle())
|
||||
.onAppear {
|
||||
if index == viewModel.seriesList.count - 1 {
|
||||
viewModel.getDayOfWeekSeriesList(dayOfWeek: dayOfWeek)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onTapGesture { AppState.shared.setAppStep(step: .seriesDetail(seriesId: item.seriesId)) }
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, horizontalPadding)
|
||||
|
||||
@@ -22,11 +22,8 @@ struct SeriesMainHomeBannerView: View {
|
||||
ForEach(0..<bannerList.count, id: \.self) { index in
|
||||
let item = bannerList[index]
|
||||
|
||||
NavigationLink {
|
||||
SeriesDetailView(seriesId: item.seriesId)
|
||||
} label: {
|
||||
SeriesMainHomeBannerImageView(url: item.imagePath, width: width, height: height)
|
||||
}
|
||||
SeriesMainHomeBannerImageView(url: item.imagePath, width: width, height: height)
|
||||
.onTapGesture { AppState.shared.setAppStep(step: .seriesDetail(seriesId: item.seriesId)) }
|
||||
}
|
||||
}
|
||||
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
|
||||
|
||||
@@ -10,6 +10,7 @@ import SwiftUI
|
||||
struct SeriesMainHomeView: View {
|
||||
|
||||
@StateObject var viewModel = SeriesMainHomeViewModel()
|
||||
@State private var isInitialized = false
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
@@ -43,11 +44,10 @@ struct SeriesMainHomeView: View {
|
||||
LazyHStack(spacing: 16) {
|
||||
ForEach(0..<viewModel.completedSeriesList.count, id: \.self) {
|
||||
let item = viewModel.completedSeriesList[$0]
|
||||
NavigationLink {
|
||||
SeriesDetailView(seriesId: item.seriesId)
|
||||
} label: {
|
||||
SeriesMainItemView(item: item)
|
||||
}
|
||||
SeriesMainItemView(item: item)
|
||||
.onTapGesture {
|
||||
AppState.shared.setAppStep(step: .seriesDetail(seriesId: item.seriesId))
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
@@ -89,11 +89,10 @@ struct SeriesMainHomeView: View {
|
||||
) {
|
||||
ForEach(viewModel.recommendSeriesList.indices, id: \.self) {
|
||||
let item = viewModel.recommendSeriesList[$0]
|
||||
NavigationLink {
|
||||
SeriesDetailView(seriesId: item.seriesId)
|
||||
} label: {
|
||||
SeriesMainItemView(item: item, width: width, height: width * 227 / 160)
|
||||
}
|
||||
SeriesMainItemView(item: item, width: width, height: width * 227 / 160)
|
||||
.onTapGesture {
|
||||
AppState.shared.setAppStep(step: .seriesDetail(seriesId: item.seriesId))
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, horizontalPadding)
|
||||
@@ -117,7 +116,10 @@ struct SeriesMainHomeView: View {
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
viewModel.fetchHome()
|
||||
if !isInitialized {
|
||||
viewModel.fetchHome()
|
||||
isInitialized = true
|
||||
}
|
||||
}
|
||||
|
||||
if viewModel.isLoading {
|
||||
|
||||
@@ -25,7 +25,7 @@ struct SeriesMainView: View {
|
||||
@State private var selectedTab: InnerTab = .home
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Group {
|
||||
BaseView {
|
||||
VStack(spacing: 0) {
|
||||
DetailNavigationBar(title: "시리즈 전체보기")
|
||||
|
||||
@@ -9,7 +9,8 @@ import SwiftUI
|
||||
|
||||
struct SeriesListAllView: View {
|
||||
|
||||
@ObservedObject var viewModel = SeriesListAllViewModel()
|
||||
@StateObject var viewModel = SeriesListAllViewModel()
|
||||
@State private var isInitialized = false
|
||||
|
||||
var creatorId: Int? = nil
|
||||
var creatorNickname: String? = nil
|
||||
@@ -18,7 +19,7 @@ struct SeriesListAllView: View {
|
||||
var isCompleted = false
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Group {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 0) {
|
||||
if isCompleted {
|
||||
@@ -48,17 +49,16 @@ struct SeriesListAllView: View {
|
||||
) {
|
||||
ForEach(0..<viewModel.seriesList.count, id: \.self) { index in
|
||||
let item = viewModel.seriesList[index]
|
||||
NavigationLink {
|
||||
SeriesDetailView(seriesId: item.seriesId)
|
||||
} label: {
|
||||
SeriesMainItemView(item: item, width: width, height: width * 227 / 160)
|
||||
.contentShape(Rectangle())
|
||||
.onAppear {
|
||||
if index == viewModel.seriesList.count - 1 {
|
||||
viewModel.getSeriesList()
|
||||
}
|
||||
SeriesMainItemView(item: item, width: width, height: width * 227 / 160)
|
||||
.contentShape(Rectangle())
|
||||
.onAppear {
|
||||
if index == viewModel.seriesList.count - 1 {
|
||||
viewModel.getSeriesList()
|
||||
}
|
||||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
AppState.shared.setAppStep(step: .seriesDetail(seriesId: item.seriesId))
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(horizontalPadding)
|
||||
@@ -67,10 +67,24 @@ struct SeriesListAllView: View {
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
viewModel.creatorId = creatorId
|
||||
viewModel.isOriginal = isOriginal
|
||||
viewModel.isCompleted = isCompleted
|
||||
viewModel.getSeriesList()
|
||||
let hasFilterChanged =
|
||||
viewModel.creatorId != creatorId ||
|
||||
viewModel.isOriginal != isOriginal ||
|
||||
viewModel.isCompleted != isCompleted
|
||||
|
||||
if !isInitialized || hasFilterChanged {
|
||||
if hasFilterChanged {
|
||||
viewModel.page = 1
|
||||
viewModel.isLast = false
|
||||
viewModel.seriesList.removeAll()
|
||||
}
|
||||
|
||||
viewModel.creatorId = creatorId
|
||||
viewModel.isOriginal = isOriginal
|
||||
viewModel.isCompleted = isCompleted
|
||||
viewModel.getSeriesList()
|
||||
isInitialized = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,303 +15,327 @@ struct ContentView: View {
|
||||
@State private var message = ""
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color.black.ignoresSafeArea()
|
||||
|
||||
if appState.isRestartApp {
|
||||
EmptyView()
|
||||
} else {
|
||||
HomeView()
|
||||
}
|
||||
|
||||
switch appState.appStep {
|
||||
case .splash:
|
||||
SplashView()
|
||||
|
||||
case .login:
|
||||
LoginView()
|
||||
|
||||
case .signUp:
|
||||
SignUpView()
|
||||
|
||||
case .findPassword:
|
||||
FindPasswordView()
|
||||
|
||||
case .textMessageDetail(let messageItem, let messageBox, let refresh):
|
||||
TextMessageDetailView(messageItem: messageItem, messageBox: messageBox, refresh: refresh)
|
||||
|
||||
case .writeTextMessage(let userId, let nickname):
|
||||
TextMessageWriteView(replySenderId: userId, replySenderNickname: nickname)
|
||||
|
||||
case .writeVoiceMessage(let userId, let nickname, let onRefresh):
|
||||
VoiceMessageWriteView(replySenderId: userId, replySenderNickname: nickname, onRefresh: onRefresh)
|
||||
|
||||
case .settings:
|
||||
SettingsView()
|
||||
|
||||
case .languageSettings:
|
||||
LanguageSettingsView()
|
||||
|
||||
case .notices:
|
||||
NoticeListView()
|
||||
|
||||
case .noticeDetail(let notice):
|
||||
NoticeDetailView(notice: notice)
|
||||
|
||||
case .events:
|
||||
EventListView()
|
||||
|
||||
case .eventDetail(let event):
|
||||
EventDetailView(event: event)
|
||||
|
||||
case .terms:
|
||||
TermsView(isPrivacyPolicy: false)
|
||||
|
||||
case .privacy:
|
||||
TermsView(isPrivacyPolicy: true)
|
||||
|
||||
case .notificationSettings:
|
||||
NotificationSettingsView()
|
||||
|
||||
case .contentViewSettings:
|
||||
ContentSettingsView()
|
||||
|
||||
case .signOut:
|
||||
SignOutView()
|
||||
|
||||
case .canStatus(let refresh):
|
||||
CanStatusView(refresh: refresh)
|
||||
|
||||
case .canCharge(let refresh, let afterCompletionToGoBack):
|
||||
CanChargeView(refresh: refresh, afterCompletionToGoBack: afterCompletionToGoBack)
|
||||
|
||||
case .canPayment(let canProduct, let refresh, let afterCompletionToGoBack):
|
||||
CanPaymentView(canProduct: canProduct, refresh: refresh, afterCompletionToGoBack: afterCompletionToGoBack)
|
||||
|
||||
case .canPgPayment(let canResponse, let refresh, let afterCompletionToGoBack):
|
||||
CanPgPaymentView(canResponse: canResponse, refresh: refresh, afterCompletionToGoBack: afterCompletionToGoBack)
|
||||
.environmentObject(canPgPaymentViewModel)
|
||||
|
||||
case .liveReservation:
|
||||
LiveReservationStatusView()
|
||||
|
||||
case .liveReservationCancel(let reservationId):
|
||||
LiveReservationCancelView(reservationId: reservationId)
|
||||
|
||||
case .serviceCenter:
|
||||
ServiceCenterView()
|
||||
|
||||
case .createContent:
|
||||
ContentCreateView()
|
||||
|
||||
case .liveReservationComplete(let response):
|
||||
LiveReservationCompleteView(reservationCompleteData: response)
|
||||
|
||||
case .creatorDetail(let userId):
|
||||
UserProfileView(userId: userId)
|
||||
|
||||
case .followerList(let userId):
|
||||
FollowerListView(userId: userId)
|
||||
|
||||
case .modifyContent(let contentId):
|
||||
ContentModifyView(contentId: contentId)
|
||||
|
||||
case .contentListAll(let userId):
|
||||
ContentListView(userId: userId)
|
||||
|
||||
case .contentDetail(let contentId):
|
||||
ContentDetailView(contentId: contentId)
|
||||
|
||||
case .createLive(let timeSettingMode, let onSuccess):
|
||||
LiveRoomCreateView(
|
||||
timeSettingMode: timeSettingMode,
|
||||
onSuccess: onSuccess
|
||||
)
|
||||
|
||||
case .liveNowAll(let onClickParticipant):
|
||||
LiveNowAllView(onClickParticipant: onClickParticipant)
|
||||
|
||||
case .liveReservationAll(let onClickReservation, let onClickStart, let onClickCancel, let onTapCreateLive):
|
||||
LiveReservationAllView(
|
||||
onClickReservation: onClickReservation,
|
||||
onClickStart: onClickStart,
|
||||
onClickCancel: onClickCancel,
|
||||
onTapCreateLive: onTapCreateLive
|
||||
)
|
||||
|
||||
case .modifyLive(let room):
|
||||
LiveRoomEditView(room: room)
|
||||
|
||||
case .liveDetail(let roomId, let onClickParticipant, let onClickReservation, let onClickStart, let onClickCancel):
|
||||
LiveDetailView(
|
||||
roomId: roomId,
|
||||
onClickParticipant: onClickParticipant,
|
||||
onClickReservation: onClickReservation,
|
||||
onClickStart: onClickStart,
|
||||
onClickCancel: onClickCancel
|
||||
)
|
||||
|
||||
case .modifyPassword:
|
||||
ModifyPasswordView()
|
||||
|
||||
case .changeNickname:
|
||||
NicknameUpdateView()
|
||||
|
||||
case .profileUpdate(let refresh):
|
||||
ProfileUpdateView(refresh: refresh)
|
||||
|
||||
case .followingList:
|
||||
FollowCreatorView()
|
||||
|
||||
case .orderListAll:
|
||||
OrderListAllView()
|
||||
|
||||
case .userProfileDonationAll(let userId):
|
||||
UserProfileDonationAllView(userId: userId)
|
||||
NavigationStack(path: $appState.navigationPath) {
|
||||
ZStack {
|
||||
Color.black.ignoresSafeArea()
|
||||
|
||||
case .channelDonationAll(let creatorId):
|
||||
ChannelDonationAllView(creatorId: creatorId)
|
||||
if appState.isRestartApp {
|
||||
EmptyView()
|
||||
} else {
|
||||
HomeView()
|
||||
}
|
||||
|
||||
case .userProfileFanTalkAll(let userId):
|
||||
UserProfileFanTalkAllView(userId: userId)
|
||||
|
||||
case .newContentAll(let isFree):
|
||||
ContentNewAllView(isFree: isFree)
|
||||
|
||||
case .curationAll(let title, let curationId):
|
||||
ContentCurationView(title: title, curationId: curationId)
|
||||
|
||||
case .contentRankingAll:
|
||||
ContentRankingAllView()
|
||||
|
||||
case .creatorCommunityAll(let creatorId):
|
||||
CreatorCommunityAllView(creatorId: creatorId)
|
||||
|
||||
case .creatorCommunityWrite(let onSuccess):
|
||||
CreatorCommunityWriteView(onSuccess: onSuccess)
|
||||
|
||||
case .creatorCommunityModify(let postId, let onSuccess):
|
||||
CreatorCommunityModifyView(postId: postId, onSuccess: onSuccess)
|
||||
|
||||
case .canCoupon(let refresh):
|
||||
CanCouponView(refresh: refresh)
|
||||
|
||||
case .contentAllByTheme(let themeId):
|
||||
ContentAllByThemeView(themeId: themeId)
|
||||
|
||||
case .seriesAll(let creatorId, let creatorNickname, let isOriginal, let isCompleted):
|
||||
SeriesListAllView(creatorId: creatorId, creatorNickname: creatorNickname, isOriginal: isOriginal, isCompleted: isCompleted)
|
||||
|
||||
case .seriesDetail(let seriesId):
|
||||
SeriesDetailView(seriesId: seriesId)
|
||||
|
||||
case .seriesContentAll(let seriesId, let seriesTitle):
|
||||
SeriesContentAllView(seriesId: seriesId, seriesTitle: seriesTitle)
|
||||
|
||||
case .tempCanPayment(let orderType, let contentId, let title, let can):
|
||||
CanPaymentTempView(orderType: orderType, contentId: contentId, title: title, can: can)
|
||||
|
||||
case .blockList:
|
||||
BlockMemberListView()
|
||||
|
||||
case .myBox(let currentTab):
|
||||
ContentBoxView(initCurrentTab: currentTab)
|
||||
|
||||
case .auditionDetail(let auditionId):
|
||||
AuditionDetailView(auditionId: auditionId)
|
||||
|
||||
case .auditionRoleDetail(let roleId, let auditionTitle):
|
||||
AuditionRoleDetailView(
|
||||
roleId: roleId,
|
||||
auditionTitle: auditionTitle
|
||||
)
|
||||
|
||||
case .search:
|
||||
SearchView()
|
||||
|
||||
case .contentMain(let startTab):
|
||||
ContentMainViewV2(selectedTab: startTab)
|
||||
|
||||
case .completedSeriesAll:
|
||||
CompletedSeriesView()
|
||||
|
||||
case .newAlarmContentAll:
|
||||
ContentMainAlarmAllView()
|
||||
|
||||
case .newAsmrContentAll:
|
||||
ContentMainAsmrAllView()
|
||||
|
||||
case .newReplayContentAll:
|
||||
ContentMainReplayAllView()
|
||||
|
||||
case .introduceCreatorAll:
|
||||
ContentMainIntroduceCreatorAllView()
|
||||
|
||||
case .message:
|
||||
MessageView()
|
||||
|
||||
case .pointStatus(let refresh):
|
||||
PointStatusView(refresh: refresh)
|
||||
|
||||
case .audition:
|
||||
AuditionView()
|
||||
|
||||
case .characterDetail(let characterId):
|
||||
CharacterDetailView(characterId: characterId)
|
||||
|
||||
case .chatRoom(let id):
|
||||
ChatRoomView(roomId: id)
|
||||
|
||||
case .newCharacterAll:
|
||||
NewCharacterListView()
|
||||
|
||||
case .originalWorkDetail(let originalId):
|
||||
OriginalWorkDetailView(originalId: originalId)
|
||||
|
||||
case .contentAll(let isFree, let isPointOnly):
|
||||
ContentAllView(isFree: isFree, isPointAvailableOnly: isPointOnly)
|
||||
|
||||
case .seriesMain:
|
||||
SeriesMainView()
|
||||
|
||||
default:
|
||||
EmptyView()
|
||||
.frame(width: 0, height: 0, alignment: .topLeading)
|
||||
if case .splash = appState.rootStep {
|
||||
AppStepLayerView(step: .splash, canPgPaymentViewModel: canPgPaymentViewModel)
|
||||
.navigationBarBackButtonHidden(true)
|
||||
}
|
||||
|
||||
if isShowDialog {
|
||||
SodaDialog(
|
||||
title: I18n.Common.pointGrantTitle,
|
||||
desc: message,
|
||||
confirmButtonTitle: I18n.Common.confirm
|
||||
) {
|
||||
isShowDialog = false
|
||||
message = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isShowDialog {
|
||||
SodaDialog(
|
||||
title: I18n.Common.pointGrantTitle,
|
||||
desc: message,
|
||||
confirmButtonTitle: I18n.Common.confirm
|
||||
) {
|
||||
isShowDialog = false
|
||||
message = ""
|
||||
.onReceive(NotificationCenter.default.publisher(for: .pointGranted)) {
|
||||
if let msg = $0.object as? String {
|
||||
self.message = msg
|
||||
self.isShowDialog = true
|
||||
}
|
||||
}
|
||||
.popup(isPresented: $appState.isShowErrorPopup, type: .toast, position: .top, autohideIn: 1) {
|
||||
GeometryReader { geo in
|
||||
HStack {
|
||||
Spacer()
|
||||
Text(appState.errorMessage)
|
||||
.padding(.vertical, 13.3)
|
||||
.frame(width: geo.size.width - 66.7, alignment: .center)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.background(Color.button)
|
||||
.foregroundColor(Color.white)
|
||||
.multilineTextAlignment(.center)
|
||||
.cornerRadius(20)
|
||||
.padding(.top, 66.7)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationDestination(for: AppRoute.self) { route in
|
||||
if let step = appState.appStep(for: route) {
|
||||
AppStepLayerView(step: step, canPgPaymentViewModel: canPgPaymentViewModel)
|
||||
.navigationBarBackButtonHidden(true)
|
||||
} else {
|
||||
EmptyView()
|
||||
.frame(width: 0, height: 0, alignment: .topLeading)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: .pointGranted)) {
|
||||
if let msg = $0.object as? String {
|
||||
self.message = msg
|
||||
self.isShowDialog = true
|
||||
}
|
||||
}
|
||||
.popup(isPresented: $appState.isShowErrorPopup, type: .toast, position: .top, autohideIn: 1) {
|
||||
GeometryReader { geo in
|
||||
HStack {
|
||||
Spacer()
|
||||
Text(appState.errorMessage)
|
||||
.padding(.vertical, 13.3)
|
||||
.frame(width: geo.size.width - 66.7, alignment: .center)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.background(Color.button)
|
||||
.foregroundColor(Color.white)
|
||||
.multilineTextAlignment(.center)
|
||||
.cornerRadius(20)
|
||||
.padding(.top, 66.7)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AppStepLayerView: View {
|
||||
let step: AppStep
|
||||
@ObservedObject var canPgPaymentViewModel: CanPgPaymentViewModel
|
||||
|
||||
@ViewBuilder
|
||||
var body: some View {
|
||||
switch step {
|
||||
case .splash:
|
||||
SplashView()
|
||||
|
||||
case .login:
|
||||
LoginView()
|
||||
|
||||
case .signUp:
|
||||
SignUpView()
|
||||
|
||||
case .findPassword:
|
||||
FindPasswordView()
|
||||
|
||||
case .textMessageDetail(let messageItem, let messageBox, let refresh):
|
||||
TextMessageDetailView(messageItem: messageItem, messageBox: messageBox, refresh: refresh)
|
||||
|
||||
case .writeTextMessage(let userId, let nickname):
|
||||
TextMessageWriteView(replySenderId: userId, replySenderNickname: nickname)
|
||||
|
||||
case .writeVoiceMessage(let userId, let nickname, let onRefresh):
|
||||
VoiceMessageWriteView(replySenderId: userId, replySenderNickname: nickname, onRefresh: onRefresh)
|
||||
|
||||
case .settings:
|
||||
SettingsView()
|
||||
|
||||
case .languageSettings:
|
||||
LanguageSettingsView()
|
||||
|
||||
case .notices:
|
||||
NoticeListView()
|
||||
|
||||
case .noticeDetail(let notice):
|
||||
NoticeDetailView(notice: notice)
|
||||
|
||||
case .events:
|
||||
EventListView()
|
||||
|
||||
case .eventDetail(let event):
|
||||
EventDetailView(event: event)
|
||||
|
||||
case .terms:
|
||||
TermsView(isPrivacyPolicy: false)
|
||||
|
||||
case .privacy:
|
||||
TermsView(isPrivacyPolicy: true)
|
||||
|
||||
case .notificationSettings:
|
||||
NotificationSettingsView()
|
||||
|
||||
case .contentViewSettings:
|
||||
ContentSettingsView()
|
||||
|
||||
case .signOut:
|
||||
SignOutView()
|
||||
|
||||
case .canStatus(let refresh):
|
||||
CanStatusView(refresh: refresh)
|
||||
|
||||
case .canCharge(let refresh, let afterCompletionToGoBack):
|
||||
CanChargeView(refresh: refresh, afterCompletionToGoBack: afterCompletionToGoBack)
|
||||
|
||||
case .canPayment(let canProduct, let refresh, let afterCompletionToGoBack):
|
||||
CanPaymentView(canProduct: canProduct, refresh: refresh, afterCompletionToGoBack: afterCompletionToGoBack)
|
||||
|
||||
case .canPgPayment(let canResponse, let refresh, let afterCompletionToGoBack):
|
||||
CanPgPaymentView(canResponse: canResponse, refresh: refresh, afterCompletionToGoBack: afterCompletionToGoBack)
|
||||
.environmentObject(canPgPaymentViewModel)
|
||||
|
||||
case .liveReservation:
|
||||
LiveReservationStatusView()
|
||||
|
||||
case .liveReservationCancel(let reservationId):
|
||||
LiveReservationCancelView(reservationId: reservationId)
|
||||
|
||||
case .serviceCenter:
|
||||
ServiceCenterView()
|
||||
|
||||
case .createContent:
|
||||
ContentCreateView()
|
||||
|
||||
case .liveReservationComplete(let response):
|
||||
LiveReservationCompleteView(reservationCompleteData: response)
|
||||
|
||||
case .creatorDetail(let userId):
|
||||
UserProfileView(userId: userId)
|
||||
|
||||
case .followerList(let userId):
|
||||
FollowerListView(userId: userId)
|
||||
|
||||
case .modifyContent(let contentId):
|
||||
ContentModifyView(contentId: contentId)
|
||||
|
||||
case .contentListAll(let userId):
|
||||
ContentListView(userId: userId)
|
||||
|
||||
case .contentDetail(let contentId):
|
||||
ContentDetailView(contentId: contentId)
|
||||
|
||||
case .createLive(let timeSettingMode, let onSuccess):
|
||||
LiveRoomCreateView(
|
||||
timeSettingMode: timeSettingMode,
|
||||
onSuccess: onSuccess
|
||||
)
|
||||
|
||||
case .liveNowAll(let onClickParticipant):
|
||||
LiveNowAllView(onClickParticipant: onClickParticipant)
|
||||
|
||||
case .liveReservationAll(let onClickReservation, let onClickStart, let onClickCancel, let onTapCreateLive):
|
||||
LiveReservationAllView(
|
||||
onClickReservation: onClickReservation,
|
||||
onClickStart: onClickStart,
|
||||
onClickCancel: onClickCancel,
|
||||
onTapCreateLive: onTapCreateLive
|
||||
)
|
||||
|
||||
case .modifyLive(let room):
|
||||
LiveRoomEditView(room: room)
|
||||
|
||||
case .liveDetail(let roomId, let onClickParticipant, let onClickReservation, let onClickStart, let onClickCancel):
|
||||
LiveDetailView(
|
||||
roomId: roomId,
|
||||
onClickParticipant: onClickParticipant,
|
||||
onClickReservation: onClickReservation,
|
||||
onClickStart: onClickStart,
|
||||
onClickCancel: onClickCancel
|
||||
)
|
||||
|
||||
case .modifyPassword:
|
||||
ModifyPasswordView()
|
||||
|
||||
case .changeNickname:
|
||||
NicknameUpdateView()
|
||||
|
||||
case .profileUpdate(let refresh):
|
||||
ProfileUpdateView(refresh: refresh)
|
||||
|
||||
case .followingList:
|
||||
FollowCreatorView()
|
||||
|
||||
case .orderListAll:
|
||||
OrderListAllView()
|
||||
|
||||
case .userProfileDonationAll(let userId):
|
||||
UserProfileDonationAllView(userId: userId)
|
||||
|
||||
case .channelDonationAll(let creatorId):
|
||||
ChannelDonationAllView(creatorId: creatorId)
|
||||
|
||||
case .userProfileFanTalkAll(let userId):
|
||||
UserProfileFanTalkAllView(userId: userId)
|
||||
|
||||
case .newContentAll(let isFree):
|
||||
ContentNewAllView(isFree: isFree)
|
||||
|
||||
case .curationAll(let title, let curationId):
|
||||
ContentCurationView(title: title, curationId: curationId)
|
||||
|
||||
case .contentRankingAll:
|
||||
ContentRankingAllView()
|
||||
|
||||
case .creatorCommunityAll(let creatorId):
|
||||
CreatorCommunityAllView(creatorId: creatorId)
|
||||
|
||||
case .creatorCommunityWrite(let onSuccess):
|
||||
CreatorCommunityWriteView(onSuccess: onSuccess)
|
||||
|
||||
case .creatorCommunityModify(let postId, let onSuccess):
|
||||
CreatorCommunityModifyView(postId: postId, onSuccess: onSuccess)
|
||||
|
||||
case .canCoupon(let refresh):
|
||||
CanCouponView(refresh: refresh)
|
||||
|
||||
case .contentAllByTheme(let themeId):
|
||||
ContentAllByThemeView(themeId: themeId)
|
||||
|
||||
case .seriesAll(let creatorId, let creatorNickname, let isOriginal, let isCompleted):
|
||||
SeriesListAllView(creatorId: creatorId, creatorNickname: creatorNickname, isOriginal: isOriginal, isCompleted: isCompleted)
|
||||
|
||||
case .seriesDetail(let seriesId):
|
||||
SeriesDetailView(seriesId: seriesId)
|
||||
|
||||
case .seriesContentAll(let seriesId, let seriesTitle):
|
||||
SeriesContentAllView(seriesId: seriesId, seriesTitle: seriesTitle)
|
||||
|
||||
case .tempCanPayment(let orderType, let contentId, let title, let can):
|
||||
CanPaymentTempView(orderType: orderType, contentId: contentId, title: title, can: can)
|
||||
|
||||
case .blockList:
|
||||
BlockMemberListView()
|
||||
|
||||
case .myBox(let currentTab):
|
||||
ContentBoxView(initCurrentTab: currentTab)
|
||||
|
||||
case .auditionDetail(let auditionId):
|
||||
AuditionDetailView(auditionId: auditionId)
|
||||
|
||||
case .auditionRoleDetail(let roleId, let auditionTitle):
|
||||
AuditionRoleDetailView(
|
||||
roleId: roleId,
|
||||
auditionTitle: auditionTitle
|
||||
)
|
||||
|
||||
case .search:
|
||||
SearchView()
|
||||
|
||||
case .contentMain(let startTab):
|
||||
ContentMainViewV2(selectedTab: startTab)
|
||||
|
||||
case .completedSeriesAll:
|
||||
CompletedSeriesView()
|
||||
|
||||
case .newAlarmContentAll:
|
||||
ContentMainAlarmAllView()
|
||||
|
||||
case .newAsmrContentAll:
|
||||
ContentMainAsmrAllView()
|
||||
|
||||
case .newReplayContentAll:
|
||||
ContentMainReplayAllView()
|
||||
|
||||
case .introduceCreatorAll:
|
||||
ContentMainIntroduceCreatorAllView()
|
||||
|
||||
case .message:
|
||||
MessageView()
|
||||
|
||||
case .pointStatus(let refresh):
|
||||
PointStatusView(refresh: refresh)
|
||||
|
||||
case .audition:
|
||||
AuditionView()
|
||||
|
||||
case .characterDetail(let characterId):
|
||||
CharacterDetailView(characterId: characterId)
|
||||
|
||||
case .chatRoom(let id):
|
||||
ChatRoomView(roomId: id)
|
||||
|
||||
case .newCharacterAll:
|
||||
NewCharacterListView()
|
||||
|
||||
case .originalWorkDetail(let originalId):
|
||||
OriginalWorkDetailView(originalId: originalId)
|
||||
|
||||
case .contentAll(let isFree, let isPointOnly):
|
||||
ContentAllView(isFree: isFree, isPointAvailableOnly: isPointOnly)
|
||||
|
||||
case .seriesMain:
|
||||
SeriesMainView()
|
||||
|
||||
case .main:
|
||||
EmptyView()
|
||||
.frame(width: 0, height: 0, alignment: .topLeading)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ struct CreatorCommunityCommentListView: View {
|
||||
@StateObject var viewModel = CreatorCommunityCommentListViewModel()
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Group {
|
||||
ZStack {
|
||||
VStack(spacing: 0) {
|
||||
HStack(spacing: 0) {
|
||||
|
||||
@@ -56,12 +56,15 @@ struct CreatorCommunityAllView: View {
|
||||
.sheet(
|
||||
isPresented: $viewModel.isShowCommentListView,
|
||||
content: {
|
||||
CreatorCommunityCommentListView(
|
||||
isPresented: $viewModel.isShowCommentListView,
|
||||
creatorId: creatorId,
|
||||
postId: viewModel.postId,
|
||||
isShowSecret: viewModel.isShowSecret
|
||||
)
|
||||
NavigationStack {
|
||||
CreatorCommunityCommentListView(
|
||||
isPresented: $viewModel.isShowCommentListView,
|
||||
creatorId: creatorId,
|
||||
postId: viewModel.postId,
|
||||
isShowSecret: viewModel.isShowSecret
|
||||
)
|
||||
}
|
||||
.toolbar(.hidden, for: .navigationBar)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ struct UserProfileView: View {
|
||||
@State private var isShowChannelDonationDialog: Bool = false
|
||||
@State private var didTriggerAutoBackOnLoadFailure: Bool = false
|
||||
@State private var isViewVisible: Bool = false
|
||||
@State private var loadedUserId: Int? = nil
|
||||
|
||||
@State private var maxCommunityPostHeight: CGFloat? = nil
|
||||
|
||||
@@ -639,7 +640,12 @@ struct UserProfileView: View {
|
||||
.onAppear {
|
||||
isViewVisible = true
|
||||
didTriggerAutoBackOnLoadFailure = false
|
||||
viewModel.getCreatorProfile(userId: userId)
|
||||
|
||||
if loadedUserId != userId || viewModel.creatorProfile == nil {
|
||||
loadedUserId = userId
|
||||
viewModel.getCreatorProfile(userId: userId)
|
||||
}
|
||||
|
||||
AppState.shared.pushChannelId = 0
|
||||
}
|
||||
.onDisappear {
|
||||
|
||||
@@ -9,10 +9,11 @@ import SwiftUI
|
||||
|
||||
struct OrderListAllInnerView: View {
|
||||
@StateObject var viewModel = OrderListAllViewModel()
|
||||
@State private var isInitialized = false
|
||||
|
||||
var body: some View {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 0) {
|
||||
VStack(spacing: 0) {
|
||||
HStack(spacing: 0) {
|
||||
Text("총")
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
@@ -38,19 +39,16 @@ struct OrderListAllInnerView: View {
|
||||
|
||||
ForEach(0..<viewModel.orderList.count, id: \.self) { index in
|
||||
let item = viewModel.orderList[index]
|
||||
NavigationLink {
|
||||
ContentDetailView(contentId: item.contentId)
|
||||
} label: {
|
||||
OrderListItemView(item: item)
|
||||
.contentShape(Rectangle())
|
||||
.padding(.horizontal, 13.3)
|
||||
.padding(.top, 6.7)
|
||||
.onAppear {
|
||||
if index == viewModel.orderList.count - 1 {
|
||||
viewModel.getOrderList()
|
||||
}
|
||||
OrderListItemView(item: item)
|
||||
.contentShape(Rectangle())
|
||||
.padding(.horizontal, 13.3)
|
||||
.padding(.top, 6.7)
|
||||
.onAppear {
|
||||
if index == viewModel.orderList.count - 1 {
|
||||
viewModel.getOrderList()
|
||||
}
|
||||
}
|
||||
}
|
||||
.onTapGesture { AppState.shared.setAppStep(step: .contentDetail(contentId: item.contentId)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,7 +56,10 @@ struct OrderListAllInnerView: View {
|
||||
.padding(.top, 13.3)
|
||||
}
|
||||
.onAppear {
|
||||
viewModel.getOrderList()
|
||||
if !isInitialized {
|
||||
viewModel.getOrderList()
|
||||
isInitialized = true
|
||||
}
|
||||
}
|
||||
.popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .bottom, autohideIn: 2) {
|
||||
HStack {
|
||||
|
||||
@@ -10,7 +10,7 @@ import SwiftUI
|
||||
struct OrderListAllView: View {
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Group {
|
||||
VStack(spacing: 0) {
|
||||
HStack(spacing: 0) {
|
||||
Button {
|
||||
|
||||
@@ -96,26 +96,23 @@ struct SearchCreatorItemView: View {
|
||||
let item: SearchResponseItem
|
||||
|
||||
var body: some View {
|
||||
NavigationLink {
|
||||
UserProfileView(userId: item.id)
|
||||
} label: {
|
||||
HStack(spacing: 13.3) {
|
||||
KFImage(URL(string: item.imageUrl))
|
||||
.cancelOnDisappear(true)
|
||||
.downsampling(size: CGSize(width: 60, height: 60))
|
||||
.resizable()
|
||||
.frame(width: 60, height: 60)
|
||||
.clipShape(Circle())
|
||||
|
||||
Text(item.nickname)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.contentShape(Rectangle())
|
||||
HStack(spacing: 13.3) {
|
||||
KFImage(URL(string: item.imageUrl))
|
||||
.cancelOnDisappear(true)
|
||||
.downsampling(size: CGSize(width: 60, height: 60))
|
||||
.resizable()
|
||||
.frame(width: 60, height: 60)
|
||||
.clipShape(Circle())
|
||||
|
||||
Text(item.nickname)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture { AppState.shared.setAppStep(step: .creatorDetail(userId: item.id)) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,34 +120,31 @@ struct SearchContentItemView: View {
|
||||
let item: SearchResponseItem
|
||||
|
||||
var body: some View {
|
||||
NavigationLink {
|
||||
ContentDetailView(contentId: item.id)
|
||||
} label: {
|
||||
HStack(spacing: 13.3) {
|
||||
KFImage(URL(string: item.imageUrl))
|
||||
.cancelOnDisappear(true)
|
||||
.downsampling(size: CGSize(width: 60, height: 60))
|
||||
.resizable()
|
||||
.frame(width: 60, height: 60)
|
||||
.cornerRadius(5.3)
|
||||
HStack(spacing: 13.3) {
|
||||
KFImage(URL(string: item.imageUrl))
|
||||
.cancelOnDisappear(true)
|
||||
.downsampling(size: CGSize(width: 60, height: 60))
|
||||
.resizable()
|
||||
.frame(width: 60, height: 60)
|
||||
.cornerRadius(5.3)
|
||||
|
||||
VStack(alignment: .leading, spacing: 6.7) {
|
||||
Text(item.title)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
.multilineTextAlignment(.leading)
|
||||
.lineLimit(2)
|
||||
|
||||
VStack(alignment: .leading, spacing: 6.7) {
|
||||
Text(item.title)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
.multilineTextAlignment(.leading)
|
||||
.lineLimit(2)
|
||||
|
||||
Text(item.nickname)
|
||||
.appFont(size: 10, weight: .medium)
|
||||
.foregroundColor(Color.gray77)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
Text(item.nickname)
|
||||
.appFont(size: 10, weight: .medium)
|
||||
.foregroundColor(Color.gray77)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.contentShape(Rectangle())
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture { AppState.shared.setAppStep(step: .contentDetail(contentId: item.id)) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,36 +152,33 @@ struct SearchSeriesItemView: View {
|
||||
let item: SearchResponseItem
|
||||
|
||||
var body: some View {
|
||||
NavigationLink {
|
||||
SeriesDetailView(seriesId: item.id)
|
||||
} label: {
|
||||
HStack(spacing: 13.3) {
|
||||
KFImage(URL(string: item.imageUrl))
|
||||
.cancelOnDisappear(true)
|
||||
.downsampling(size: CGSize(width: 60, height: 85))
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 60, height: 85)
|
||||
.clipped()
|
||||
.cornerRadius(5.3)
|
||||
HStack(spacing: 13.3) {
|
||||
KFImage(URL(string: item.imageUrl))
|
||||
.cancelOnDisappear(true)
|
||||
.downsampling(size: CGSize(width: 60, height: 85))
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 60, height: 85)
|
||||
.clipped()
|
||||
.cornerRadius(5.3)
|
||||
|
||||
VStack(alignment: .leading, spacing: 6.7) {
|
||||
Text(item.title)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
.multilineTextAlignment(.leading)
|
||||
.lineLimit(2)
|
||||
|
||||
VStack(alignment: .leading, spacing: 6.7) {
|
||||
Text(item.title)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
.multilineTextAlignment(.leading)
|
||||
.lineLimit(2)
|
||||
|
||||
Text(item.nickname)
|
||||
.appFont(size: 10, weight: .medium)
|
||||
.foregroundColor(Color.gray77)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
Text(item.nickname)
|
||||
.appFont(size: 10, weight: .medium)
|
||||
.foregroundColor(Color.gray77)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.contentShape(Rectangle())
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture { AppState.shared.setAppStep(step: .seriesDetail(seriesId: item.id)) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ struct SearchView: View {
|
||||
]
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Group {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 0) {
|
||||
HStack(spacing: 0) {
|
||||
|
||||
@@ -76,6 +76,7 @@ struct SplashView: View {
|
||||
)
|
||||
}
|
||||
}
|
||||
.toolbar(.hidden, for: .navigationBar)
|
||||
.onAppear {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
AppState.shared.isRestartApp = false
|
||||
|
||||
Reference in New Issue
Block a user