// lib/model/activity/activity_library_bootstrap.dart import 'dart:io'; import 'package:aves/model/settings/defaults.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/services/common/services.dart'; import 'package:flutter/material.dart'; class ActivityLibraryBootstrap { static bool _running = false; static Future ensureConfigured(BuildContext context) async { if (_running) return; _running = true; try { final existingTreeUri = settings.activityRootTreeUri; // Già configurata: non chiedere più nulla. // Però proviamo comunque a garantire che esista Activity/main. if (existingTreeUri != null && existingTreeUri.isNotEmpty) { final mainReady = await storageService.ensureActivityMainDirectory( existingTreeUri, ); if (!mainReady && context.mounted) { await _showActivityMainCreationFailedDialog( context: context, ); } return; } final rootPath = settings.activityRootPath; final createdOrExists = await _tryCreateRootDirectory(rootPath); if (!context.mounted) return; if (createdOrExists) { final shouldContinue = await _showActivityRootReadyDialog( context: context, rootPath: rootPath, ); if (!shouldContinue) { settings.activityRootPromptDone = true; return; } } else { final understood = await _showActivityRootMissingDialog( context: context, rootPath: rootPath, ); if (!understood) { settings.activityRootPromptDone = true; return; } if (!context.mounted) return; final shouldOpenPicker = await _showOpenPickerDialog( context: context, rootPath: rootPath, ); if (!shouldOpenPicker) { settings.activityRootPromptDone = true; return; } } if (!context.mounted) return; await _pickValidateAndSaveActivityRoot( context: context, rootPath: rootPath, ); } catch (e, st) { debugPrint('[ActivityBootstrap] failed to configure activity library: $e'); debugPrint('[ActivityBootstrap] stack: $st'); } finally { _running = false; } } static Future _pickValidateAndSaveActivityRoot({ required BuildContext context, required String rootPath, }) async { while (context.mounted) { // Piccola pausa per lasciare chiudere completamente il dialog Flutter // prima di aprire il picker Android SAF. await Future.delayed(const Duration(milliseconds: 300)); if (!context.mounted) return; final picked = await storageService.pickActivityDirectory(); if (picked == null || picked.treeUri.isEmpty) { settings.activityRootPromptDone = true; debugPrint('[ActivityBootstrap] user did not select activity directory'); return; } final selectedPath = picked.path; debugPrint('[ActivityBootstrap] picked treeUri=${picked.treeUri}'); debugPrint('[ActivityBootstrap] picked path=$selectedPath'); if (!_isExpectedActivityRoot(selectedPath, rootPath)) { if (!context.mounted) return; final retry = await _showWrongActivityRootDialog( context: context, expectedPath: rootPath, selectedPath: selectedPath, ); if (!retry) { settings.activityRootPromptDone = true; return; } if (!context.mounted) return; final shouldOpenPicker = await _showOpenPickerDialog( context: context, rootPath: rootPath, ); if (!shouldOpenPicker) { settings.activityRootPromptDone = true; return; } continue; } settings.activityRootPath = rootPath; settings.activityRootTreeUri = picked.treeUri; settings.activityRootPromptDone = true; debugPrint('[ActivityBootstrap] saved activity root path=$rootPath'); debugPrint('[ActivityBootstrap] saved activity treeUri=${picked.treeUri}'); final mainReady = await storageService.ensureActivityMainDirectory( picked.treeUri, ); debugPrint('[ActivityBootstrap] Activity/main ready=$mainReady'); if (!mainReady && context.mounted) { await _showActivityMainCreationFailedDialog( context: context, ); } return; } } static Future _tryCreateRootDirectory(String rootPath) async { try { final dir = Directory(rootPath); if (!await dir.exists()) { await dir.create(recursive: true); debugPrint('[ActivityBootstrap] attempted to create root directory: $rootPath'); } final existsAfterCreate = await dir.exists(); if (existsAfterCreate) { debugPrint('[ActivityBootstrap] root directory exists: $rootPath'); } else { debugPrint('[ActivityBootstrap] root directory still missing after create attempt: $rootPath'); } return existsAfterCreate; } catch (e, st) { debugPrint('[ActivityBootstrap] failed to create root directory: $rootPath'); debugPrint('[ActivityBootstrap] error: $e'); debugPrint('[ActivityBootstrap] stack: $st'); return false; } } static String _normalizePath(String path) { final trimmed = path.trim(); return trimmed.endsWith('/') ? trimmed : '$trimmed/'; } static bool _isExpectedActivityRoot( String? selectedPath, String expectedPath, ) { if (selectedPath == null || selectedPath.trim().isEmpty) { return false; } return _normalizePath(selectedPath) == _normalizePath(expectedPath); } static Future _showActivityRootReadyDialog({ required BuildContext context, required String rootPath, }) async { final result = await showDialog( context: context, barrierDismissible: false, builder: (dialogContext) { return AlertDialog( title: const Text('Cartella attività'), content: Text( 'Aves deve accedere alla cartella:\n\n' '$rootPath\n\n' 'La cartella esiste già o è stata creata correttamente.\n\n' 'Nella schermata successiva seleziona questa cartella per permettere ' 'ad Aves di scansionare i file FIT, GPX e TCX.\n\n' 'Dopo il consenso Aves creerà automaticamente anche la sottocartella:\n\n' '$rootPath/main', ), actions: [ TextButton( onPressed: () => Navigator.of(dialogContext).pop(false), child: const Text('Più tardi'), ), TextButton( onPressed: () => Navigator.of(dialogContext).pop(true), child: const Text('Continua'), ), ], ); }, ); return result ?? false; } static Future _showActivityRootMissingDialog({ required BuildContext context, required String rootPath, }) async { final result = await showDialog( context: context, barrierDismissible: false, builder: (dialogContext) { return AlertDialog( title: const Text('Cartella Activity mancante'), content: Text( 'Aves non è riuscita a creare automaticamente la cartella:\n\n' '$rootPath\n\n' 'Questo può succedere su Android moderno per le limitazioni di accesso ' 'allo storage.\n\n' 'Ora dovrai creare manualmente la cartella "Activity" nella memoria interna ' 'del dispositivo, oppure selezionarla se esiste già.\n\n' 'Percorso richiesto:\n' '/storage/emulated/0/Activity\n\n' 'Dopo il consenso Aves proverà a creare automaticamente la sottocartella:\n\n' '/storage/emulated/0/Activity/main', ), actions: [ TextButton( onPressed: () => Navigator.of(dialogContext).pop(false), child: const Text('Annulla'), ), TextButton( onPressed: () => Navigator.of(dialogContext).pop(true), child: const Text('OK, ho capito'), ), ], ); }, ); return result ?? false; } static Future _showOpenPickerDialog({ required BuildContext context, required String rootPath, }) async { final result = await showDialog( context: context, barrierDismissible: false, builder: (dialogContext) { return AlertDialog( title: const Text('Seleziona cartella Activity'), content: Text( 'Nella schermata successiva seleziona esattamente questa cartella:\n\n' '$rootPath\n\n' 'Se non esiste, creala dal selettore file o da un file manager, poi torna qui ' 'e selezionala.\n\n' 'Aves userà questa cartella come root della libreria attività.\n\n' 'Dopo il consenso Aves creerà automaticamente anche:\n\n' '$rootPath/main', ), actions: [ TextButton( onPressed: () => Navigator.of(dialogContext).pop(false), child: const Text('Più tardi'), ), TextButton( onPressed: () => Navigator.of(dialogContext).pop(true), child: const Text('Apri selettore cartella'), ), ], ); }, ); return result ?? false; } static Future _showWrongActivityRootDialog({ required BuildContext context, required String expectedPath, required String? selectedPath, }) async { final result = await showDialog( context: context, barrierDismissible: false, builder: (dialogContext) { return AlertDialog( title: const Text('Cartella non corretta'), content: Text( 'Hai selezionato una cartella diversa da quella richiesta.\n\n' 'Cartella selezionata:\n' '${selectedPath == null || selectedPath.isEmpty ? 'non riconosciuta' : selectedPath}\n\n' 'Cartella richiesta:\n' '$expectedPath\n\n' 'Devi creare o selezionare esattamente la cartella "Activity" nella memoria interna del dispositivo.\n\n' 'Non selezionare "Activity/main", "Download", "Documents" o altre cartelle.', ), actions: [ TextButton( onPressed: () => Navigator.of(dialogContext).pop(false), child: const Text('Più tardi'), ), TextButton( onPressed: () => Navigator.of(dialogContext).pop(true), child: const Text('Riprova'), ), ], ); }, ); return result ?? false; } static Future _showActivityMainCreationFailedDialog({ required BuildContext context, }) async { await showDialog( context: context, barrierDismissible: true, builder: (dialogContext) { return AlertDialog( title: const Text('Sottocartella main non creata'), content: Text( 'Aves ha ottenuto l’accesso alla cartella Activity, ma non è riuscita ' 'a creare automaticamente la sottocartella:\n\n' '${SettingsDefaults.activityRootPath}/main\n\n' 'Puoi crearla manualmente con un file manager.\n\n' 'La scansione continuerà comunque a funzionare sulla cartella Activity ' 'e sulle sue sottocartelle esistenti.', ), actions: [ TextButton( onPressed: () => Navigator.of(dialogContext).pop(), child: const Text('OK'), ), ], ); }, ); } static Future resetConfiguration() async { settings.activityRootTreeUri = null; settings.activityRootPromptDone = false; settings.activityRootPath = SettingsDefaults.activityRootPath; } }