RootView.swift 128 lignes · 4427 octets
import SwiftUI

// Port of ui/nav/Card2vcfNavHost.kt — NavigationStack + Carnet | Projets tabs.
// Deep flows: scan → draft → fiche, fiche → edit → rescan, duplicates → merge
// → fiche, carnet → import.

enum AppRoute: Hashable {
    case fiche(contactId: Int64, sort: ContactSort)
    case edit(contactId: Int64)
    case duplicates(contactId: Int64)
    case scan(editId: Int64?)
    case manual
    case importVcf
    case settings
    case projetDetail(serverId: String)
}

struct RootView: View {
    @State private var path: [AppRoute] = []
    @State private var tab: MainTab = .carnet

    // Long-lived view models (Android scoped them to the NavHost).
    @StateObject private var carnetVm = CarnetViewModel()
    @StateObject private var projetsVm = ProjetsViewModel()
    @StateObject private var scanVm = ScanCarteViewModel()
    @StateObject private var settingsVm = SettingsViewModel()
    @StateObject private var syncVm = SyncChromeViewModel()

    var body: some View {
        NavigationStack(path: $path) {
            rootTab
                .navigationDestination(for: AppRoute.self) { route in
                    destination(route)
                }
        }
        .tint(C2VColor.ink)
    }

    @ViewBuilder
    private var rootTab: some View {
        switch tab {
        case .carnet:
            CarnetScreen(
                viewModel: carnetVm,
                onOpenContact: { id, sort in path.append(.fiche(contactId: id, sort: sort)) },
                onScan: { path.append(.scan(editId: nil)) },
                onManual: { path.append(.manual) },
                onImportVcf: { path.append(.importVcf) },
                onSettings: { path.append(.settings) },
                onOpenProjets: { tab = .projets },
                syncViewModel: syncVm
            )
        case .projets:
            ProjetsListScreen(
                viewModel: projetsVm,
                onOpenCarnet: { tab = .carnet },
                onOpenProjet: { serverId in path.append(.projetDetail(serverId: serverId)) },
                onSettings: { path.append(.settings) },
                syncViewModel: syncVm
            )
        }
    }

    @ViewBuilder
    private func destination(_ route: AppRoute) -> some View {
        switch route {
        case .fiche(let contactId, let sort):
            ContactPagerScreen(
                contactId: contactId,
                sort: sort,
                onBack: { pop() },
                onEdit: { id in path.append(.edit(contactId: id)) },
                onDuplicates: { id in path.append(.duplicates(contactId: id)) },
                onDeleted: { path = [] }
            )
        case .edit(let contactId):
            ContactEditScreen(
                contactId: contactId,
                onBack: { pop() },
                onSaved: { pop() },
                onRescan: { id in path.append(.scan(editId: id)) }
            )
        case .duplicates(let contactId):
            DuplicateReviewScreen(
                contactId: contactId,
                onBack: { pop() },
                onMerged: { keepId in
                    // Kotlin: navigate fiche(keepId) popUpTo(carnet).
                    path = [.fiche(contactId: keepId, sort: .lastName)]
                }
            )
        case .scan(let editId):
            ScanCarteScreen(
                viewModel: scanVm,
                onBack: { pop() },
                onContactSaved: { id in
                    scanVm.editingContactId = nil
                    scanVm.reset()
                    path = [.fiche(contactId: id, sort: .lastName)]
                }
            )
            .onAppear {
                // Kotlin LaunchedEffect(editId).
                scanVm.editingContactId = editId
                scanVm.reset()
            }
        case .manual:
            ManualContactScreen(
                onBack: { pop() },
                onSaved: { id in
                    path = [.fiche(contactId: id, sort: .lastName)]
                }
            )
        case .importVcf:
            VcfImportScreen(onBack: { pop() })
        case .settings:
            SettingsScreen(viewModel: settingsVm, onBack: { pop() })
        case .projetDetail(let serverId):
            ProjetDetailScreen(serverId: serverId, onBack: { pop() })
        }
    }

    private func pop() {
        if !path.isEmpty {
            path.removeLast()
        }
    }
}