feat(notes): Improve note editor change detection and UI refinements
This commit is contained in:
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user