struct code for the section

 avatar
unknown
swift
3 months ago
23 kB
5
Indexable
struct ImageDetailView: View {
    
    private var board: SavedBoard?
        
    
    // State properties
    @State private var imageName: String
    @State private var selectedCars: [DraggableCar]
    @State private var selectedRoadSigns: [DraggableRoadSign]
    @State private var isSaveBoardModalPresented = false
    @State private var boardName: String = ""
    @State private var saveMessage: String?
    @State private var selectedCarID: UUID?
    @State private var currentPath = DrawnPath(path: Path(), color: Color.black, size: 5)
    @State private var paths: [DrawnPath] = []
    @State private var undonePaths: [DrawnPath] = []
    @State private var isDrawing = false
    @State private var strokeColor: Color = .red
    @State private var strokeSize: CGFloat = 5
    @State private var isRoadSignMenuPresented = false
    @State private var isCarMenuPresented = false
    
    // Initializer for loading a saved board
        init(board: SavedBoard) {
            self.board = board
            _imageName = State(initialValue: board.imageName)
            _selectedCars = State(initialValue: board.selectedCars)
            _selectedRoadSigns = State(initialValue: board.selectedSigns)
            _paths = State(initialValue: board.drawnPaths)

        }
        
        // Initializer for new board creation
        init(imageName: String) {
            self.board = nil
            _imageName = State(initialValue: imageName)
            _selectedCars = State(initialValue: [])
            _selectedRoadSigns = State(initialValue: [])
            _paths = State(initialValue: []) // Initialize empty paths

        }
    
    @Environment(\.colorScheme) var colorScheme
    
    var isIphone: Bool {
        UIDevice.current.userInterfaceIdiom == .phone
    }
    
    var isIpad: Bool {
        UIDevice.current.userInterfaceIdiom == .pad
    }
    
    var iconColor: Color {
        if isIpad {
            return .white
        } else {
            return colorScheme == .dark ? .white : .black
        }
    }
    
    var body: some View {

        GeometryReader { geometry in
            ZStack {
                Image(imageName)
                    .resizable()
                    .aspectRatio(contentMode: isIphone ? .fit : .fill)
                    .frame(width: geometry.size.width, height: geometry.size.height)
                    .edgesIgnoringSafeArea(.all)
                
                // Drawing canvas
                if isDrawing {
                    Canvas { context, size in
                        context.stroke(currentPath.path, with: .color(currentPath.color), lineWidth: currentPath.size)
                        for drawnPath in paths {
                            context.stroke(drawnPath.path, with: .color(drawnPath.color), lineWidth: drawnPath.size)
                        }
                    }
                    .background(Color.clear)
                    .gesture(
                        DragGesture(minimumDistance: 0)
                            .onChanged { value in
                                guard isDrawing else { return }
                                let newPoint = value.location
                                if currentPath.path.isEmpty {
                                    currentPath.path.move(to: newPoint)
                                } else {
                                    currentPath.path.addLine(to: newPoint)
                                }
                            }
                            .onEnded { _ in
                                if isDrawing {
                                    paths.append(currentPath)
                                    currentPath = DrawnPath(path: Path(), color: strokeColor, size: strokeSize)
                                    undonePaths.removeAll()
                                }
                            }
                    )
                } else {
                    Canvas { context, size in
                        for drawnPath in paths {
                            context.stroke(drawnPath.path, with: .color(drawnPath.color), lineWidth: drawnPath.size)
                        }
                    }
                    .background(Color.clear)
                }
                
                //                // Stroke size slider in the pencil section
                //                if isDrawing {
                //                    VStack {
                //                        HStack {
                //                            Text("")
                //                                .foregroundColor(.white)
                //                                .padding(.leading, 20)
                //                            Slider(value: $strokeSize, in: 1...20, step: 1)
                //                                .accentColor(strokeColor)
                //                                .padding(.trailing, 20)
                //                                .frame(width: 500)
                //                        }
                //                        .padding(.top, 930) // Adjusted to move it to the top
                //                        Spacer()
                //                    }
                //                }
                
                
                
                
                // Draggable and rotatable cars
                ForEach($selectedCars) { $car in
                    ZStack {
                        Image(car.imageName)
                            .resizable()
                            .scaledToFit()
                            .frame(width: car.size.width, height: car.size.height) // Use car.size
                            .rotationEffect(car.rotation, anchor: .center) // Rotate around its center
                            .position(car.position)
                        
                        // Rotation Handle
                        if selectedCarID == car.id { // Show the handle only for the selected car
                            ZStack {
                                // Blue Circle Background
                                Circle()
                                    .fill(Color.gray)
                                    .frame(width: 40, height: 40)
                                
                                // Clockwise Arrow Icon (inside the circle)
                                Image("rotation-clock+anticlock")
                                    .resizable()
                                    .scaledToFit()
                                    .frame(width: 30, height: 30)
                                    .foregroundColor(.white)
                            }
                            .position(
                                x: car.position.x + cos(car.rotation.radians) * 70,
                                y: car.position.y + sin(car.rotation.radians) * 70
                            )
                            .gesture(
                                DragGesture()
                                    .onChanged { value in
                                        // Calculate angle based on car center and drag position
                                        let dx = value.location.x - car.position.x
                                        let dy = value.location.y - car.position.y
                                        let angle = atan2(dy, dx)
                                        car.rotation = .radians(angle)
                                    }
                            )
                        }
                    }
                    .gesture(
                        DragGesture()
                            .onChanged { value in
                                car.position = value.location // Update position while dragging
                            }
                    )
                    .onTapGesture {
                        selectedCarID = (selectedCarID == car.id) ? nil : car.id // Toggle selection
                    }
                    .zIndex(selectedCarID == car.id ? 2 : 1) // Highlight selected car
                }
                
                // Rotation and Delete Buttons for selected car
                if let selectedCar = selectedCars.first(where: { $0.id == selectedCarID }) {
                    HStack {
                        // Rotation Button
                        Button(action: {
                            if let index = selectedCars.firstIndex(where: { $0.id == selectedCarID }) {
                                selectedCars[index].rotation += .degrees(15) // Increment rotation by 15 degrees
                            }
                        }) {
                            Image(systemName: "arrow.clockwise.circle.fill")
                                .resizable()
                                .frame(width: 0, height: 0)
                                .foregroundColor(.blue)
                        }
                        
                        // Delete Button
                        Button(action: {
                            selectedCars.removeAll { $0.id == selectedCarID } // Remove selected car
                            selectedCarID = nil // Deselect after deletion
                        }) {
                            Image(systemName: "trash.fill")
                                .resizable()
                                .frame(width: 40, height: 40)
                                .foregroundColor(.red)
                        }
                    }
                    .position(x: selectedCar.position.x + 120, y: selectedCar.position.y) // Position buttons near the car
                }
                
                
                ForEach($selectedRoadSigns) { $roadSign in
                    ZStack {
                        // Road sign image
                        Image(roadSign.imageName)
                            .resizable()
                            .scaledToFit()
                            .frame(width: roadSign.imageName == "discount for you only - 42" ? 320 : 100, height: roadSign.imageName == "discount for you only - 42" ? 320 : 100)
                            .rotationEffect(roadSign.rotation) // Apply rotation
                            .position(roadSign.position)
                            .onTapGesture {
                                // Toggle selection
                                roadSign.isSelected.toggle()
                                roadSign.zIndex = roadSign.isSelected ? 10 : 0
                            }
                        
                        // Conditionally show trash icon and rotation handle if the sign is selected
                        if roadSign.isSelected {
                            // Trash icon
                            Image(systemName: "trash.fill")
                                .resizable()
                                .scaledToFit()
                                .frame(width: 40, height: 80)
                                .font(.system(size: 40))
                                .foregroundColor(.red)
                                .position(x: roadSign.position.x + 80, y: roadSign.position.y - 80)
                                .onTapGesture {
                                    if let index = selectedRoadSigns.firstIndex(where: { $0.id == roadSign.id }) {
                                        selectedRoadSigns.remove(at: index)
                                    }
                                }
                            
                            // Rotation handle (matching the car's style)
                            ZStack {
                                // Blue Circle Background
                                Circle()
                                    .fill(Color.gray)
                                    .frame(width: 40, height: 40)
                                
                                // Clockwise Arrow Icon (inside the circle)
                                Image("rotation-clock+anticlock") // Use the same image as the car's rotation handle
                                    .resizable()
                                    .scaledToFit()
                                    .frame(width: 30, height: 30)
                                    .foregroundColor(.white)
                            }
                            .position(
                                x: roadSign.position.x + cos(roadSign.rotation.radians) * 70, // Fixed distance from center
                                y: roadSign.position.y + sin(roadSign.rotation.radians) * 70
                            )
                            .gesture(
                                DragGesture()
                                    .onChanged { value in
                                        // Calculate angle based on road sign center and drag position
                                        let dx = value.location.x - roadSign.position.x
                                        let dy = value.location.y - roadSign.position.y
                                        let angle = atan2(dy, dx)
                                        roadSign.rotation = .radians(angle)
                                    }
                            )
                        }
                    }
                    .gesture(
                        DragGesture()
                            .onChanged { value in
                                roadSign.position = value.location // Update position while dragging
                            }
                    )
                    .zIndex(roadSign.zIndex)
                }
                
                // Toolbar at the bottom
                VStack {
                    Spacer()
                    HStack {
                        // Road Sign Menu Icon
                        Button(action: { isRoadSignMenuPresented.toggle() }) {
                            Image(systemName: "signpost.left.fill") // You can change this icon as desired
                                .resizable()
                                .scaledToFit()
                                .frame(width: 40, height: 40)
                                .foregroundColor(iconColor)
                                .padding(10)
                                .background(Circle().stroke(iconColor, lineWidth: 2))
                        }
                        
                        .sheet(isPresented: $isRoadSignMenuPresented) {
                            RoadSignMenuView(selectedRoadSigns: $selectedRoadSigns)
                        }
                        
                        // Car menu button
                        Button(action: {
                            isCarMenuPresented.toggle()
                        }) {
                            Image(systemName: "car.fill")
                                .resizable()
                                .scaledToFit()
                                .frame(width: 40, height: 40)
                                .foregroundColor(iconColor)
                                .padding(10)
                                .background(Circle().stroke(iconColor, lineWidth: 2))
                        }
                        .sheet(isPresented: $isCarMenuPresented) {
                            CarMenuView(selectedCars: $selectedCars)
                        }
                        
                        Spacer()
                        
                        // Save Board Button
                        Button(action: { isSaveBoardModalPresented.toggle()
                        }) {
                               Text("Save Board")
                                   .foregroundColor(.black)
                                   .padding(10)
                                   .background(Capsule().fill(Color.white))
                                   .overlay(Capsule().stroke(Color.black, lineWidth: 2)) // Black outline
                           }
                        
                        // Undo, redo, clear, and drawing toggle buttons
                        if isDrawing {
                            Button(action: undo) {
                                Image(systemName: "arrow.uturn.backward.circle.fill")
                                    .resizable()
                                    .scaledToFit()
                                    .frame(width: 40, height: 40)
                                    .foregroundColor(iconColor)
                                    .padding(10)
                                    .background(Circle().stroke(iconColor, lineWidth: 2))
                            }
                            
                            Button(action: redo) {
                                Image(systemName: "arrow.uturn.forward.circle.fill")
                                    .resizable()
                                    .scaledToFit()
                                    .frame(width: 40, height: 40)
                                    .foregroundColor(iconColor)
                                    .padding(10)
                                    .background(Circle().stroke(iconColor, lineWidth: 2))
                            }
                            
                            Button(action: clearDrawing) {
                                Image(systemName: "trash.fill")
                                    .resizable()
                                    .scaledToFit()
                                    .frame(width: 40, height: 40)
                                    .foregroundColor(iconColor)
                                    .padding(10)
                                    .background(Circle().stroke(iconColor, lineWidth: 2))
                            }
                        }
                        
                        Button(action: {
                            if !isDrawing {
                                currentPath = DrawnPath(path: Path(), color: strokeColor, size: strokeSize)
                            }
                            isDrawing.toggle()
                        }) {
                            Image(systemName: "pencil.circle.fill")
                                .resizable()
                                .scaledToFit()
                                .frame(width: 40, height: 40)
                                .foregroundColor(iconColor)
                                .padding(10)
                                .background(Circle().stroke(iconColor, lineWidth: 2))
                        }
                    }
                    .padding(.horizontal, 20)
                    .padding(.vertical, 15)
                    .padding(.bottom, geometry.safeAreaInsets.bottom) // Respect bottom safe area

                }
                .zIndex(1)
            }
        }
        .sheet(isPresented: $isSaveBoardModalPresented) {
              VStack {
                  Text("Save Board")
                      .font(.title)
                      .padding()

                  TextField("Enter board name", text: $boardName)
                      .textFieldStyle(RoundedBorderTextFieldStyle())
                      .padding()

                  // Display save message if available
                         if let message = saveMessage {
                             Text(message)
                                 .foregroundColor(message.contains("Error") || message == "Board name cannot be empty" ? .red : .green)
                                 .padding()
                         }
                  HStack {
                                    Button("Close") {
                                        isSaveBoardModalPresented = false
                                        saveMessage = nil
                                        boardName = ""
                                    }
                                    .padding()
                                    .background(Color.red)
                                    .foregroundColor(.white)
                                    .cornerRadius(8)
                                    
                                    Button("Save") {
                                        saveBoard()
                                    }
                                    .padding()
                                    .background(Color.blue)
                                    .foregroundColor(.white)
                                    .cornerRadius(8)
                                }
                            }
                            .padding()
                            .frame(width: 300, height: 200)
                        }
        .navigationBarBackButtonHidden(false)
        
      }

    func saveBoard() {
        guard !boardName.isEmpty else {
            saveMessage = "Board name cannot be empty"
            return
        }
        
        do {
            // Create the SavedBoard object
            let newBoard = SavedBoard(
                name: boardName,
                imageName: imageName,
                selectedCars: selectedCars,
                selectedSigns: selectedRoadSigns,
                drawnPaths: paths // Include drawn paths

            )
            
            // Encode to JSON
            let encoder = JSONEncoder()
            encoder.outputFormatting = .prettyPrinted
            let data = try encoder.encode(newBoard)
            
            // Create filename (sanitize for filesystem)
            let sanitizedFileName = boardName.replacingOccurrences(of: " ", with: "_")
                .filter { $0.isLetter || $0.isNumber || $0 == "_" }
            let fileURL = getDocumentsDirectory()
                .appendingPathComponent("\(sanitizedFileName).json")
            print("Saving file to: \(fileURL.path)")  // Print the path to console

            // Save to documents directory
            try data.write(to: fileURL, options: .atomic)
            
            saveMessage = "Board saved successfully!"
            boardName = ""  // Clear the name field
//            isSaveBoardModalPresented = false  // Close the modal
            
        } catch {
            saveMessage = "Error saving board: \(error.localizedDescription)"
        }
    }
    
    func sanitizeFileName(_ name: String) -> String {
        let invalidCharacters = CharacterSet(charactersIn: ":/\\?%*|\"<>")
        return name.components(separatedBy: invalidCharacters).joined(separator: "")
    }

    private func undo() {
        if let lastPath = paths.last {
            paths.removeLast()
            undonePaths.append(lastPath)
        }
    }

    private func redo() {
        if let lastUndonePath = undonePaths.last {
            undonePaths.removeLast()
            paths.append(lastUndonePath)
        }
    }

    private func clearDrawing() {
        paths.removeAll()
        undonePaths.removeAll()
        currentPath = DrawnPath(path: Path(), color: strokeColor, size: strokeSize)
    }
}

Editor is loading...
Leave a Comment