refactor(itinerary): replace CustomItineraryItem with ItineraryItem across codebase
- Update CKModels.swift to remove deleted type references - Migrate LocalCustomItem to LocalItineraryItem in SavedTrip.swift - Update AppDelegate to handle subscription removal - Refactor AddItemSheet to create ItineraryItem with CustomInfo - Update ItineraryTableViewController and Wrapper for new model - Refactor TripDetailView state, methods and callbacks - Fix TripMapView to display custom items with new model structure This completes the migration from the legacy CustomItineraryItem/TravelDayOverride model to the unified ItineraryItem model with ItemKind enum. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -151,11 +151,11 @@
|
||||
// - Parameters: itemId, newDay (1-indexed), newSortOrder
|
||||
// - Parent updates item and syncs to CloudKit
|
||||
//
|
||||
// onCustomItemTapped: ((CustomItineraryItem) -> Void)?
|
||||
// onCustomItemTapped: ((ItineraryItem) -> Void)?
|
||||
// - Called when user taps custom item row
|
||||
// - Parent presents edit sheet
|
||||
//
|
||||
// onCustomItemDeleted: ((CustomItineraryItem) -> Void)?
|
||||
// onCustomItemDeleted: ((ItineraryItem) -> Void)?
|
||||
// - Called from context menu delete action
|
||||
// - Parent deletes from CloudKit
|
||||
//
|
||||
@@ -209,8 +209,8 @@
|
||||
//
|
||||
// - ItineraryTableViewWrapper.swift: SwiftUI bridge, data transformation
|
||||
// - TripDetailView.swift: Parent view, owns state, handles callbacks
|
||||
// - CustomItineraryItem.swift: Domain model with (day, sortOrder) positioning
|
||||
// - CustomItemService.swift: CloudKit persistence for custom items
|
||||
// - ItineraryItem.swift: Domain model with (day, sortOrder, kind) positioning
|
||||
// - ItineraryItemService.swift: CloudKit persistence for itinerary items
|
||||
//
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -250,7 +250,7 @@ enum ItineraryRowItem: Identifiable, Equatable {
|
||||
case dayHeader(dayNumber: Int, date: Date) // Fixed: structural anchor (includes Add button)
|
||||
case games([RichGame], dayNumber: Int) // Fixed: games are trip-determined
|
||||
case travel(TravelSegment, dayNumber: Int) // Reorderable: within valid range
|
||||
case customItem(CustomItineraryItem) // Reorderable: anywhere
|
||||
case customItem(ItineraryItem) // Reorderable: anywhere
|
||||
|
||||
/// Stable identifier for table view diffing and external references.
|
||||
/// Travel IDs are lowercase to ensure consistency across sessions.
|
||||
@@ -297,8 +297,8 @@ final class ItineraryTableViewController: UITableViewController {
|
||||
// Callbacks
|
||||
var onTravelMoved: ((String, Int) -> Void)? // travelId, newDay
|
||||
var onCustomItemMoved: ((UUID, Int, Double) -> Void)? // itemId, newDay, newSortOrder
|
||||
var onCustomItemTapped: ((CustomItineraryItem) -> Void)?
|
||||
var onCustomItemDeleted: ((CustomItineraryItem) -> Void)?
|
||||
var onCustomItemTapped: ((ItineraryItem) -> Void)?
|
||||
var onCustomItemDeleted: ((ItineraryItem) -> Void)?
|
||||
var onAddButtonTapped: ((Int) -> Void)? // Just day number
|
||||
|
||||
// Cell reuse identifiers
|
||||
@@ -981,7 +981,7 @@ final class ItineraryTableViewController: UITableViewController {
|
||||
|
||||
/// Custom item cell - shows user-added item with category icon.
|
||||
/// Selectable (opens edit sheet on tap) and draggable.
|
||||
private func configureCustomItemCell(_ cell: UITableViewCell, item: CustomItineraryItem) {
|
||||
private func configureCustomItemCell(_ cell: UITableViewCell, item: ItineraryItem) {
|
||||
cell.contentConfiguration = UIHostingConfiguration {
|
||||
CustomItemRowView(item: item, colorScheme: colorScheme)
|
||||
}
|
||||
@@ -1225,36 +1225,42 @@ struct TravelRowView: View {
|
||||
///
|
||||
/// This row is both tappable (opens edit sheet) and draggable.
|
||||
struct CustomItemRowView: View {
|
||||
let item: CustomItineraryItem
|
||||
let item: ItineraryItem
|
||||
let colorScheme: ColorScheme
|
||||
|
||||
private var customInfo: CustomInfo? {
|
||||
item.customInfo
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: Theme.Spacing.sm) {
|
||||
// Category icon (emoji)
|
||||
Text(item.category.icon)
|
||||
.font(.title3)
|
||||
if let info = customInfo {
|
||||
Text(info.icon)
|
||||
.font(.title3)
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
HStack(spacing: 4) {
|
||||
Text(item.title)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
.lineLimit(1)
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
HStack(spacing: 4) {
|
||||
Text(info.title)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
.lineLimit(1)
|
||||
|
||||
// Map pin indicator for items with coordinates
|
||||
if item.isMappable {
|
||||
Image(systemName: "mappin.circle.fill")
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
// Map pin indicator for items with coordinates
|
||||
if info.isMappable {
|
||||
Image(systemName: "mappin.circle.fill")
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Address subtitle (shown only if present)
|
||||
if let address = item.address, !address.isEmpty {
|
||||
Text(address)
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
.lineLimit(1)
|
||||
// Address subtitle (shown only if present)
|
||||
if let address = info.address, !address.isEmpty {
|
||||
Text(address)
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user