feat: OneNote-style notebooks, text fonts, and page navigation
All checks were successful
CI / Windows build (push) Successful in 8m42s
All checks were successful
CI / Windows build (push) Successful in 8m42s
Add notebook.json containers with multi-member pages, fix PDF text editing (size/bold/drag/double-tap), index SidecarText in search, and share keyboard page shortcuts plus a PDF scrubber. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import '../models/document.dart';
|
||||
import '../models/note.dart';
|
||||
import '../providers/document_provider.dart';
|
||||
import '../providers/note_provider.dart';
|
||||
import '../providers/notebook_container_provider.dart';
|
||||
import '../providers/ocr_provider.dart';
|
||||
import '../providers/search_provider.dart';
|
||||
import '../editor/canvas/pen_editor_screen.dart';
|
||||
@@ -17,6 +18,7 @@ import '../services/pptx_service.dart';
|
||||
import '../services/vault_service.dart';
|
||||
import '../editor/canvas/pen_note_screen.dart';
|
||||
import '../editor/canvas/pen_slide_screen.dart';
|
||||
import 'notebook_screen.dart';
|
||||
import 'search_screen.dart';
|
||||
import 'settings_screen.dart';
|
||||
|
||||
@@ -88,8 +90,10 @@ class HomeScreen extends ConsumerWidget {
|
||||
data: (notes) {
|
||||
final documentsAsync = ref.watch(documentListProvider);
|
||||
final documents = documentsAsync.valueOrNull ?? [];
|
||||
final containersAsync = ref.watch(notebookContainerListProvider);
|
||||
final containers = containersAsync.valueOrNull ?? [];
|
||||
|
||||
if (notes.isEmpty && documents.isEmpty) {
|
||||
if (notes.isEmpty && documents.isEmpty && containers.isEmpty) {
|
||||
return _buildEmptyState(context, ref);
|
||||
}
|
||||
return RefreshIndicator(
|
||||
@@ -97,10 +101,32 @@ class HomeScreen extends ConsumerWidget {
|
||||
await Future.wait([
|
||||
ref.read(noteListProvider.notifier).loadNotes(),
|
||||
ref.read(documentListProvider.notifier).loadDocuments(),
|
||||
ref
|
||||
.read(notebookContainerListProvider.notifier)
|
||||
.loadContainers(),
|
||||
]);
|
||||
},
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
if (containers.isNotEmpty) ...[
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
|
||||
child: Text(
|
||||
l.notebooksSection,
|
||||
style: Theme.of(context).textTheme.titleMedium
|
||||
?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) =>
|
||||
_ContainerTile(container: containers[index]),
|
||||
childCount: containers.length,
|
||||
),
|
||||
),
|
||||
],
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
|
||||
@@ -189,9 +215,10 @@ class HomeScreen extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
/// "Create notebook": prompt a title (defaulting to Untitled), create the
|
||||
/// standalone notebook FOLDER + `notebook.badnote.json` via
|
||||
/// `VaultService.createEmptyNotebook`, then open the editor on the new note.
|
||||
/// "Create notebook" (OneNote style): prompt a title (defaulting to
|
||||
/// Untitled), create the notebook container FOLDER + `notebook.json` (with
|
||||
/// one blank ink page) via `VaultService.createNotebookContainer`, then open
|
||||
/// [NotebookScreen] on it.
|
||||
Future<void> _createAndOpenNote(BuildContext context, WidgetRef ref) async {
|
||||
final title = await _promptNotebookTitle(context);
|
||||
if (title == null) return; // cancelled
|
||||
@@ -199,12 +226,18 @@ class HomeScreen extends ConsumerWidget {
|
||||
final resolved = title.trim().isEmpty
|
||||
? (l?.untitledNote ?? 'Untitled')
|
||||
: title.trim();
|
||||
final note =
|
||||
await ref.read(noteListProvider.notifier).createNote(title: resolved);
|
||||
final container = await ref
|
||||
.read(notebookContainerListProvider.notifier)
|
||||
.createContainer(resolved);
|
||||
if (context.mounted) {
|
||||
Navigator.of(
|
||||
context,
|
||||
).push(MaterialPageRoute(builder: (_) => PenNoteScreen(note: note)));
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => NotebookScreen(
|
||||
folderPath: container.folderPath,
|
||||
title: container.title,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,6 +430,45 @@ class HomeScreen extends ConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple tile for a OneNote-style notebook container on the home screen.
|
||||
/// Tapping opens [NotebookScreen] on the container's folder.
|
||||
class _ContainerTile extends StatelessWidget {
|
||||
const _ContainerTile({required this.container});
|
||||
|
||||
final VaultContainer container;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l = AppLocalizations.of(context);
|
||||
final dateStr = _formatDate(context, container.modified);
|
||||
return ListTile(
|
||||
leading: Icon(
|
||||
Icons.menu_book,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
title: Text(
|
||||
container.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
subtitle: Text(
|
||||
'${l.memberCount(container.memberCount)} · $dateStr',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => NotebookScreen(
|
||||
folderPath: container.folderPath,
|
||||
title: container.title,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NoteTile extends ConsumerStatefulWidget {
|
||||
final Note note;
|
||||
const _NoteTile({required this.note});
|
||||
|
||||
Reference in New Issue
Block a user