Add functionality to identify and clean up problematic entries in notes
This commit is contained in:
		
							
								
								
									
										192
									
								
								lib/main.dart
									
									
									
									
									
								
							
							
						
						
									
										192
									
								
								lib/main.dart
									
									
									
									
									
								
							@@ -575,6 +575,41 @@ class MainPageState extends State<MainPage> with WindowListener {
 | 
			
		||||
    debugPrint("Volume saved: $_volume");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Check if content appears to be keyboard smashing or repeated characters
 | 
			
		||||
  bool _isLikelyKeyboardSmashing(String content) {
 | 
			
		||||
    // Skip empty content
 | 
			
		||||
    if (content.trim().isEmpty) return false;
 | 
			
		||||
 | 
			
		||||
    // Check for repeated characters (like "wwwwwwww")
 | 
			
		||||
    final repeatedCharPattern = RegExp(
 | 
			
		||||
      r'(.)\1{4,}',
 | 
			
		||||
    ); // 5 or more repeated chars
 | 
			
		||||
    if (repeatedCharPattern.hasMatch(content)) {
 | 
			
		||||
      // But allow if it's a legitimate pattern like base64
 | 
			
		||||
      if (RegExp(r'^[A-Za-z0-9+/=]+$').hasMatch(content)) return false;
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check for alternating characters (like "asdfasdf")
 | 
			
		||||
    final alternatingPattern = RegExp(r'(.)(.)\1\2{2,}');
 | 
			
		||||
    if (alternatingPattern.hasMatch(content)) return true;
 | 
			
		||||
 | 
			
		||||
    // Check for common keyboard smashing patterns
 | 
			
		||||
    final commonPatterns = [
 | 
			
		||||
      r'[qwerty]{5,}', // Common keyboard rows
 | 
			
		||||
      r'[asdf]{5,}',
 | 
			
		||||
      r'[zxcv]{5,}',
 | 
			
		||||
      r'[1234]{5,}',
 | 
			
		||||
      r'[wasd]{5,}', // Common gaming keys
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    for (final pattern in commonPatterns) {
 | 
			
		||||
      if (RegExp(pattern, caseSensitive: false).hasMatch(content)) return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> _saveData() async {
 | 
			
		||||
    String previousEntry = _previousEntryController.text;
 | 
			
		||||
    String currentEntry = _currentEntryController.text;
 | 
			
		||||
@@ -584,8 +619,14 @@ class MainPageState extends State<MainPage> with WindowListener {
 | 
			
		||||
 | 
			
		||||
    // Handle current entry
 | 
			
		||||
    if (currentEntry.isNotEmpty) {
 | 
			
		||||
      await createNote(currentEntry);
 | 
			
		||||
      _currentEntryController.clear(); // Clear the input field after saving
 | 
			
		||||
      // Skip saving if it looks like keyboard smashing
 | 
			
		||||
      if (!_isLikelyKeyboardSmashing(currentEntry)) {
 | 
			
		||||
        await createNote(currentEntry);
 | 
			
		||||
        _currentEntryController.clear(); // Clear the input field after saving
 | 
			
		||||
      } else {
 | 
			
		||||
        debugPrint("Skipping save of likely keyboard smashing: $currentEntry");
 | 
			
		||||
        _currentEntryController.clear(); // Still clear the input
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Handle scratch pad
 | 
			
		||||
@@ -756,7 +797,9 @@ class MainPageState extends State<MainPage> with WindowListener {
 | 
			
		||||
                                      .toList();
 | 
			
		||||
 | 
			
		||||
                              // Sort by date, newest first
 | 
			
		||||
                              filteredResults.sort((a, b) => b.date.compareTo(a.date));
 | 
			
		||||
                              filteredResults.sort(
 | 
			
		||||
                                (a, b) => b.date.compareTo(a.date),
 | 
			
		||||
                              );
 | 
			
		||||
 | 
			
		||||
                              // Important: update the dialog state after search completes
 | 
			
		||||
                              dialogSetState(() {
 | 
			
		||||
@@ -908,6 +951,143 @@ class MainPageState extends State<MainPage> with WindowListener {
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Show cleanup dialog
 | 
			
		||||
  void _showCleanupDialog() async {
 | 
			
		||||
    final problematicEntries = await findProblematicEntries();
 | 
			
		||||
    
 | 
			
		||||
    if (!mounted) return;
 | 
			
		||||
 | 
			
		||||
    showDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      builder: (BuildContext context) {
 | 
			
		||||
        return StatefulBuilder(
 | 
			
		||||
          builder: (context, dialogSetState) {
 | 
			
		||||
            return AlertDialog(
 | 
			
		||||
              title: const Text('Cleanup Problematic Entries'),
 | 
			
		||||
              content: SizedBox(
 | 
			
		||||
                width: MediaQuery.of(context).size.width * 0.7,
 | 
			
		||||
                height: MediaQuery.of(context).size.height * 0.7,
 | 
			
		||||
                child: Column(
 | 
			
		||||
                  children: [
 | 
			
		||||
                    Text(
 | 
			
		||||
                      'Found ${problematicEntries.length} potentially problematic entries',
 | 
			
		||||
                      style: const TextStyle(fontWeight: FontWeight.bold),
 | 
			
		||||
                    ),
 | 
			
		||||
                    const SizedBox(height: 16),
 | 
			
		||||
                    Expanded(
 | 
			
		||||
                      child: problematicEntries.isEmpty
 | 
			
		||||
                          ? const Center(
 | 
			
		||||
                              child: Text('No problematic entries found!'),
 | 
			
		||||
                            )
 | 
			
		||||
                          : ListView.builder(
 | 
			
		||||
                              itemCount: problematicEntries.length,
 | 
			
		||||
                              itemBuilder: (context, index) {
 | 
			
		||||
                                final note = problematicEntries[index];
 | 
			
		||||
                                return Card(
 | 
			
		||||
                                  margin: const EdgeInsets.only(bottom: 8),
 | 
			
		||||
                                  child: ListTile(
 | 
			
		||||
                                    title: Text(
 | 
			
		||||
                                      note.displayDate,
 | 
			
		||||
                                      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);
 | 
			
		||||
                                              });
 | 
			
		||||
                                            }
 | 
			
		||||
                                          },
 | 
			
		||||
                                        ),
 | 
			
		||||
                                      ],
 | 
			
		||||
                                    ),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                );
 | 
			
		||||
                              },
 | 
			
		||||
                            ),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
              actions: [
 | 
			
		||||
                TextButton(
 | 
			
		||||
                  onPressed: () {
 | 
			
		||||
                    Navigator.of(context).pop();
 | 
			
		||||
                  },
 | 
			
		||||
                  child: const Text('Close'),
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
            );
 | 
			
		||||
          },
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    // Wrap Scaffold with RawKeyboardListener as workaround for Escape key
 | 
			
		||||
@@ -939,6 +1119,12 @@ class MainPageState extends State<MainPage> with WindowListener {
 | 
			
		||||
        appBar: AppBar(
 | 
			
		||||
          title: const Text('Journaler'),
 | 
			
		||||
          actions: <Widget>[
 | 
			
		||||
            // Add cleanup button
 | 
			
		||||
            IconButton(
 | 
			
		||||
              icon: const Icon(Icons.cleaning_services),
 | 
			
		||||
              tooltip: 'Cleanup Problematic Entries',
 | 
			
		||||
              onPressed: _showCleanupDialog,
 | 
			
		||||
            ),
 | 
			
		||||
            // Add search button
 | 
			
		||||
            IconButton(
 | 
			
		||||
              icon: const Icon(Icons.search),
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user