import SwiftUI import SwiftData struct AddBooksView: View { enum FlowStep: Int { case source case confirm case categorize case success } @EnvironmentObject private var router: AppRouter @Environment(\.modelContext) private var modelContext @ObservedObject var viewModel: AddBooksViewModel @State private var step: FlowStep = .source @State private var selectedBook: BookRemote? @State private var selectedCategory = "Design" @State private var selectedStatus: ReadingStatus = .wantToRead @State private var manualISBN = "" @State private var startReadingNow = false @State private var animateSuccess = false private let defaultCategories = ["Design", "Psychology", "Novels"] var body: some View { ScrollView { VStack(spacing: Theme.Spacing.medium) { progressHeader switch step { case .source: sourceStep case .confirm: confirmStep case .categorize: categorizeStep case .success: successStep } } .frame(maxWidth: .infinity, alignment: .top) .padding(.bottom, 24) } .padding(.horizontal, 16) .navigationTitle(String(localized: "add.title")) .navigationBarTitleDisplayMode(.inline) .overlay { if viewModel.isLoading { ProgressView() .controlSize(.large) } } .sensoryFeedback(.success, trigger: animateSuccess) } private var progressHeader: some View { HStack(spacing: 8) { ForEach(0..<4, id: \.self) { index in Capsule() .fill(index <= step.rawValue ? Color.accentColor : Color.gray.opacity(0.25)) .frame(height: 6) .animation(.easeOut(duration: 0.2), value: step.rawValue) } } .padding(.top, 4) } private var sourceStep: some View { VStack(spacing: Theme.Spacing.medium) { Picker("Mode", selection: $viewModel.mode) { ForEach(AddBooksViewModel.Mode.allCases, id: \.self) { mode in Text(mode.title).tag(mode) } } .pickerStyle(.segmented) Group { switch viewModel.mode { case .title: TextField(String(localized: "add.searchPlaceholder"), text: $viewModel.titleQuery) .textFieldStyle(.roundedBorder) .onChange(of: viewModel.titleQuery) { _, _ in viewModel.titleChanged() } case .scan: VStack(spacing: 10) { BarcodeScannerView { isbn in Task { await viewModel.searchByISBN(isbn) } } .frame(height: 240) .clipShape(RoundedRectangle(cornerRadius: 16)) HStack { TextField("ISBN manuel gir", text: $manualISBN) .textFieldStyle(.roundedBorder) .keyboardType(.numberPad) Button("Ara") { Task { await viewModel.searchByISBN(manualISBN) } } .buttonStyle(.borderedProminent) } } case .filter: VStack(spacing: 8) { TextField("Title", text: $viewModel.filterTitle) .textFieldStyle(.roundedBorder) TextField("YYYY", text: $viewModel.filterYear) .textFieldStyle(.roundedBorder) .keyboardType(.numberPad) Button("Apply") { Task { await viewModel.applyFilter() } } .buttonStyle(.borderedProminent) .frame(maxWidth: .infinity, alignment: .trailing) } } } if let error = viewModel.errorMessage { NetworkErrorView(message: error) { Task { await viewModel.searchByTitle() } } } if viewModel.results.isEmpty, !viewModel.isLoading { EmptyStateView( symbol: "magnifyingglass", title: "Sonuç bulunamadı", message: "Farklı bir başlık dene veya ISBN'i manuel gir.", buttonTitle: nil, action: nil ) } else { LazyVStack(spacing: 10) { ForEach(viewModel.results, id: \.id) { book in Button { selectedBook = book step = .confirm } label: { HStack(spacing: 12) { AsyncImage(url: book.coverImageUrl) { image in image.resizable().scaledToFill() } placeholder: { RoundedRectangle(cornerRadius: 8).fill(.gray.opacity(0.2)) } .frame(width: 48, height: 72) .clipShape(RoundedRectangle(cornerRadius: 8)) VStack(alignment: .leading, spacing: 4) { Text(book.title) .font(.headline) .lineLimit(2) Text(book.authors.joined(separator: ", ")) .font(.subheadline) .foregroundStyle(.secondary) .lineLimit(1) } Spacer() Image(systemName: "chevron.right") .foregroundStyle(.tertiary) } } .buttonStyle(.plain) .padding(12) .background(Color.white.opacity(0.75), in: RoundedRectangle(cornerRadius: 12, style: .continuous)) } } } } } private var confirmStep: some View { VStack(alignment: .leading, spacing: Theme.Spacing.medium) { if let book = selectedBook { BookCardView( title: book.title, author: book.authors.first ?? "", coverURL: book.coverImageUrl, status: .wantToRead, progress: nil ) } HStack { Button("Geri") { step = .source } .buttonStyle(.bordered) Spacer() Button("Bilgileri Onayla") { step = .categorize } .buttonStyle(.borderedProminent) } } } private var categorizeStep: some View { VStack(alignment: .leading, spacing: Theme.Spacing.medium) { Text("Kategori & Durum") .font(.title3.weight(.semibold)) Picker("Kategori", selection: $selectedCategory) { ForEach(defaultCategories, id: \.self) { category in Text(category).tag(category) } } .pickerStyle(.menu) Picker("Durum", selection: $selectedStatus) { ForEach(ReadingStatus.allCases) { status in Text(status.title).tag(status) } } .pickerStyle(.segmented) Toggle("Hemen okumaya başla", isOn: $startReadingNow) Button("Kitabı Kaydet") { saveSelectedBook() } .buttonStyle(.borderedProminent) .frame(maxWidth: .infinity, alignment: .trailing) Button("Geri") { step = .confirm } .buttonStyle(.bordered) } } private var successStep: some View { VStack(spacing: Theme.Spacing.large) { Image(systemName: "checkmark.circle.fill") .font(.system(size: 72)) .foregroundStyle(.green) .scaleEffect(animateSuccess ? 1 : 0.7) .opacity(animateSuccess ? 1 : 0.4) .animation(.spring(response: 0.5, dampingFraction: 0.7), value: animateSuccess) Text("Kitap başarıyla eklendi") .font(.title3.weight(.semibold)) HStack { Button("Yeni Kitap Ekle") { resetFlow() } .buttonStyle(.bordered) Button("Kitaplığa Dön") { router.selectedTab = .library resetFlow() } .buttonStyle(.borderedProminent) } } .frame(maxWidth: .infinity, maxHeight: .infinity) .onAppear { animateSuccess = true } } private func saveSelectedBook() { guard let book = selectedBook else { return } let progress = startReadingNow ? 0.1 : (selectedStatus == .finished ? 1 : 0) let status = startReadingNow ? ReadingStatus.reading : selectedStatus let local = LibraryBook( title: book.title, authorsString: book.authors.joined(separator: ", "), coverUrlString: book.coverImageUrl?.absoluteString, isbn10: book.isbn10, isbn13: book.isbn13, publishedYear: book.publishedYear, categoriesString: selectedCategory, summary: book.description, language: book.language, sourceLocale: book.sourceLocale, remotePayloadJson: nil, statusRaw: status.rawValue, readingProgress: progress ) modelContext.insert(local) try? modelContext.save() UIImpactFeedbackGenerator(style: .medium).impactOccurred() withAnimation(.easeInOut(duration: 0.25)) { step = .success animateSuccess = false } } private func resetFlow() { selectedBook = nil selectedCategory = defaultCategories[0] selectedStatus = .wantToRead startReadingNow = false manualISBN = "" step = .source animateSuccess = false } }