Skip to content

Commit

Permalink
[372] feat: add checklist note
Browse files Browse the repository at this point in the history
  • Loading branch information
maelchiotti committed Feb 2, 2025
1 parent 180b10e commit 357cf72
Show file tree
Hide file tree
Showing 27 changed files with 408 additions and 113 deletions.
6 changes: 5 additions & 1 deletion lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:after_layout/after_layout.dart';
import 'package:back_button_interceptor/back_button_interceptor.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/material.dart';
import 'package:flutter_checklist/checklist.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import 'common/actions/labels/select.dart';
Expand Down Expand Up @@ -135,7 +136,10 @@ class _AppState extends ConsumerState<App> with AfterLayoutMixin<App> {
theme: lightTheme,
darkTheme: darkTheme,
themeMode: themeMode,
localizationsDelegates: AppLocalizations.localizationsDelegates,
localizationsDelegates: [
...AppLocalizations.localizationsDelegates,
ChecklistLocalizations.delegate,
],
supportedLocales: SupportedLanguage.locales,
locale: LocaleUtils().appLocale,
debugShowCheckedModeBanner: false,
Expand Down
2 changes: 2 additions & 0 deletions lib/common/actions/notes/add.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Future<void> addNote<NoteType>(BuildContext context, WidgetRef ref, {String? con
note = content == null ? PlainTextNote.empty() : PlainTextNote.content(content);
case == RichTextNote:
note = content == null ? RichTextNote.empty() : RichTextNote.content(content);
case == ChecklistNote:
note = ChecklistNote.empty();
default:
throw Exception('Unknown note type when creating a new note: $NoteType');
}
Expand Down
6 changes: 5 additions & 1 deletion lib/common/constants/paddings.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';

import 'constants.dart';

// ignore_for_file: avoid_classes_with_only_static_members
Expand Down Expand Up @@ -40,7 +41,10 @@ class Paddings {
/// Padding for a page.
static EdgeInsetsDirectional get page => const EdgeInsetsDirectional.all(16);

/// Padding for a page except the bottom.
/// Padding for a page (horizontal).
static EdgeInsetsDirectional get pageHorizontal => const EdgeInsetsDirectional.symmetric(horizontal: 16);

/// Padding for a page (except the bottom).
static EdgeInsetsDirectional get pageButBottom => const EdgeInsetsDirectional.only(top: 16, start: 16, end: 16);

/// Padding for the end of the app bar.
Expand Down
2 changes: 1 addition & 1 deletion lib/common/navigation/app_bars/editor_app_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'package:fleather/fleather.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../../../models/note/notes_types.dart';
import '../../../models/note/types/note_type.dart';
import '../../../pages/editor/sheets/about_sheet.dart';
import '../../../providers/notifiers/notifiers.dart';
import '../../actions/notes/copy.dart';
Expand Down
2 changes: 1 addition & 1 deletion lib/common/preferences/preference_key.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ enum PreferenceKey<T extends Object> {
editorFont<String>('systemDefault'),

// Notes types
availableNotesTypes<List<String>>(['plainText', 'richText']),
availableNotesTypes<List<String>>(['plainText', 'richText', 'checklist']),
defaultShortcutNoteType<String>('plainText'),

// Rich text notes
Expand Down
2 changes: 1 addition & 1 deletion lib/common/preferences/watched_preferences.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';

import '../../models/note/notes_types.dart';
import '../../models/note/types/note_type.dart';
import '../ui/theme_utils.dart';
import 'enums/bin_swipe_action.dart';
import 'enums/font.dart';
Expand Down
4 changes: 3 additions & 1 deletion lib/common/system/quick_actions_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:quick_actions/quick_actions.dart';

import '../../models/note/note.dart';
import '../../models/note/notes_types.dart';
import '../../models/note/types/note_type.dart';
import '../actions/notes/add.dart';
import '../localization/localizations_utils.dart';

Expand All @@ -25,6 +25,8 @@ class QuickActionsUtils {
addNote<PlainTextNote>(context, ref);
case NoteType.richText:
addNote<RichTextNote>(context, ref);
default:
throw Exception('This note type cannot be created from a shortcut: $defaultShortcutNoteType');
}
}
});
Expand Down
4 changes: 3 additions & 1 deletion lib/common/system/share_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import 'package:parchment_delta/parchment_delta.dart';
import 'package:receive_sharing_intent/receive_sharing_intent.dart';

import '../../models/note/note.dart';
import '../../models/note/notes_types.dart';
import '../../models/note/types/note_type.dart';
import '../actions/notes/add.dart';
import '../constants/constants.dart';

Expand Down Expand Up @@ -49,5 +49,7 @@ void _processSharedData(WidgetRef ref, List<SharedMediaFile> data) {
addNote<PlainTextNote>(context, ref, content: content);
case NoteType.richText:
addNote<RichTextNote>(context, ref, content: content);
default:
throw Exception('This note type cannot be created from a shortcut: $defaultShortcutNoteType');
}
}
8 changes: 8 additions & 0 deletions lib/l10n/translations/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,10 @@
"@tooltip_fab_add_rich_text_note": {
"description": "Floating action button in the notes page to add a new rich text note."
},
"tooltip_fab_add_checklist_note": "Add a checklist note",
"@tooltip_fab_add_checklist_note": {
"description": "Floating action button in the notes page to add a new checklist note."
},
"tooltip_fab_add_label": "Add a label",
"@tooltip_fab_add_label": {
"description": "Floating action button in the labels page to add a new label."
Expand Down Expand Up @@ -1068,5 +1072,9 @@
"note_type_rich_text": "Rich text",
"@note_type_rich_text": {
"description": "Note with rich text."
},
"note_type_checklist": "Checklist",
"@note_type_checklist": {
"description": "Note with a checklist."
}
}
12 changes: 8 additions & 4 deletions lib/models/note/note.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:convert';

import 'package:collection/collection.dart';
import 'package:fleather/fleather.dart';
import 'package:flutter_checklist/checklist.dart';
import 'package:isar/isar.dart';
import 'package:json_annotation/json_annotation.dart';

Expand All @@ -10,13 +11,15 @@ import '../../common/files/encryption_utils.dart';
import '../../common/preferences/enums/sort_method.dart';
import '../../common/preferences/preference_key.dart';
import '../label/label.dart';
import 'notes_types.dart';
import 'types/note_type.dart';

part 'note.g.dart';

part 'plain_text/plain_text_note.dart';
part 'types/checklist_note.dart';

part 'rich_text/rich_text_note.dart';
part 'types/plain_text_note.dart';

part 'types/rich_text_note.dart';

/// Converts the [labels] to a JSON-compatible list of strings.
List<String> labelToJson(IsarLinks<Label> labels) => labels.map((label) => label.name).toList();
Expand Down Expand Up @@ -68,14 +71,15 @@ sealed class Note implements Comparable<Note> {
required this.createdTime,
required this.editedTime,
required this.title,
required this.type,
});

/// Returns this note with the [title] and the content encrypted using the [password].
Note encrypted(String password);

/// The type of the note.
@ignore
NoteType get type;
NoteType type;

/// The content as plain text.
@ignore
Expand Down
125 changes: 125 additions & 0 deletions lib/models/note/types/checklist_note.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
part of '../note.dart';

/// Checklist note.
@JsonSerializable()
@Collection()
class ChecklistNote extends Note {
/// The list of checkboxes of this checklist.
List<bool> checkboxes;

/// The list of texts of this checklist.
List<String> texts;

/// A note with checklist content.
ChecklistNote({
required super.deleted,
required super.pinned,
required super.createdTime,
required super.editedTime,
required super.title,
super.type = NoteType.checklist,
required this.checkboxes,
required this.texts,
}) : assert(
checkboxes.length == texts.length,
'The lists of checkboxes and texts of a checklist note are different: ${checkboxes.length} and ${texts.length}',
);

/// Checklist note with empty title and content.
factory ChecklistNote.empty() => ChecklistNote(
deleted: false,
pinned: false,
createdTime: DateTime.now(),
editedTime: DateTime.now(),
title: '',
checkboxes: [],
texts: [],
);

/// Checklist note with the provided [content].
factory ChecklistNote.content(List<ChecklistLine> content) => ChecklistNote(
deleted: false,
pinned: false,
createdTime: DateTime.now(),
editedTime: DateTime.now(),
title: '',
checkboxes: content.map((checklistLine) => checklistLine.toggled).toList(),
texts: content.map((checklistLine) => checklistLine.text).toList(),
);

/// Checklist note from [json] data.
factory ChecklistNote.fromJson(Map<String, dynamic> json) => _$ChecklistNoteFromJson(json);

/// Checklist note from [json] data, encrypted with [password].
factory ChecklistNote.fromJsonEncrypted(Map<String, dynamic> json, String password) {
String title = json['title'];
List texts = json['texts'];

return _$ChecklistNoteFromJson(json)
..title = title.isEmpty ? '' : EncryptionUtils().decrypt(password, title)
..texts = texts.isEmpty ? [] : texts.map((text) => EncryptionUtils().decrypt(password, text)).toList();
}

/// Checklist note to JSON.
Map<String, dynamic> toJson() => _$ChecklistNoteToJson(this);

@override
Note encrypted(String password) {
return this
..title = isTitleEmpty ? '' : EncryptionUtils().encrypt(password, title)
..texts = texts.map((text) => EncryptionUtils().encrypt(password, text)).toList();
}

@ignore
@override
String get plainText {
StringBuffer plainText = StringBuffer();

for (int index = 0; index < checkboxes.length; index++) {
final checked = checkboxes[index];
final text = texts[index];

plainText.writeln('${checked ? '✅' : '⬜'} $text');
}

return plainText.toString();
}

@ignore
@override
String get markdown {
StringBuffer plainText = StringBuffer();

for (int index = 0; index < checkboxes.length; index++) {
final checked = checkboxes[index];
final text = texts[index];

plainText.writeln('${checked ? '[x]' : '[ ]'} $text');
}

return plainText.toString();
}

@ignore
@override
String get contentPreview => plainText.trim();

@ignore
@override
bool get isContentEmpty => checkboxes.isEmpty;

/// The list of [ChecklistLine] of this checklist.
@ignore
List<ChecklistLine> get checklistLines {
final checklistLines = <ChecklistLine>[];

for (int index = 0; index < checkboxes.length; index++) {
final checked = checkboxes[index];
final text = texts[index];

checklistLines.add((toggled: checked, text: text));
}

return checklistLines;
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_helper_utils/flutter_helper_utils.dart';

import '../../common/constants/constants.dart';
import '../../common/preferences/preference_key.dart';
import 'note.dart';
import '../../../common/constants/constants.dart';
import '../../../common/preferences/preference_key.dart';
import '../note.dart';

/// The types of notes.
enum NoteType<T extends Note> {
Expand All @@ -12,6 +12,9 @@ enum NoteType<T extends Note> {

/// Rich text note.
richText<RichTextNote>(Icons.format_paint),

/// Checklist note.
checklist<ChecklistNote>(Icons.checklist),
;

/// Icon representing this note type.
Expand All @@ -24,6 +27,7 @@ enum NoteType<T extends Note> {
return switch (T) {
== PlainTextNote => l.note_type_plain_text,
== RichTextNote => l.note_type_rich_text,
== ChecklistNote => l.note_type_checklist,
_ => throw Exception('Unknown note type: $T'),
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ class PlainTextNote extends Note {
/// The content of the note.
String content;

/// Note with plain text content.
/// A note with plain text content.
PlainTextNote({
required super.deleted,
required super.pinned,
required super.createdTime,
required super.editedTime,
required super.title,
super.type = NoteType.plainText,
required this.content,
});

Expand Down Expand Up @@ -53,10 +54,6 @@ class PlainTextNote extends Note {
..title = isTitleEmpty ? '' : EncryptionUtils().encrypt(password, title)
..content = EncryptionUtils().encrypt(password, content);

@ignore
@override
NoteType get type => NoteType.plainText;

@ignore
@override
String get plainText => content;
Expand All @@ -67,7 +64,7 @@ class PlainTextNote extends Note {

@ignore
@override
String get contentPreview => content.trim();
String get contentPreview => plainText.trim();

@ignore
@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ class RichTextNote extends Note {
/// The content of the note, as rich text in the fleather representation.
String content;

/// Note with rich text content.
/// A note with rich text content.
RichTextNote({
required super.deleted,
required super.pinned,
required super.createdTime,
required super.editedTime,
required super.title,
super.type = NoteType.richText,
required this.content,
});

Expand Down Expand Up @@ -60,10 +61,6 @@ class RichTextNote extends Note {
@ignore
ParchmentDocument get document => ParchmentDocument.fromJson(jsonDecode(content) as List);

@ignore
@override
NoteType get type => NoteType.richText;

@ignore
@override
String get plainText => document.toPlainText();
Expand Down
Loading

0 comments on commit 357cf72

Please sign in to comment.