Skip to content

Commit

Permalink
[68] Add support for labels (#245)
Browse files Browse the repository at this point in the history
* [68] feat: add support for labels

* [68] fix: initialize notes service

* [68] fix: disable labels list onTap in the bin

* [68] fix: sort labels

* [68] feat: allow to disable labels

* [68] feat: allow to hide labels on note tiles and in the editor

* [68] fix: notes and bin providers update

* [68] feat: add l10n

* [68] doc: add dart doc

* [68] feat: hide hidden labels on tile and editor labels lists

* [68] fix: rebase

* [68] fix: wrong current note in the editor
  • Loading branch information
maelchiotti authored Nov 17, 2024
1 parent 675cdb2 commit 0248401
Show file tree
Hide file tree
Showing 84 changed files with 2,857 additions and 672 deletions.
6 changes: 6 additions & 0 deletions lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import 'package:localmaterialnotes/common/constants/constants.dart';
import 'package:localmaterialnotes/common/extensions/locale_extension.dart';
import 'package:localmaterialnotes/common/widgets/placeholders/error_placeholder.dart';
import 'package:localmaterialnotes/l10n/app_localizations/app_localizations.g.dart';
import 'package:localmaterialnotes/providers/labels/labels_list/labels_list_provider.dart';
import 'package:localmaterialnotes/providers/labels/labels_navigation/labels_navigation_provider.dart';
import 'package:localmaterialnotes/providers/notifiers.dart';
import 'package:localmaterialnotes/routing/router.dart';
import 'package:localmaterialnotes/utils/locale_utils.dart';
Expand Down Expand Up @@ -35,6 +37,10 @@ class _AppState extends ConsumerState<App> with AfterLayoutMixin<App> {
// Read the potential data shared from other applications
readSharedData(ref);
_stream = listenSharedData(ref);

// Eagerly get the labels for the full list and the navigation
ref.read(labelsListProvider.notifier).get();
ref.read(labelsNavigationProvider.notifier).get();
}

@override
Expand Down
22 changes: 22 additions & 0 deletions lib/common/actions/labels/add.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:localmaterialnotes/common/constants/constants.dart';
import 'package:localmaterialnotes/models/label/label.dart';
import 'package:localmaterialnotes/pages/labels/dialogs/label_dialog.dart';
import 'package:localmaterialnotes/providers/labels/labels/labels_provider.dart';

/// Adds a label.
Future<void> addLabel(BuildContext context, WidgetRef ref) async {
final label = await showAdaptiveDialog<Label>(
context: context,
builder: (context) {
return LabelDialog(title: l.dialog_label_add);
},
);

if (label == null) {
return;
}

ref.read(labelsProvider.notifier).edit(label);
}
64 changes: 64 additions & 0 deletions lib/common/actions/labels/delete.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// ignore_for_file: unused_import

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:localmaterialnotes/common/actions/labels/select.dart';
import 'package:localmaterialnotes/common/actions/notes/select.dart';
import 'package:localmaterialnotes/common/constants/constants.dart';
import 'package:localmaterialnotes/common/dialogs/confirmation_dialog.dart';
import 'package:localmaterialnotes/common/extensions/build_context_extension.dart';
import 'package:localmaterialnotes/models/label/label.dart';
import 'package:localmaterialnotes/models/note/note.dart';
import 'package:localmaterialnotes/providers/bin/bin_provider.dart';
import 'package:localmaterialnotes/providers/labels/labels/labels_provider.dart';
import 'package:localmaterialnotes/providers/notes/notes/notes_provider.dart';
import 'package:localmaterialnotes/providers/notifiers.dart';
import 'package:localmaterialnotes/routing/routes/notes/notes_editor_route.dart';
import 'package:localmaterialnotes/routing/routes/shell/shell_route.dart';

/// Deletes the [label].
///
/// Returns `true` if the [label] was deleted, `false` otherwise.
///
/// First, asks for a confirmation if needed.
Future<bool> deleteLabel(BuildContext context, WidgetRef ref, Label? label) async {
if (label == null) {
return false;
}

if (!await askForConfirmation(
context,
l.dialog_delete,
l.dialog_delete_body(1),
l.dialog_delete,
)) {
return false;
}

return await ref.read(labelsProvider.notifier).delete(label);
}

/// Deletes the [labels].
///
/// Returns `true` if the [labels] were deleted, `false` otherwise.
///
/// First, asks for a confirmation if needed.
Future<bool> deleteLabels(BuildContext context, WidgetRef ref, List<Label> labels) async {
if (!await askForConfirmation(
context,
l.dialog_delete,
l.dialog_delete_body(labels.length),
l.dialog_delete,
)) {
return false;
}

final succeeded = await ref.read(labelsProvider.notifier).deleteAll(labels);

if (context.mounted) {
exitLabelsSelectionMode(ref);
}

return succeeded;
}
25 changes: 25 additions & 0 deletions lib/common/actions/labels/pin.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:localmaterialnotes/common/actions/labels/select.dart';
import 'package:localmaterialnotes/models/label/label.dart';
import 'package:localmaterialnotes/providers/labels/labels/labels_provider.dart';

/// Toggles the pined status of the [label].
///
/// Returns `true` if the pined status of the [label] was toggled, `false` otherwise.
Future<bool> togglePinLabel(BuildContext context, WidgetRef ref, Label? label) async {
if (label == null) {
return false;
}

await ref.read(labelsProvider.notifier).togglePin(label);

return false;
}

/// Toggles the pined status of the [labels].
Future<void> togglePinLabels(WidgetRef ref, List<Label> labels) async {
await ref.read(labelsProvider.notifier).togglePinAll(labels);

exitLabelsSelectionMode(ref);
}
28 changes: 28 additions & 0 deletions lib/common/actions/labels/select.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:localmaterialnotes/models/label/label.dart';
import 'package:localmaterialnotes/providers/labels/labels/labels_provider.dart';
import 'package:localmaterialnotes/providers/notifiers.dart';

/// Toggles the select status of the [label].
void toggleSelectLabel(WidgetRef ref, Label label) {
label.selected ? ref.read(labelsProvider.notifier).unselect(label) : ref.read(labelsProvider.notifier).select(label);
}

/// Selects all the labels.
void selectAllLabels(WidgetRef ref) {
ref.read(labelsProvider.notifier).selectAll();
}

/// Unselects all the labels.
void unselectAllLabels(WidgetRef ref) {
ref.read(labelsProvider.notifier).unselectAll();
}

/// Exits the labels selection mode.
///
/// First unselects all the labels.
void exitLabelsSelectionMode(WidgetRef ref) {
unselectAllLabels(ref);

isLabelsSelectionModeNotifier.value = false;
}
24 changes: 24 additions & 0 deletions lib/common/actions/labels/visible.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:localmaterialnotes/common/actions/labels/select.dart';
import 'package:localmaterialnotes/models/label/label.dart';
import 'package:localmaterialnotes/providers/labels/labels/labels_provider.dart';

/// Toggles the visible status of the [label].
///
/// Returns `true` if the visible status of the [label] was toggled, `false` otherwise.
Future<bool> toggleVisibleLabel(WidgetRef ref, Label? label) async {
if (label == null) {
return false;
}

await ref.read(labelsProvider.notifier).toggleVisible(label);

return false;
}

/// Toggles the visible status of the [labels].
Future<void> toggleVisibleLabels(WidgetRef ref, List<Label> labels) async {
await ref.read(labelsProvider.notifier).toggleVisibleAll(labels);

exitLabelsSelectionMode(ref);
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:localmaterialnotes/common/actions/select.dart';
import 'package:localmaterialnotes/common/actions/notes/select.dart';
import 'package:localmaterialnotes/models/note/note.dart';
import 'package:localmaterialnotes/providers/notes/notes_provider.dart';
import 'package:localmaterialnotes/providers/notes/notes/notes_provider.dart';
import 'package:localmaterialnotes/providers/notifiers.dart';
import 'package:localmaterialnotes/routing/routes/notes/notes_editor_route.dart';
import 'package:localmaterialnotes/routing/routes/shell/shell_route.dart';
Expand All @@ -11,8 +11,8 @@ import 'package:localmaterialnotes/routing/routes/shell/shell_route.dart';
///
/// A [content] can be specified when the note is created from a sharing intent.
Future<void> addNote(BuildContext context, WidgetRef ref, {String? content}) async {
if (isSelectionModeNotifier.value) {
exitSelectionMode(context, ref);
if (isNotesSelectionModeNotifier.value) {
exitNotesSelectionMode(context, ref);
}

final note = content == null ? Note.empty() : Note.content(content);
Expand All @@ -24,9 +24,5 @@ Future<void> addNote(BuildContext context, WidgetRef ref, {String? content}) asy

currentNoteNotifier.value = note;

if (!context.mounted) {
return;
}

NotesEditorRoute(readOnly: false, autoFocus: true).push(context);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'package:localmaterialnotes/models/note/note.dart';
import 'package:localmaterialnotes/utils/snack_bar_utils.dart';

/// Copies the content of the [note] to the clipboard.
Future<void> copy(Note note) async {
Future<void> copyNote(Note note) async {
Clipboard.setData(
ClipboardData(text: note.contentPreview),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:localmaterialnotes/common/actions/notes/select.dart';
import 'package:localmaterialnotes/common/constants/constants.dart';
import 'package:localmaterialnotes/common/dialogs/confirmation_dialog.dart';
import 'package:localmaterialnotes/common/extensions/build_context_extension.dart';
import 'package:localmaterialnotes/models/note/note.dart';
import 'package:localmaterialnotes/providers/bin/bin_provider.dart';
import 'package:localmaterialnotes/providers/notes/notes_provider.dart';
import 'package:localmaterialnotes/providers/notes/notes/notes_provider.dart';
import 'package:localmaterialnotes/providers/notifiers.dart';
import 'package:localmaterialnotes/routing/routes/notes/notes_editor_route.dart';
import 'package:localmaterialnotes/routing/routes/shell/shell_route.dart';
Expand All @@ -35,13 +36,13 @@ Future<bool> deleteNote(BuildContext context, WidgetRef ref, Note? note) async {

currentNoteNotifier.value = null;

await ref.read(notesProvider.notifier).delete(note);
final succeeded = await ref.read(notesProvider.notifier).delete(note);

if (context.mounted && context.location == const NotesEditorRoute.empty().location) {
context.pop();
}

return true;
return succeeded;
}

/// Deletes the [notes].
Expand All @@ -59,9 +60,13 @@ Future<bool> deleteNotes(BuildContext context, WidgetRef ref, List<Note> notes)
return false;
}

await ref.read(notesProvider.notifier).deleteAll(notes);
final succeeded = await ref.read(notesProvider.notifier).deleteAll(notes);

return true;
if (context.mounted) {
exitNotesSelectionMode(context, ref);
}

return succeeded;
}

/// Permanently deletes the [note].
Expand All @@ -87,13 +92,13 @@ Future<bool> permanentlyDeleteNote(BuildContext context, WidgetRef ref, Note? no

currentNoteNotifier.value = null;

await ref.read(binProvider.notifier).permanentlyDelete(note);
final succeeded = await ref.read(binProvider.notifier).permanentlyDelete(note);

if (context.mounted && context.location == const NotesEditorRoute.empty().location) {
context.pop();
}

return true;
return succeeded;
}

/// Permanently deletes the [notes].
Expand All @@ -112,9 +117,13 @@ Future<bool> permanentlyDeleteNotes(BuildContext context, WidgetRef ref, List<No
return false;
}

await ref.read(binProvider.notifier).permanentlyDeleteAll(notes);
final succeeded = await ref.read(binProvider.notifier).permanentlyDeleteAll(notes);

if (context.mounted) {
exitNotesSelectionMode(context, ref);
}

return true;
return succeeded;
}

/// Empties the bin by deleting every note inside.
Expand All @@ -132,9 +141,9 @@ Future<bool> emptyBin(BuildContext context, WidgetRef ref) async {
return false;
}

isSelectionModeNotifier.value = false;
isNotesSelectionModeNotifier.value = false;

await ref.read(binProvider.notifier).empty();
final succeeded = await ref.read(binProvider.notifier).empty();

return true;
return succeeded;
}
31 changes: 31 additions & 0 deletions lib/common/actions/notes/labels.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:localmaterialnotes/models/label/label.dart';
import 'package:localmaterialnotes/models/note/note.dart';
import 'package:localmaterialnotes/pages/editor/dialogs/labels_selection_dialog.dart';
import 'package:localmaterialnotes/providers/notes/notes/notes_provider.dart';
import 'package:localmaterialnotes/providers/notifiers.dart';

/// Asks the user to select the labels for the [note].
Future<List<Label>?> selectLabels(BuildContext context, WidgetRef ref, Note note) async {
final selectedLabels = await showAdaptiveDialog<List<Label>>(
context: context,
builder: (context) {
return LabelsSelectionDialog(
note: note,
);
},
);

if (selectedLabels == null) {
return null;
}

await ref.read(notesProvider.notifier).editLabels(note, selectedLabels);

// Forcefully notify the listeners because only the labels of the note have changed
currentNoteNotifier.value = note;
currentNoteNotifier.forceNotify();

return selectedLabels;
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:localmaterialnotes/common/actions/notes/select.dart';
import 'package:localmaterialnotes/models/note/note.dart';
import 'package:localmaterialnotes/providers/notes/notes_provider.dart';
import 'package:localmaterialnotes/providers/notes/notes/notes_provider.dart';

/// Toggles the pined status of the [note].
///
/// Returns `true` if the pined status of the [note] was toggled, `false` otherwise.
Future<bool> togglePinNote(BuildContext context, WidgetRef ref, Note? note) async {
if (note == null) {
return false;
Expand All @@ -17,4 +20,8 @@ Future<bool> togglePinNote(BuildContext context, WidgetRef ref, Note? note) asyn
/// Toggles the pined status of the [notes].
Future<void> togglePinNotes(BuildContext context, WidgetRef ref, List<Note> notes) async {
await ref.read(notesProvider.notifier).togglePinAll(notes);

if (context.mounted) {
exitNotesSelectionMode(context, ref);
}
}
Loading

0 comments on commit 0248401

Please sign in to comment.