// // MigrateView+Content.swift // Passepartout // // Created by Davide De Rosa on 11/14/24. // Copyright (c) 2024 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn // // This file is part of Passepartout. // // Passepartout is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Passepartout is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Passepartout. If not, see . // import CommonLibrary import PassepartoutKit import SwiftUI struct MigrateContentView: View where PerformButton: View { let style: MigrateView.Style let step: MigrateViewStep let profiles: [MigratableProfile] @Binding var statuses: [UUID: MigrationStatus] @Binding var isEditing: Bool let onDelete: ([MigratableProfile]) -> Void let performButton: () -> PerformButton var body: some View { switch style { case .list: ListView( step: step, profiles: profiles, statuses: $statuses, isEditing: $isEditing, onDelete: onDelete, performButton: performButton ) case .table: TableView( step: step, profiles: profiles, statuses: $statuses, onDelete: onDelete, performButton: performButton ) } } } extension Optional where Wrapped == MigrationStatus { var style: some ShapeStyle { switch self { case .excluded, .failed: return .secondary default: return .primary } } } extension Dictionary where Key == UUID, Value == MigrationStatus { func style(for profileId: UUID) -> some ShapeStyle { self[profileId].style } } // MARK: - Previews #Preview("Fetched") { PrivatePreviews.MigratePreview( step: .fetched(PrivatePreviews.profiles), profiles: PrivatePreviews.profiles, initialStatuses: [ PrivatePreviews.profiles[1].id: .excluded, PrivatePreviews.profiles[2].id: .excluded ] ) .withMockEnvironment() } #Preview("Migrated") { PrivatePreviews.MigratePreview( step: .migrated([]), profiles: PrivatePreviews.profiles, initialStatuses: [ PrivatePreviews.profiles[0].id: .excluded, PrivatePreviews.profiles[1].id: .pending, PrivatePreviews.profiles[2].id: .done, PrivatePreviews.profiles[3].id: .failed ] ) .withMockEnvironment() } #Preview("Empty") { PrivatePreviews.MigratePreview( step: .fetched([]), profiles: [], initialStatuses: [:] ) .withMockEnvironment() } private struct PrivatePreviews { static let oneDay: TimeInterval = 24 * 60 * 60 static let profiles: [MigratableProfile] = [ .init(id: UUID(), name: "1 One", lastUpdate: Date().addingTimeInterval(-oneDay)), .init(id: UUID(), name: "2 Two", lastUpdate: Date().addingTimeInterval(-3 * oneDay)), .init(id: UUID(), name: "3 Three", lastUpdate: Date().addingTimeInterval(-90 * oneDay)), .init(id: UUID(), name: "4 Four", lastUpdate: Date().addingTimeInterval(-180 * oneDay)), .init(id: UUID(), name: "5 Five", lastUpdate: Date().addingTimeInterval(-240 * oneDay)) ] struct MigratePreview: View { let step: MigrateViewStep let profiles: [MigratableProfile] let initialStatuses: [UUID: MigrationStatus] @State private var statuses: [UUID: MigrationStatus] = [:] @State private var isEditing = false #if os(iOS) private let style: MigrateView.Style = .list #else private let style: MigrateView.Style = .table #endif var body: some View { MigrateContentView( style: style, step: step, profiles: profiles, statuses: $statuses, isEditing: $isEditing, onDelete: { _ in }, performButton: { Button("Item") {} } ) .navigationTitle("Migrate") .themeNavigationStack() .task { statuses = initialStatuses } } } }