feat(notes): Improve note editor change detection and UI refinements

This commit is contained in:
cogwheel0
2025-12-06 19:48:33 +05:30
parent df2a921ffd
commit a13a2de7d6

View File

@@ -117,10 +117,21 @@ class _NoteEditorPageState extends ConsumerState<NoteEditorPage> {
} }
void _onContentChanged() { void _onContentChanged() {
if (!_hasChanges && mounted) { if (!mounted || _isLoading) return;
setState(() => _hasChanges = true);
// Check if content actually changed from the saved note
final titleChanged = _note != null && _titleController.text != _note!.title;
final contentChanged =
_note != null && _contentController.text != _note!.markdownContent;
final hasRealChanges = titleChanged || contentChanged;
if (hasRealChanges != _hasChanges) {
setState(() => _hasChanges = hasRealChanges);
}
if (hasRealChanges) {
_debounceSave();
} }
_debounceSave();
} }
void _debounceSave() { void _debounceSave() {
@@ -378,9 +389,7 @@ class _NoteEditorPageState extends ConsumerState<NoteEditorPage> {
} }
Future<void> _startDictation() async { Future<void> _startDictation() async {
_voiceService ??= VoiceInputService( _voiceService ??= VoiceInputService(api: ref.read(apiServiceProvider));
api: ref.read(apiServiceProvider),
);
try { try {
final ok = await _voiceService!.initialize(); final ok = await _voiceService!.initialize();
@@ -474,10 +483,12 @@ class _NoteEditorPageState extends ConsumerState<NoteEditorPage> {
canPop: !_hasChanges, canPop: !_hasChanges,
onPopInvokedWithResult: (didPop, result) async { onPopInvokedWithResult: (didPop, result) async {
if (didPop) return; // Already popped, nothing to do if (didPop) return; // Already popped, nothing to do
// Capture navigator before async gap
final navigator = Navigator.of(context);
// Save changes before allowing pop // Save changes before allowing pop
await _saveNote(showFeedback: false); await _saveNote(showFeedback: false);
if (!mounted) return; if (!mounted) return;
Navigator.of(context).pop(); navigator.pop();
}, },
child: ErrorBoundary( child: ErrorBoundary(
child: Scaffold( child: Scaffold(
@@ -572,7 +583,9 @@ class _NoteEditorPageState extends ConsumerState<NoteEditorPage> {
// Generate title button - aligned with other header icons // Generate title button - aligned with other header icons
AnimatedOpacity( AnimatedOpacity(
opacity: _titleFocusNode.hasFocus && !_isGeneratingTitle ? 1.0 : 0.0, opacity: _titleFocusNode.hasFocus && !_isGeneratingTitle
? 1.0
: 0.0,
duration: const Duration(milliseconds: 150), duration: const Duration(milliseconds: 150),
child: IgnorePointer( child: IgnorePointer(
ignoring: !_titleFocusNode.hasFocus || _isGeneratingTitle, ignoring: !_titleFocusNode.hasFocus || _isGeneratingTitle,
@@ -846,11 +859,11 @@ class _NoteEditorPageState extends ConsumerState<NoteEditorPage> {
context, context,
icon: _isRecording icon: _isRecording
? (Platform.isIOS ? (Platform.isIOS
? CupertinoIcons.stop_fill ? CupertinoIcons.stop_fill
: Icons.stop_rounded) : Icons.stop_rounded)
: (Platform.isIOS : (Platform.isIOS
? CupertinoIcons.mic_fill ? CupertinoIcons.mic_fill
: Icons.mic_rounded), : Icons.mic_rounded),
color: _isRecording ? theme.error : null, color: _isRecording ? theme.error : null,
isLoading: false, isLoading: false,
tooltip: _isRecording ? l10n.stopRecording : l10n.startDictation, tooltip: _isRecording ? l10n.stopRecording : l10n.startDictation,
@@ -909,11 +922,7 @@ class _NoteEditorPageState extends ConsumerState<NoteEditorPage> {
), ),
), ),
) )
: Icon( : Icon(icon, color: color ?? theme.iconPrimary, size: IconSize.lg),
icon,
color: color ?? theme.iconPrimary,
size: IconSize.lg,
),
); );
if (showMenu) { if (showMenu) {
@@ -932,22 +941,22 @@ class _NoteEditorPageState extends ConsumerState<NoteEditorPage> {
} }
}, },
itemBuilder: (context) => [ itemBuilder: (context) => [
PopupMenuItem( PopupMenuItem(
value: 'enhance', value: 'enhance',
child: Row( child: Row(
children: [ children: [
Icon( Icon(
Platform.isIOS Platform.isIOS
? CupertinoIcons.sparkles ? CupertinoIcons.sparkles
: Icons.auto_fix_high_rounded, : Icons.auto_fix_high_rounded,
color: theme.buttonPrimary, color: theme.buttonPrimary,
size: IconSize.md, size: IconSize.md,
),
const SizedBox(width: Spacing.sm),
Text(l10n.enhanceNote),
],
), ),
), const SizedBox(width: Spacing.sm),
Text(l10n.enhanceNote),
],
),
),
PopupMenuItem( PopupMenuItem(
value: 'title', value: 'title',
child: Row( child: Row(