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

@@ -99,5 +99,21 @@
"@boardDanglingLink": {
"placeholders": { "target": { "type": "String" } }
},
"close": "Close"
"close": "Close",
"vaultSetupTitle": "Choose your vault",
"vaultSetupHeadline": "Pick a folder for your notebooks",
"vaultSetupBody": "BadNote stores your notebooks inside one folder you choose — like an Obsidian vault. Pick a folder you control (e.g. a synced folder) so your notes travel with their files.",
"vaultChooseFolder": "Choose folder",
"vaultMissingTitle": "Your vault folder is missing",
"vaultMissingBody": "The folder you picked can't be found (it may have been moved, deleted, or on a drive that's unplugged). Relocate it or pick a new one.",
"vaultPickFailed": "Couldn't open the folder picker: {error}",
"@vaultPickFailed": {
"placeholders": { "error": { "type": "String" } }
},
"vaultNotWritable": "That folder isn't writable. Please choose another.",
"vaultSection": "Vault",
"vaultFolderLabel": "Vault folder",
"vaultNoneSelected": "No folder selected",
"vaultChangeFolder": "Change vault folder",
"vaultUpdated": "Vault folder updated"
}

View File

@@ -571,6 +571,84 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'Close'**
String get close;
/// No description provided for @vaultSetupTitle.
///
/// In en, this message translates to:
/// **'Choose your vault'**
String get vaultSetupTitle;
/// No description provided for @vaultSetupHeadline.
///
/// In en, this message translates to:
/// **'Pick a folder for your notebooks'**
String get vaultSetupHeadline;
/// No description provided for @vaultSetupBody.
///
/// In en, this message translates to:
/// **'BadNote stores your notebooks inside one folder you choose — like an Obsidian vault. Pick a folder you control (e.g. a synced folder) so your notes travel with their files.'**
String get vaultSetupBody;
/// No description provided for @vaultChooseFolder.
///
/// In en, this message translates to:
/// **'Choose folder'**
String get vaultChooseFolder;
/// No description provided for @vaultMissingTitle.
///
/// In en, this message translates to:
/// **'Your vault folder is missing'**
String get vaultMissingTitle;
/// No description provided for @vaultMissingBody.
///
/// In en, this message translates to:
/// **'The folder you picked can\'t be found (it may have been moved, deleted, or on a drive that\'s unplugged). Relocate it or pick a new one.'**
String get vaultMissingBody;
/// No description provided for @vaultPickFailed.
///
/// In en, this message translates to:
/// **'Couldn\'t open the folder picker: {error}'**
String vaultPickFailed(String error);
/// No description provided for @vaultNotWritable.
///
/// In en, this message translates to:
/// **'That folder isn\'t writable. Please choose another.'**
String get vaultNotWritable;
/// No description provided for @vaultSection.
///
/// In en, this message translates to:
/// **'Vault'**
String get vaultSection;
/// No description provided for @vaultFolderLabel.
///
/// In en, this message translates to:
/// **'Vault folder'**
String get vaultFolderLabel;
/// No description provided for @vaultNoneSelected.
///
/// In en, this message translates to:
/// **'No folder selected'**
String get vaultNoneSelected;
/// No description provided for @vaultChangeFolder.
///
/// In en, this message translates to:
/// **'Change vault folder'**
String get vaultChangeFolder;
/// No description provided for @vaultUpdated.
///
/// In en, this message translates to:
/// **'Vault folder updated'**
String get vaultUpdated;
}
class _AppLocalizationsDelegate

View File

@@ -257,4 +257,48 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get close => 'Close';
@override
String get vaultSetupTitle => 'Choose your vault';
@override
String get vaultSetupHeadline => 'Pick a folder for your notebooks';
@override
String get vaultSetupBody =>
'BadNote stores your notebooks inside one folder you choose — like an Obsidian vault. Pick a folder you control (e.g. a synced folder) so your notes travel with their files.';
@override
String get vaultChooseFolder => 'Choose folder';
@override
String get vaultMissingTitle => 'Your vault folder is missing';
@override
String get vaultMissingBody =>
'The folder you picked can\'t be found (it may have been moved, deleted, or on a drive that\'s unplugged). Relocate it or pick a new one.';
@override
String vaultPickFailed(String error) {
return 'Couldn\'t open the folder picker: $error';
}
@override
String get vaultNotWritable =>
'That folder isn\'t writable. Please choose another.';
@override
String get vaultSection => 'Vault';
@override
String get vaultFolderLabel => 'Vault folder';
@override
String get vaultNoneSelected => 'No folder selected';
@override
String get vaultChangeFolder => 'Change vault folder';
@override
String get vaultUpdated => 'Vault folder updated';
}

View File

@@ -256,4 +256,46 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get close => '关闭';
@override
String get vaultSetupTitle => '选择笔记库';
@override
String get vaultSetupHeadline => '为你的笔记本选择一个文件夹';
@override
String get vaultSetupBody =>
'BadNote 会把你的笔记本都存放在你选择的一个文件夹里——就像 Obsidian 的库vault。请选择一个你能掌控的文件夹例如同步盘这样你的笔记会跟着文件一起走。';
@override
String get vaultChooseFolder => '选择文件夹';
@override
String get vaultMissingTitle => '笔记库文件夹不见了';
@override
String get vaultMissingBody => '找不到你选择的文件夹(可能被移动、删除,或所在磁盘已拔出)。请重新定位或另选一个。';
@override
String vaultPickFailed(String error) {
return '无法打开文件夹选择器:$error';
}
@override
String get vaultNotWritable => '该文件夹不可写,请另选一个。';
@override
String get vaultSection => '笔记库';
@override
String get vaultFolderLabel => '笔记库文件夹';
@override
String get vaultNoneSelected => '尚未选择文件夹';
@override
String get vaultChangeFolder => '更改笔记库文件夹';
@override
String get vaultUpdated => '笔记库文件夹已更新';
}

View File

@@ -78,5 +78,18 @@
"boardBacklinks": "哪些链接到这里",
"boardNoBacklinks": "暂无其他便利贴链接到这里",
"boardDanglingLink": "没有名为“{target}”的便利贴",
"close": "关闭"
"close": "关闭",
"vaultSetupTitle": "选择笔记库",
"vaultSetupHeadline": "为你的笔记本选择一个文件夹",
"vaultSetupBody": "BadNote 会把你的笔记本都存放在你选择的一个文件夹里——就像 Obsidian 的库vault。请选择一个你能掌控的文件夹例如同步盘这样你的笔记会跟着文件一起走。",
"vaultChooseFolder": "选择文件夹",
"vaultMissingTitle": "笔记库文件夹不见了",
"vaultMissingBody": "找不到你选择的文件夹(可能被移动、删除,或所在磁盘已拔出)。请重新定位或另选一个。",
"vaultPickFailed": "无法打开文件夹选择器:{error}",
"vaultNotWritable": "该文件夹不可写,请另选一个。",
"vaultSection": "笔记库",
"vaultFolderLabel": "笔记库文件夹",
"vaultNoneSelected": "尚未选择文件夹",
"vaultChangeFolder": "更改笔记库文件夹",
"vaultUpdated": "笔记库文件夹已更新"
}