diff --git a/lib/note_list.dart b/lib/note_list.dart index 5b15451..4880afa 100755 --- a/lib/note_list.dart +++ b/lib/note_list.dart @@ -48,6 +48,8 @@ class _NoteListScreenState extends State { bool _includeFileContent = false; bool _includeSubdirectories = true; Timer? _debounce; + Set _favorites = {}; + bool _showOnlyFavorites = false; @override void initState() { @@ -90,6 +92,23 @@ class _NoteListScreenState extends State { } + Future _saveFavorites() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setStringList('favorites', _favorites.toList()); + } + + void _toggleFavorite(File note) { + setState(() { + if (_favorites.contains(note.path)) { + _favorites.remove(note.path); + } else { + _favorites.add(note.path); + } + }); + _saveFavorites(); + _filterNotes(); + } + Future _toggleExternalEditor(bool value) async { setState(() { _useExternalEditor = value; @@ -124,6 +143,7 @@ class _NoteListScreenState extends State { defaultDir = dir.path; _useExternalEditor = prefs.getBool('use_external_editor') ?? false; _includeSubdirectories = prefs.getBool('include_subdirectories') ?? true; + _favorites = (prefs.getStringList('favorites') ?? []).toSet(); }); notesDirectoryPath = prefs.getString('notes_directory') ?? defaultDir; @@ -149,11 +169,13 @@ class _NoteListScreenState extends State { Welcome to *Muzzle Velocity*. This is your fast and minimal note-taking app. -1. ✍️ Create Notes quickly: just type a title in the search bar and press enter. -2. πŸ” Type keywords to filter notes instantly. -3. πŸ’» Type commands like :help to get more information. -4. 🏷️ Use hashtags like #quickstart, context tags like @help, or project tags like +muzzle in your notes for categorization. -5. πŸ”« Tap the blaster at the top right for more settings or to clear the search field. +1. ✍️ **Create** a note: type a title in the search bar and press Enter. +2. πŸ” **Search** by title, #tag, +project, @context, or /subfolder. +3. ⭐ **Favorite** a note: swipe it to the right. +4. πŸ—‘οΈ **Delete** a note: swipe it to the left. +5. πŸ“– **View / ✏️ Edit / πŸ‘οΈ Preview**: switch modes from the editor menu. +6. πŸ’» **Commands**: type :help in the search bar for more information. +7. πŸ”« Tap the blaster at the top right for settings. *Muzzle Velocity* is built for speed and simplicity! πŸš€ @@ -326,30 +348,35 @@ Happy note taking! ✨ setState(() { if (query.isEmpty) { filteredNotes = List.from(notes); - return; + } else { + final terms = query.split(' '); // Split search into words + final searchTags = terms.where((t) => t.startsWith(RegExp(r'[#@+]'))).toSet(); + final searchText = terms.where((t) => !t.startsWith(RegExp(r'[#@+]'))).join(' '); + + // Strip leading '/' so "/subdir" and "subdir" match the same notes + final normalizedSearch = searchText.replaceFirst(RegExp(r'^/'), ''); + + filteredNotes = notes.where((note) { + final relativePath = _noteRelativePaths[note] ?? ''; + final tags = noteTags[note] ?? {}; + final content = _includeFileContent ? (_noteContentLower[note] ?? '') : ''; + + final matchesTags = searchTags.every((tag) => + tags.any((noteTag) => noteTag.toLowerCase().startsWith(tag))); + + if (!_includeFileContent) { + return relativePath.contains(normalizedSearch) && matchesTags; + } else { + return (relativePath.contains(normalizedSearch) || content.contains(normalizedSearch)) && matchesTags; + } + }).toList(); } - final terms = query.split(' '); // Split search into words - final searchTags = terms.where((t) => t.startsWith(RegExp(r'[#@+]'))).toSet(); - final searchText = terms.where((t) => !t.startsWith(RegExp(r'[#@+]'))).join(' '); - - // Strip leading '/' so "/subdir" and "subdir" match the same notes - final normalizedSearch = searchText.replaceFirst(RegExp(r'^/'), ''); - - filteredNotes = notes.where((note) { - final relativePath = _noteRelativePaths[note] ?? ''; - final tags = noteTags[note] ?? {}; - final content = _includeFileContent ? (_noteContentLower[note] ?? '') : ''; - - final matchesTags = searchTags.every((tag) => - tags.any((noteTag) => noteTag.toLowerCase().startsWith(tag))); - - if (!_includeFileContent) { - return relativePath.contains(normalizedSearch) && matchesTags; - } else { - return (relativePath.contains(normalizedSearch) || content.contains(normalizedSearch)) && matchesTags; - } - }).toList(); + if (_showOnlyFavorites) { + filteredNotes = filteredNotes + .where((note) => _favorites.contains(note.path)) + .toList(); + } }); } @@ -717,6 +744,10 @@ Happy note taking! ✨ ), onSelected: (String value) { switch (value) { + case 'Toggle Favorites': + setState(() { _showOnlyFavorites = !_showOnlyFavorites; }); + _filterNotes(); + break; case 'Toggle Tags': widget.toggleTags(); break; @@ -744,6 +775,17 @@ Happy note taking! ✨ } }, itemBuilder: (BuildContext context) => [ + PopupMenuItem( + value: 'Toggle Favorites', + child: ListTile( + leading: Icon( + _showOnlyFavorites ? Icons.star : Icons.star_outline, + color: _showOnlyFavorites ? Colors.amber : null, + ), + title: Text(_showOnlyFavorites ? 'Show All Notes' : 'Show Favorites'), + ), + ), + PopupMenuDivider(), PopupMenuItem( value: 'Toggle Tags', child: ListTile( @@ -869,16 +911,35 @@ Happy note taking! ✨ final noteFile = filteredNotes[index]; return Dismissible( key: Key(noteFile.path), - direction: DismissDirection.endToStart, + direction: DismissDirection.horizontal, background: Container( + color: Colors.amber, + alignment: Alignment.centerLeft, + padding: EdgeInsets.symmetric(horizontal: 20), + child: Icon( + _favorites.contains(noteFile.path) ? Icons.star_outline : Icons.star, + color: Colors.white, + ), + ), + secondaryBackground: Container( color: Colors.redAccent, alignment: Alignment.centerRight, padding: EdgeInsets.symmetric(horizontal: 20), child: Icon(Icons.delete, color: Colors.white), ), + confirmDismiss: (direction) async { + if (direction == DismissDirection.startToEnd) { + _toggleFavorite(noteFile); + return false; + } + return true; + }, onDismissed: (direction) => _deleteNote(noteFile), child: ListTile( title: Text(noteFile.uri.pathSegments.last.replaceAll('.md', '')), + trailing: _favorites.contains(noteFile.path) + ? Icon(Icons.star, color: Colors.amber, size: 18) + : null, subtitle: LayoutBuilder( builder: (context, constraints) { final bool hasEnoughSpace = constraints.maxWidth > 200; // Adjust threshold diff --git a/lib/texts.dart b/lib/texts.dart old mode 100644 new mode 100755 index 15b488d..e66c8f5 --- a/lib/texts.dart +++ b/lib/texts.dart @@ -3,63 +3,82 @@ String helpContent = """ Type to filter notes by title. Use #tags, +projects, or @contexts to find notes with specific tags. -Start with a / to filter notes within /subdirectories. -Mix text, tags, and folders (/work project #important). +Start with a / to filter notes within /folders and /sub/folders. +Mix text, tags, and folders freely (/work project #important). # Create Notes Type the title of a new note in the search bar and hit Enter. -Use /folder/note title to create a note inside a subdirectory. -Subfolders will be automatically created if they don’t exist. +Use /folder/note to create a note inside a subdirectory. +Subfolders are automatically created if they don't exist. -# Delete Notes +# Open Notes -Swipe a note in the list to the left to move it to the trash. -The trash is a directory in the root folder. -Use :emptytrash or the context menu button to permanently clear deleted notes. +**Tap** a note to open it in View mode (syntax-highlighted, read-only). +**Long-press** a note to open it scrolled to the end of the file. +Switch between View, Edit, and Preview modes from the editor menu. # Edit Notes -Enable/disable autosave in the menu. When disabled you can use the Save button in the menu. -If there are unsaved changes when you are leaving the editor, you will be prompted to save. +Switch to Edit mode from the editor menu. The cursor is placed at the end. +Enable/disable Autosave in the editor menu. +When Autosave is off, use the Save button β€” or you will be prompted on exit. +Adjust the font size with the slider in the editor menu. +Share a note as plain text from the editor menu. + +# Favorites + +Swipe a note to the **right** to mark it as a favorite (gold star). +Swipe right again to remove it from favorites. +Use *Show Favorites* in the menu to filter the list to starred notes only. + +# Delete Notes + +Swipe a note to the **left** to move it to the trash. +The trash is a directory in the root notes folder. +Use :emptytrash or the context menu to permanently clear deleted notes. # Tag Notes -Use tags anywhere in the notes and they will be detected. +Use tags anywhere in note content β€” they are detected automatically. + #tags – General categorization. +projects – Project-based grouping. @contexts – Context-specific notes. +Tags must start with a letter and be preceded by a space or line break. + # Context Menu -Tap the orange blaster at the top right to open the Preferences. +Tap the orange blaster at the top right to open the preferences menu. -Here you find toggles to show/hide the tags below the note titles and switch between light and dark themes. - -You can also exclude subdirectories from your search. - -By default the search bar filters by note titles, folders, and tags, but you can also toggle including the file contents. Please be aware that this might slow down the app if you have many files. - -If you prefer to use a different Markdown editor for your files you can toggle that and when you tap a file in the list you can choose what to open the file with. - -By default the notes are saved in the App's own directory which is inaccessible to other apps. If you want to choose your own custom folder for this, you can set it. You will be asked for special permissions to do so. This will enable you to save the notes wherever you want, for example to sync them with a different app. You can also choose a folder that already has notes in it. +- *Show Favorites / Show All Notes* – Filter the list to starred notes. +- *Hide/Show Tags* – Show tag chips below note titles in the list. +- *Switch to Light/Dark Mode* – Toggle themes. +- *Exclude/Search Subdirectories* – Control the depth of your search. +- *Search/Exclude File Content* – Also search inside note content (may slow down the app with many files). +- *Use External/Internal Editor* – Open notes in your preferred editor app. +- *Set Notes Directory* – Choose a folder anywhere on the device. +- *Empty Trash* – Permanently delete all trashed notes. +- *Help* – Show this guide. +- *About* – App credits and version. # File Organization Root notes are listed above subfolder notes. Subfolders are ordered by the latest modified file inside them. -Empty folders auto-delete after the last file is removed. +Empty folders are automatically removed after the last file is deleted. # Commands -Use commands by starting with a colon : +Type a colon command in the search bar and press Enter: :help – Open this guide. :tags – Toggle tag visibility. -:day - Switch to light theme. -:night - Switch to dark theme. -:extern – Toggle external editor usage. -:intern – Toggle internal editor usage. +:day – Switch to light theme. +:night – Switch to dark theme. +:extern – Toggle external editor. +:intern – Toggle internal editor. :emptytrash – Permanently delete trashed notes. :about – Open app information. @@ -67,12 +86,11 @@ Use commands by starting with a colon : """; String aboutContent = """ -**Muzzle Velocity** is a fast and minimalist note-taking app inspired by *Notational Velocity* and *Terminal Velocity*. +v.1.1.0 + +**Muzzle Velocity** is a fast and minimalist but opinionated note-taking app inspired by the legendary *Notational Velocity* on MacOS and *Terminal Velocity* for command line terminals. It is built for speed and efficiency, eliminating unnecessary UI clutter. Every action should be as fast and frictionless as possible. Made with 🧑 by randogoth - -with the invaluable help of -OpenAI's πŸ€– ChatGPT -"""; \ No newline at end of file +"""; diff --git a/pubspec.yaml b/pubspec.yaml index 6847ac9..ad9ed14 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: muzzle_velocity description: "A new Flutter project." publish_to: 'none' # Remove this line if you wish to publish to pub.dev -version: 1.0.0+1 +version: 1.1.0+2 environment: sdk: ^3.6.0