feat(vault): pick a notebook vault folder on first run
Some checks failed
CI / Windows build (push) Has been cancelled

Phase 0 of the file-based storage plan (docs/plans/
2026-06-24-file-based-storage.md). Foundation only — no editor or
DB change yet.

- VaultService (SharedPreferences): stores the vault root path,
  vaultRootValid() = path set AND directory exists.
- VaultSetupScreen: first-run folder picker (file_picker, Windows).
- main.dart gates HomeScreen behind a valid vault, re-prompting if
  the saved folder is gone.
- Settings: a Vault section to change the folder.

Editors still use SQLite; later phases move annotations into
per-file sidecars under the vault. analyze clean, tests green.
This commit is contained in:
2026-06-24 20:45:16 +08:00
parent 875dabcd89
commit 9fcac47ef2
10 changed files with 655 additions and 3 deletions

View File

@@ -10,7 +10,9 @@ import 'editor/pdf/pen_capture_region.dart';
import 'l10n/app_localizations.dart';
import 'providers/settings_provider.dart';
import 'screens/home_screen.dart';
import 'screens/vault_setup_screen.dart';
import 'services/database_service.dart';
import 'services/vault_service.dart';
Future<void> main() async {
// Kind-aware binding (extends WidgetsFlutterBinding) must be the active
@@ -64,7 +66,7 @@ class BadNoteApp extends ConsumerWidget {
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: AppLocalizations.supportedLocales,
home: const HomeScreen(),
home: const VaultGate(),
);
},
);
@@ -78,3 +80,56 @@ class BadNoteApp extends ConsumerWidget {
),
);
}
/// Startup gate: shows [HomeScreen] only once a valid vault root folder has been
/// chosen. If none is set — or the saved folder no longer exists — it shows
/// [VaultSetupScreen] first (re-prompting on a missing folder rather than
/// silently scattering data elsewhere). Phase 0: records the vault path only;
/// editors still use SQLite.
class VaultGate extends StatefulWidget {
const VaultGate({super.key});
@override
State<VaultGate> createState() => _VaultGateState();
}
class _VaultGateState extends State<VaultGate> {
VaultService? _vault;
bool _valid = false;
bool _hadStoredPath = false;
bool _loading = true;
@override
void initState() {
super.initState();
_check();
}
Future<void> _check() async {
final vault = await VaultService.getInstance();
final valid = await vault.vaultRootValid();
if (!mounted) return;
setState(() {
_vault = vault;
_valid = valid;
// A stored-but-invalid path means the chosen folder went missing.
_hadStoredPath = (vault.vaultRoot?.isNotEmpty ?? false);
_loading = false;
});
}
@override
Widget build(BuildContext context) {
if (_loading) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
if (_valid) return const HomeScreen();
return VaultSetupScreen(
vaultService: _vault!,
missing: _hadStoredPath,
onVaultReady: () => setState(() => _valid = true),
);
}
}