Enhance cleanup dialog with sensitivity slider for problematic entries

This commit is contained in:
2025-05-20 12:44:53 +02:00
parent 621e85c747
commit 4339763261
2 changed files with 178 additions and 108 deletions

View File

@@ -953,8 +953,11 @@ class MainPageState extends State<MainPage> with WindowListener {
// Show cleanup dialog // Show cleanup dialog
void _showCleanupDialog() async { void _showCleanupDialog() async {
final problematicEntries = await findProblematicEntries(); double sensitivity = 0.7; // Default 70%
final problematicEntries = await findProblematicEntries(
maxCharPercentage: sensitivity,
);
if (!mounted) return; if (!mounted) return;
showDialog( showDialog(
@@ -969,106 +972,167 @@ class MainPageState extends State<MainPage> with WindowListener {
height: MediaQuery.of(context).size.height * 0.7, height: MediaQuery.of(context).size.height * 0.7,
child: Column( child: Column(
children: [ children: [
Row(
children: [
const Text('Sensitivity: '),
Expanded(
child: Slider(
value: sensitivity,
min: 0.3,
max: 0.9,
divisions: 12,
label: '${(sensitivity * 100).toInt()}%',
onChanged: (value) async {
dialogSetState(() {
sensitivity =
(value * 100).round() /
100; // Round to 2 decimal places
});
// Refresh results with new sensitivity
final newResults = await findProblematicEntries(
maxCharPercentage: sensitivity,
);
dialogSetState(() {
problematicEntries.clear();
problematicEntries.addAll(newResults);
});
},
),
),
Text('${(sensitivity * 100).toInt()}%'),
],
),
Text( Text(
'Found ${problematicEntries.length} potentially problematic entries', 'Found ${problematicEntries.length} potentially problematic entries',
style: const TextStyle(fontWeight: FontWeight.bold), style: const TextStyle(fontWeight: FontWeight.bold),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Expanded( Expanded(
child: problematicEntries.isEmpty child:
? const Center( problematicEntries.isEmpty
child: Text('No problematic entries found!'), ? const Center(
) child: Text('No problematic entries found!'),
: ListView.builder( )
itemCount: problematicEntries.length, : ListView.builder(
itemBuilder: (context, index) { itemCount: problematicEntries.length,
final note = problematicEntries[index]; itemBuilder: (context, index) {
return Card( final note = problematicEntries[index];
margin: const EdgeInsets.only(bottom: 8), return Card(
child: ListTile( margin: const EdgeInsets.only(bottom: 8),
title: Text( child: ListTile(
note.displayDate, title: Text(
style: const TextStyle( note.displayDate,
fontWeight: FontWeight.bold, style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
subtitle: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
'Reason: ${note.problemReason}',
style: TextStyle(
color: Colors.red[700],
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(note.content),
],
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.visibility),
tooltip: 'View in main window',
onPressed: () {
// Close the dialog
Navigator.of(context).pop();
// Navigate to the note in main window
setState(() {
_currentlyDisplayedNote = note;
_previousEntryController.text =
note.content;
});
_checkNavigation();
},
),
IconButton(
icon: const Icon(Icons.delete),
color: Colors.red,
tooltip:
'Delete (hold Shift to skip confirmation)',
onPressed: () async {
// Check if shift is pressed
final isShiftPressed =
HardwareKeyboard
.instance
.isShiftPressed;
bool shouldDelete =
isShiftPressed;
if (!isShiftPressed) {
// Show confirmation dialog
shouldDelete =
await showDialog<bool>(
context: context,
builder:
(
context,
) => AlertDialog(
title: const Text(
'Delete Entry?',
),
content: const Text(
'Are you sure you want to delete this entry? This action cannot be undone.',
),
actions: [
TextButton(
onPressed:
() => Navigator.of(
context,
).pop(
false,
),
child:
const Text(
'Cancel',
),
),
TextButton(
onPressed:
() => Navigator.of(
context,
).pop(true),
child:
const Text(
'Delete',
),
),
],
),
) ??
false;
}
if (shouldDelete) {
await deleteNote(note.date);
dialogSetState(() {
problematicEntries.removeAt(
index,
);
});
}
},
),
],
), ),
), ),
subtitle: Column( );
crossAxisAlignment: CrossAxisAlignment.start, },
children: [ ),
Text(
'Reason: ${note.problemReason}',
style: TextStyle(
color: Colors.red[700],
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(note.content),
],
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.visibility),
tooltip: 'View in main window',
onPressed: () {
// Close the dialog
Navigator.of(context).pop();
// Navigate to the note in main window
setState(() {
_currentlyDisplayedNote = note;
_previousEntryController.text = note.content;
});
_checkNavigation();
},
),
IconButton(
icon: const Icon(Icons.delete),
color: Colors.red,
tooltip: 'Delete (hold Shift to skip confirmation)',
onPressed: () async {
// Check if shift is pressed
final isShiftPressed = HardwareKeyboard.instance.isShiftPressed;
bool shouldDelete = isShiftPressed;
if (!isShiftPressed) {
// Show confirmation dialog
shouldDelete = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Delete Entry?'),
content: const Text(
'Are you sure you want to delete this entry? This action cannot be undone.',
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('Delete'),
),
],
),
) ?? false;
}
if (shouldDelete) {
await deleteNote(note.date);
dialogSetState(() {
problematicEntries.removeAt(index);
});
}
},
),
],
),
),
);
},
),
), ),
], ],
), ),

View File

@@ -162,14 +162,14 @@ Future<List<Note>> searchNotes(String query) async {
} }
// Find potentially problematic entries based on character distribution // Find potentially problematic entries based on character distribution
Future<List<Note>> findProblematicEntries() async { Future<List<Note>> findProblematicEntries({
const double maxCharPercentage = 0.7; // If a single char makes up more than 70%, it's suspicious double maxCharPercentage = 0.7,
const int minLength = 10; // Only check notes longer than this }) async {
// Simple SQLite query that counts character occurrences using replace // Simple SQLite query that counts character occurrences using replace
final List<Map<String, dynamic>> results = await DB.db.rawQuery(''' final List<Map<String, dynamic>> results = await DB.db.rawQuery(
'''
WITH char_counts AS ( WITH char_counts AS (
SELECT SELECT
id, id,
date, date,
content, content,
@@ -178,18 +178,24 @@ Future<List<Note>> findProblematicEntries() async {
length(content) as total_length, length(content) as total_length,
cast(length(content) - length(replace(content, substr(content, 1, 1), '')) as float) / length(content) as percentage cast(length(content) - length(replace(content, substr(content, 1, 1), '')) as float) / length(content) as percentage
FROM notes FROM notes
WHERE length(content) >= ?
) )
SELECT * SELECT *
FROM char_counts FROM char_counts
WHERE percentage > ? WHERE percentage > ?
ORDER BY date DESC ORDER BY date DESC
''', [minLength, maxCharPercentage]); ''',
[maxCharPercentage],
);
return results.map((row) => Note( return results
date: row['date'] as String, .map(
content: row['content'] as String, (row) => Note(
isProblematic: true, date: row['date'] as String,
problemReason: 'Character "${row['char']}" makes up ${(row['percentage'] * 100).toStringAsFixed(1)}% of the content', content: row['content'] as String,
)).toList(); isProblematic: true,
problemReason:
'Character "${row['char']}" makes up ${(row['percentage'] * 100).toStringAsFixed(1)}% of the content',
),
)
.toList();
} }