v1.1.0: favorites, note modes, long-press, search improvements
Bump version to 1.1.0+2. New features since 1.0.x: - Three editor modes: View (read-only, syntax-highlighted), Edit, Preview - Favorites: swipe right to star a note; Show Favorites filter in menu - Long-press a note to open scrolled to end; Edit mode always places cursor at end of file - Subdirectory note creation (subdir/notename) and search (/subdir) - Tag search (#tag @context +project) with case-insensitive matching - Tag highlighting no longer falsely matches phone numbers / emails - Font size slider and Share button in editor menu - Search cache: note metadata indexed at load time, no per-keystroke file I/O - App ID changed to com.randogoth.muzzlevelocity Update Help and About texts to document all current features. Update QuickStart note for new installs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
14d6b3c44d
commit
8645c656f2
3 changed files with 140 additions and 61 deletions
|
|
@ -48,6 +48,8 @@ class _NoteListScreenState extends State<NoteListScreen> {
|
|||
bool _includeFileContent = false;
|
||||
bool _includeSubdirectories = true;
|
||||
Timer? _debounce;
|
||||
Set<String> _favorites = {};
|
||||
bool _showOnlyFavorites = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -90,6 +92,23 @@ class _NoteListScreenState extends State<NoteListScreen> {
|
|||
}
|
||||
|
||||
|
||||
Future<void> _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<void> _toggleExternalEditor(bool value) async {
|
||||
setState(() {
|
||||
_useExternalEditor = value;
|
||||
|
|
@ -124,6 +143,7 @@ class _NoteListScreenState extends State<NoteListScreen> {
|
|||
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<NoteListScreen> {
|
|||
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<String>(
|
||||
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<String>(
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue