import 'dart:io'; import 'package:flutter/foundation.dart' show visibleForTesting; import 'package:shared_preferences/shared_preferences.dart'; /// Records the user-picked vault root folder (an Obsidian-style vault) and /// gates app startup behind a valid choice. /// /// The vault root is the single folder under which all notebooks will live. /// Phase 0 only persists the path and validates it exists; no data is moved /// into the vault yet (later phases do that). /// /// Persistence is SharedPreferences-backed under [vaultRootKey]. The service is /// easily mockable: inject a [SharedPreferences] (e.g. from /// `SharedPreferences.setMockInitialValues`) via the constructor for tests. class VaultService { /// SharedPreferences key under which the vault root path is stored. static const String vaultRootKey = 'vaultRoot'; final SharedPreferences _prefs; VaultService._(this._prefs); static VaultService? _instance; /// Singleton accessor, mirroring [DatabaseService.getInstance]. Lazily reads /// the shared [SharedPreferences] instance. static Future getInstance() async { if (_instance != null) return _instance!; final prefs = await SharedPreferences.getInstance(); final service = VaultService._(prefs); _instance = service; return service; } /// Test-only constructor: inject a (typically mock) [SharedPreferences] so /// the vault root can be exercised without platform channels. @visibleForTesting VaultService.forTest(SharedPreferences prefs) : _prefs = prefs; /// Test-only: drop the cached singleton so the next [getInstance] rebuilds. @visibleForTesting static void resetForTest() { _instance = null; } /// The currently stored vault root path, or null if none has been chosen. String? get vaultRoot => _prefs.getString(vaultRootKey); /// Persist [path] as the vault root. Future setVaultRoot(String path) async { await _prefs.setString(vaultRootKey, path); } /// Forget the stored vault root (e.g. to re-prompt the user). Future clearVaultRoot() async { await _prefs.remove(vaultRootKey); } /// True iff a vault root is set AND that directory currently exists. /// /// Returns false when no path is stored or when the stored path no longer /// resolves to a directory (external drive unplugged, folder deleted) — the /// caller then re-prompts rather than silently scattering data elsewhere. Future vaultRootValid() async { final path = vaultRoot; if (path == null || path.isEmpty) return false; return Directory(path).exists(); } }