EnregistrementNoteSheet.swift
258 lignes · 9240 octets
import SwiftUI /// Feuille d'enregistrement de note vocale. /// /// Commune à la fiche contact (crée une `InteractionEntity` de type `note_vocale`) /// et à la section Notes du projet (crée une `NoteProjetEntity`). /// La logique de sauvegarde est fournie par `sauvegarder`. struct EnregistrementNoteSheet: View { @StateObject private var viewModel: EnregistrementNoteViewModel @Environment(\.dismiss) private var dismiss /// - Parameters: /// - modeTranscription: mode lu depuis `PreferencesAudio` par défaut. /// - sauvegarder: `(sujet, description, audioPath, demandeTranscription) async -> envoye`. init( modeTranscription: ModeTranscription = PreferencesAudio.modeTranscription, sauvegarder: @escaping (String, String, String, Bool) async -> Bool ) { _viewModel = StateObject(wrappedValue: EnregistrementNoteViewModel( modeTranscription: modeTranscription, sauvegarder: sauvegarder )) } var body: some View { VStack(alignment: .leading, spacing: 0) { barreHaut switch viewModel.phase { case .demandePermission: chargementView(label: "Vérification des permissions…") case .permissionMicRefusee: permissionRefuseeView( icone: "mic.slash.circle", titre: "Microphone", message: "L'accès au microphone est requis pour enregistrer une note vocale. Activez-le dans Réglages → Confidentialité." ) case .permissionSpeechRefusee: permissionRefuseeView( icone: "waveform.slash", titre: "Reconnaissance vocale", message: "Autorisez la reconnaissance vocale pour activer la transcription sur l'appareil. Activez-la dans Réglages → Confidentialité." ) case .pret: pretView case .enregistrement: enregistrementView case .edition: editionView case .sauvegarde: chargementView(label: "Sauvegarde en cours…") case .termine(let message): termineView(message: message) } } .background(C2VColor.fond) .task { await viewModel.demanderPermissions() } } // MARK: - Barre haute private var barreHaut: some View { ZStack { HStack { Button("Annuler") { viewModel.annuler() dismiss() } .buttonStyle(C2VTextButtonStyle(color: C2VColor.brandError)) Spacer() } Text("Note vocale") .font(C2VFont.titleMedium) .foregroundColor(C2VColor.encre) } .padding(.horizontal, 12) .padding(.vertical, 10) .background(C2VColor.surface) } // MARK: - Bandeau d'avertissement permanent private var bandeauAvertissement: some View { HStack(spacing: 8) { Image(systemName: "exclamationmark.triangle.fill") .font(.system(size: 13)) Text("La transcription ne distingue pas les interlocuteurs") .font(C2VFont.bodySmall) } .foregroundColor(C2VColor.brandError) .padding(.horizontal, 16) .padding(.vertical, 8) .frame(maxWidth: .infinity, alignment: .leading) .background(C2VColor.brandErrorSoft) } // MARK: - Phase : prêt private var pretView: some View { VStack(spacing: 0) { bandeauAvertissement Spacer() VStack(spacing: 20) { Text("Appuyez pour démarrer l'enregistrement") .font(C2VFont.bodyMedium) .foregroundColor(C2VColor.texteFaible) .multilineTextAlignment(.center) .padding(.horizontal, 32) Button { viewModel.demarrer() } label: { Image(systemName: "mic.circle.fill") .font(.system(size: 84)) .foregroundColor(C2VColor.ink) } .buttonStyle(.plain) } Spacer() } .frame(maxWidth: .infinity) } // MARK: - Phase : enregistrement private var enregistrementView: some View { VStack(spacing: 0) { bandeauAvertissement Spacer() VStack(spacing: 20) { Text(viewModel.dureeFormatee) .font(.system(size: 60, weight: .light, design: .monospaced)) .foregroundColor(C2VColor.encre) if !viewModel.transcriptionEnDirect.isEmpty { Text(viewModel.transcriptionEnDirect) .font(C2VFont.bodyMedium) .foregroundColor(C2VColor.texteFaible) .multilineTextAlignment(.center) .padding(.horizontal, 24) } Button { viewModel.arreter() } label: { VStack(spacing: 8) { Image(systemName: "stop.circle.fill") .font(.system(size: 72)) .foregroundColor(C2VColor.brandError) Text("Arrêter") .font(C2VFont.labelMedium) .foregroundColor(C2VColor.brandError) } } .buttonStyle(.plain) } Spacer() } .frame(maxWidth: .infinity) } // MARK: - Phase : édition private var editionView: some View { ScrollView { VStack(alignment: .leading, spacing: 0) { bandeauAvertissement VStack(alignment: .leading, spacing: 12) { Text("Sujet") .font(C2VFont.labelMedium) .foregroundColor(C2VColor.texteFaible) C2VTextField( label: "", placeholder: "Sujet de la note", text: $viewModel.sujet ) Text("Texte / transcription") .font(C2VFont.labelMedium) .foregroundColor(C2VColor.texteFaible) C2VTextField( label: "", placeholder: "Texte (optionnel)", text: $viewModel.descriptionNote, minLines: 3 ) Button("Enregistrer") { viewModel.sauvegarderNote() } .buttonStyle(C2VPrimaryButtonStyle()) .padding(.top, 4) } .padding(16) } } } // MARK: - Phase : chargement private func chargementView(label: String) -> some View { VStack(spacing: 16) { Spacer() ProgressView() Text(label) .font(C2VFont.bodyMedium) .foregroundColor(C2VColor.texteFaible) Spacer() } .frame(maxWidth: .infinity) } // MARK: - Phase : terminé private func termineView(message: String) -> some View { VStack(spacing: 20) { Spacer() Image(systemName: "checkmark.circle.fill") .font(.system(size: 64)) .foregroundColor(C2VColor.brandGreen) Text(message) .font(C2VFont.bodyLarge) .foregroundColor(C2VColor.ink) .multilineTextAlignment(.center) .padding(.horizontal, 32) Button("Fermer") { dismiss() } .buttonStyle(C2VPrimaryButtonStyle()) .padding(.horizontal, 32) Spacer() } .frame(maxWidth: .infinity) } // MARK: - Permission refusée private func permissionRefuseeView(icone: String, titre: String, message: String) -> some View { VStack(spacing: 20) { Spacer() Image(systemName: icone) .font(.system(size: 60)) .foregroundColor(C2VColor.brandError) Text("Permission \(titre) refusée") .font(C2VFont.titleMedium) .foregroundColor(C2VColor.encre) Text(message) .font(C2VFont.bodyMedium) .foregroundColor(C2VColor.texteFaible) .multilineTextAlignment(.center) .padding(.horizontal, 32) Button("Ouvrir les Réglages") { if let url = URL(string: UIApplication.openSettingsURLString) { UIApplication.shared.open(url) } } .buttonStyle(C2VOutlineButtonStyle()) .padding(.horizontal, 32) Spacer() } .frame(maxWidth: .infinity) } }
GitRust