feat(original): 작품별 상세 UI 변경
- 캐릭터 / 작품 정보 탭 추가 - 작품 정보 탭 구성 - 작품 소개 - 원작 보러 가기 - 상세 정보 - 작가 - 제작사 - 원작
This commit is contained in:
@@ -14,9 +14,6 @@ struct OriginalWorkDetailView: View {
|
||||
|
||||
let originalId: Int
|
||||
|
||||
private let horizontalPadding: CGFloat = 12
|
||||
private let gridSpacing: CGFloat = 12
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
@@ -46,54 +43,46 @@ struct OriginalWorkDetailView: View {
|
||||
.frame(height: 56)
|
||||
|
||||
if let response = viewModel.response {
|
||||
GeometryReader { geo in
|
||||
let totalSpacing: CGFloat = gridSpacing * 2
|
||||
let width = (geo.size.width - (horizontalPadding * 2) - totalSpacing) / 3
|
||||
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
VStack(spacing: 0) {
|
||||
OriginalWorkDetailHeaderView(item: response)
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.bottom, 24)
|
||||
|
||||
LazyVGrid(
|
||||
columns: Array(
|
||||
repeating: GridItem(
|
||||
.flexible(),
|
||||
spacing: gridSpacing,
|
||||
alignment: .topLeading
|
||||
),
|
||||
count: 3
|
||||
),
|
||||
alignment: .leading,
|
||||
spacing: gridSpacing
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
VStack(spacing: 0) {
|
||||
OriginalWorkDetailHeaderView(item: response)
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.bottom, 24)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
SeriesDetailTabView(
|
||||
title: "캐릭터",
|
||||
width: screenSize().width / 2,
|
||||
isSelected: viewModel.currentTab == .character
|
||||
) {
|
||||
ForEach(viewModel.characters.indices, id: \.self) { idx in
|
||||
let item = viewModel.characters[idx]
|
||||
|
||||
NavigationLink(value: item.characterId) {
|
||||
CharacterItemView(
|
||||
character: item,
|
||||
size: width,
|
||||
rank: 0,
|
||||
isShowRank: false
|
||||
)
|
||||
.onAppear { viewModel.loadMoreIfNeeded(currentIndex: idx) }
|
||||
}
|
||||
if viewModel.currentTab != .character {
|
||||
viewModel.currentTab = .character
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, horizontalPadding)
|
||||
|
||||
if viewModel.isLoadingMore {
|
||||
HStack {
|
||||
Spacer()
|
||||
ProgressView()
|
||||
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
||||
.padding(.vertical, 16)
|
||||
Spacer()
|
||||
SeriesDetailTabView(
|
||||
title: "작품정보",
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,6 +101,170 @@ struct OriginalWorkDetailView: View {
|
||||
}
|
||||
}
|
||||
|
||||
struct OriginalWorkCharacterView: View {
|
||||
|
||||
private let horizontalPadding: CGFloat = 12
|
||||
private let gridSpacing: CGFloat = 12
|
||||
|
||||
let characters: [Character]
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
let totalSpacing: CGFloat = gridSpacing * 2
|
||||
let width = (screenSize().width - (horizontalPadding * 2) - totalSpacing) / 3
|
||||
|
||||
LazyVGrid(
|
||||
columns: Array(
|
||||
repeating: GridItem(
|
||||
.flexible(),
|
||||
spacing: gridSpacing,
|
||||
alignment: .topLeading
|
||||
),
|
||||
count: 3
|
||||
),
|
||||
alignment: .leading,
|
||||
spacing: gridSpacing
|
||||
) {
|
||||
ForEach(characters.indices, id: \.self) { idx in
|
||||
let item = characters[idx]
|
||||
|
||||
NavigationLink(value: item.characterId) {
|
||||
CharacterItemView(
|
||||
character: item,
|
||||
size: width,
|
||||
rank: 0,
|
||||
isShowRank: false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, horizontalPadding)
|
||||
}
|
||||
.padding(.top, 24)
|
||||
.background(Color.black)
|
||||
}
|
||||
}
|
||||
|
||||
struct OriginalWorkInfoView: View {
|
||||
|
||||
let response: OriginalWorkDetailResponse
|
||||
|
||||
@State private var isExpandDesc = false
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
VStack(spacing: 16) {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("작품 소개")
|
||||
.font(.custom(Font.preBold.rawValue, size: 16))
|
||||
.foregroundColor(.white)
|
||||
|
||||
Text(response.description)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
.lineLimit(isExpandDesc ? Int.max : 3)
|
||||
.truncationMode(.tail)
|
||||
.onTapGesture {
|
||||
isExpandDesc.toggle()
|
||||
}
|
||||
}
|
||||
.padding(16)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.background(Color(hex: "263238"))
|
||||
.cornerRadius(16)
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("원작 보러 가기")
|
||||
.font(.custom(Font.preBold.rawValue, size: 16))
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack(spacing: 8) {
|
||||
ForEach(0..<response.originalLinks.count, id: \.self) {
|
||||
let link = response.originalLinks[$0]
|
||||
|
||||
Text(link)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.foregroundColor(.white)
|
||||
.onTapGesture {
|
||||
if let url = URL(string: link) {
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(16)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.background(Color(hex: "263238"))
|
||||
.cornerRadius(16)
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("상세 정보")
|
||||
.font(.custom(Font.preBold.rawValue, size: 16))
|
||||
.foregroundColor(.white)
|
||||
|
||||
HStack(spacing: 16) {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
if let _ = response.writer {
|
||||
Text("작가")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
}
|
||||
|
||||
if let _ = response.studio {
|
||||
Text("제작사")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
}
|
||||
|
||||
if let _ = response.originalWork {
|
||||
Text("원작")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
}
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
if let writer = response.writer {
|
||||
Text(writer)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
if let studio = response.studio {
|
||||
Text(studio)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
if let originalWork = response.originalWork {
|
||||
Text(originalWork)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.foregroundColor(.white)
|
||||
.underline(response.originalLink != nil ? true : false)
|
||||
.onTapGesture {
|
||||
if let link = response.originalLink, let url = URL(string: link) {
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(16)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.background(Color(hex: "263238"))
|
||||
.cornerRadius(16)
|
||||
}
|
||||
}
|
||||
.padding(24)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color.black)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
OriginalWorkDetailView(originalId: 0)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user