From 1ade99e3179aa6b9920a5d324502eb3cc6ba6485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=92=E1=85=A9=E1=86=BC=E1=84=89=E1=85=A5=E1=86=A8?= =?UTF-8?q?=E1=84=92=E1=85=A7=E1=86=AB?= Date: Fri, 17 Oct 2025 20:49:47 +0900 Subject: [PATCH] =?UTF-8?q?feat.=20=EC=98=88=EB=A7=A4=ED=95=98=EA=B8=B0=20?= =?UTF-8?q?UI=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/BookingOptionsCard.swift | 125 ++++++++++++++++ .../Components/PaymentInfoCard.swift | 137 ++++++++++++++++++ .../Feature/MovieBook/MovieBookView.swift | 60 ++++++++ 3 files changed, 322 insertions(+) create mode 100644 MovieBooking/Feature/MovieBook/Components/BookingOptionsCard.swift create mode 100644 MovieBooking/Feature/MovieBook/Components/PaymentInfoCard.swift create mode 100644 MovieBooking/Feature/MovieBook/MovieBookView.swift diff --git a/MovieBooking/Feature/MovieBook/Components/BookingOptionsCard.swift b/MovieBooking/Feature/MovieBook/Components/BookingOptionsCard.swift new file mode 100644 index 0000000..e79da42 --- /dev/null +++ b/MovieBooking/Feature/MovieBook/Components/BookingOptionsCard.swift @@ -0,0 +1,125 @@ +// +// BookingOptionsCard.swift +// MovieBooking +// +// Created by 홍석현 on 10/17/25. +// + +import SwiftUI + +struct BookingOptionsCard: View { + let theaters: [String] + let times: [String] + @Binding var selectedTheater: String + @Binding var selectedTime: String + @Binding var numberOfPeople: Int + + var body: some View { + VStack(spacing: 20) { + // 극장 선택 + CustomPickerRow( + title: "극장", + selection: $selectedTheater, + options: theaters + ) + + // 상영시간 선택 + CustomPickerRow( + title: "상영시간", + selection: $selectedTime, + options: times + ) + + // 인원수 선택 + CustomPickerRow( + title: "인원수", + selection: $numberOfPeople, + options: Array(1...10), + displayFormatter: { "\($0)명" } + ) + } + .padding(20) + .background(Color.white) + .clipShape(RoundedRectangle(cornerRadius: 16)) + .shadow(color: .black.opacity(0.1), radius: 10, x: 0, y: 5) + } +} + +// MARK: - Custom Picker Row +fileprivate struct CustomPickerRow: View { + let title: String + @Binding var selection: T + let options: [T] + var displayFormatter: ((T) -> String)? + + var body: some View { + VStack(alignment: .leading, spacing: 8) { + Text(title) + .font(.system(size: 16, weight: .semibold)) + + Menu { + ForEach(options, id: \.self) { option in + Button { + selection = option + } label: { + HStack { + Text(displayText(for: option)) + if selection == option { + Spacer() + Image(systemName: "checkmark") + .foregroundColor(.basicPurple) + } + } + } + } + } label: { + HStack { + Text(displayText(for: selection)) + .foregroundColor(.primary) + .font(.system(size: 16)) + + Spacer() + + Image(systemName: "chevron.down") + .font(.system(size: 14)) + .foregroundColor(.secondary) + } + .padding(14) + .background(Color.white) + .clipShape(RoundedRectangle(cornerRadius: 14)) + .overlay( + RoundedRectangle(cornerRadius: 14) + .stroke(Color.gray.opacity(0.2), lineWidth: 1) + ) + } + } + } + + private func displayText(for option: T) -> String { + if let formatter = displayFormatter { + return formatter(option) + } + return "\(option)" + } +} + +#Preview { + BookingOptionsCardPreview() +} + +private struct BookingOptionsCardPreview: View { + @State var selectedTheater = "CGV 강남" + @State var selectedTime = "14:30" + @State var numberOfPeople = 1 + + var body: some View { + BookingOptionsCard( + theaters: ["CGV 강남", "메가박스 코엑스", "롯데시네마 월드타워", "CGV 용산"], + times: ["10:00", "12:30", "14:30", "17:00", "19:30", "22:00"], + selectedTheater: $selectedTheater, + selectedTime: $selectedTime, + numberOfPeople: $numberOfPeople + ) + .padding() + } +} diff --git a/MovieBooking/Feature/MovieBook/Components/PaymentInfoCard.swift b/MovieBooking/Feature/MovieBook/Components/PaymentInfoCard.swift new file mode 100644 index 0000000..daaa60b --- /dev/null +++ b/MovieBooking/Feature/MovieBook/Components/PaymentInfoCard.swift @@ -0,0 +1,137 @@ +// +// PaymentInfoCard.swift +// MovieBooking +// +// Created by 홍석현 on 10/17/25. +// + +import SwiftUI + +struct PaymentInfoCard: View { + let posterPath: String + let title: String + let pricePerTicket: Int + let numberOfPeople: Int + let onPaymentTapped: () -> Void + + private var totalPrice: Int { + pricePerTicket * numberOfPeople + } + + var body: some View { + VStack(spacing: 24) { + VStack(spacing: 16) { + // 헤더: 로고 + 결제 정보 + HStack { + Image(systemName: "creditcard") + .font(.system(size: 24)) + .foregroundColor(.basicPurple) + + Text("결제 정보") + .font(.system(size: 18, weight: .light)) + + Spacer() + } + + // 이미지 + 타이틀 + HStack(alignment: .top, spacing: 16) { + AsyncImage(url: URL(string: "https://image.tmdb.org/t/p/w500\(posterPath)")) { phase in + switch phase { + case .success(let image): + image + .resizable() + .aspectRatio(contentMode: .fill) + case .failure: + Color.gray + case .empty: + ProgressView() + @unknown default: + EmptyView() + } + } + .frame(width: 70, height: 70) + .clipShape(RoundedRectangle(cornerRadius: 16)) + + Text(title) + .font(.system(size: 18, weight: .light)) + .lineLimit(2) + + Spacer() + } + + Divider() + .overlay(Color.white) + + HStack { + Text("일반 관람권 (\(numberOfPeople)매)") + + Spacer() + + Text("\(pricePerTicket.formatted()) 원") + } + .font(.system(size: 16, weight: .light)) + .foregroundColor(.secondary) + + Divider() + .overlay(Color.white) + + // 총 결제금액 + HStack { + Text("총 결제금액") + .font(.system(size: 18, weight: .regular)) + + Spacer() + + Text("\(totalPrice.formatted()) 원") + .font(.system(size: 24, weight: .regular)) + .foregroundColor(.basicPurple) + } + } + .padding(20) + .background( + LinearGradient( + gradient: Gradient(colors: [ + Color.basicPurple.opacity(0.1), + Color.basicPurple.opacity(0.2), + Color.basicPurple.opacity(0.25), + Color.basicPurple.opacity(0.2), + Color.basicPurple.opacity(0.1) + ]), + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + ) + .background(Color.white) + .clipShape(RoundedRectangle(cornerRadius: 16)) + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(Color.basicPurple.opacity(0.15), lineWidth: 0.5) + ) + .shadow(color: .black.opacity(0.2), radius: 2, x: 0, y: 3) + + // 결제하기 버튼 + Button(action: onPaymentTapped) { + Text("결제하기") + .frame(maxWidth: .infinity) + .font(.system(size: 18, weight: .bold)) + .foregroundStyle(Color.white) + .padding(.vertical, 16) + .background(Color.basicPurple) + .clipShape(Capsule()) + } + } + } +} + +#Preview { + PaymentInfoCard( + posterPath: "/bUrReoZFLGti6ehkBW0xw8f12MT.jpg", + title: "The Dark Knight", + pricePerTicket: 13000, + numberOfPeople: 2, + onPaymentTapped: { + print("결제하기 버튼 눌림") + } + ) + .padding() +} diff --git a/MovieBooking/Feature/MovieBook/MovieBookView.swift b/MovieBooking/Feature/MovieBook/MovieBookView.swift new file mode 100644 index 0000000..01f094c --- /dev/null +++ b/MovieBooking/Feature/MovieBook/MovieBookView.swift @@ -0,0 +1,60 @@ +// +// MovieBookView.swift +// MovieBooking +// +// Created by 홍석현 on 10/17/25. +// + +import SwiftUI + +struct MovieBookView: View { + let posterPath: String + let title: String + let pricePerTicket: Int = 13000 + + @State private var selectedTheater: String = "CGV 강남" + @State private var selectedTime: String = "14:30" + @State private var numberOfPeople: Int = 1 + + private let theaters = ["CGV 강남", "메가박스 코엑스", "롯데시네마 월드타워", "CGV 용산"] + private let times = ["10:00", "12:30", "14:30", "17:00", "19:30", "22:00"] + + var body: some View { + ScrollView { + VStack(spacing: 24) { + // 극장/시간/인원 선택 카드 + BookingOptionsCard( + theaters: theaters, + times: times, + selectedTheater: $selectedTheater, + selectedTime: $selectedTime, + numberOfPeople: $numberOfPeople + ) + // 결제 정보 카드 + PaymentInfoCard( + posterPath: posterPath, + title: title, + pricePerTicket: pricePerTicket, + numberOfPeople: numberOfPeople, + onPaymentTapped: handlePayment + ) + } + .padding(24) + } + } + + private func handlePayment() { + print("결제하기 버튼 눌림") + print("극장: \(selectedTheater)") + print("시간: \(selectedTime)") + print("인원: \(numberOfPeople)명") + print("총액: ₩\((pricePerTicket * numberOfPeople).formatted())") + } +} + +#Preview { + MovieBookView( + posterPath: "/bUrReoZFLGti6ehkBW0xw8f12MT.jpg", + title: "The Dark Knight" + ) +}