-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathnote.dart
More file actions
244 lines (195 loc) · 7.16 KB
/
Copy pathnote.dart
File metadata and controls
244 lines (195 loc) · 7.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:fleather/fleather.dart';
import 'package:flutter_checklist/checklist.dart';
import 'package:isar_community/isar.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:remove_markdown/remove_markdown.dart';
import '../../../common/constants/constants.dart';
import '../../common/files/encryption_utils.dart';
import '../../common/preferences/enums/sort_method.dart';
import '../../common/preferences/preference_key.dart';
import '../../services/notes/notes_service.dart';
import '../label/label.dart';
import 'note_status.dart';
import 'types/note_type.dart';
part 'note.g.dart';
part 'types/checklist_note.dart';
part 'types/markdown_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();
/// Base class of the notes.
sealed class Note implements Comparable<Note> {
/// The regex expression used to count the number of words in the content of the note.
final RegExp _wordsCountRegex = RegExp(r'[\w-]+');
/// The ID of the note as an [int] used by isar.
Id get isarId => _idHash;
/// The ID of the note as a [String] used by the application.
@JsonKey(includeFromJson: false, includeToJson: false)
String id = uuid.v4();
/// The type of the note.
@ignore
@JsonKey(includeToJson: true, includeFromJson: false)
NoteType type;
/// Whether the note is archived.
@Index()
@JsonKey(defaultValue: false)
bool archived;
/// Whether the note is deleted.
@Index()
@JsonKey(defaultValue: false)
bool deleted;
/// Whether the note is pinned.
@Index()
@JsonKey(defaultValue: false)
bool pinned;
/// Whether the note is locked.
@JsonKey(includeFromJson: false, includeToJson: false)
bool locked;
/// The time when the note was created.
DateTime createdTime;
/// The last time when the note was edited (title or content).
DateTime editedTime;
/// The time when the note was deleted (if applicable).
DateTime? deletedTime;
/// The title of the note.
@JsonKey(defaultValue: '')
String title;
/// The labels used to categorize the note.
@JsonKey(includeFromJson: false, includeToJson: true, toJson: labelToJson)
IsarLinks<Label> labels = IsarLinks<Label>();
/// Whether the note is selected.
///
/// Excluded from JSON and the database because it's only needed temporarily during multi-selection.
@JsonKey(includeFromJson: false, includeToJson: false)
@ignore
bool selected = false;
/// Default constructor of a note.
Note({
required this.archived,
required this.deleted,
required this.pinned,
this.locked = false,
required this.createdTime,
required this.editedTime,
this.deletedTime,
required this.title,
required this.type,
}) : assert(!(archived && deleted), 'A note cannot be archived and deleted at the same time');
/// Returns this note with the [title] and the content encrypted using the [password].
Note encrypted(String password);
/// The status of the note.
@ignore
NoteStatus get status {
if (archived) {
return NoteStatus.archived;
} else if (deleted) {
return NoteStatus.deleted;
}
return NoteStatus.available;
}
/// The content as plain text.
@ignore
String get contentAsText;
/// The content as markdown.
@ignore
String get contentAsMarkdown;
/// The content for the preview of the notes tiles.
@ignore
String get contentPreview;
/// The title and the content joined together to be shared as a single text.
@ignore
String get shareText => '$title\n\n$contentPreview';
/// The number of words in the content.
@ignore
int get wordsCount => _wordsCountRegex.allMatches(contentAsText).length;
/// The number of characters in the content.
@ignore
int get charactersCount => contentAsText.length;
/// Whether the title is empty.
@ignore
bool get isTitleEmpty => title.isEmpty;
/// Whether the content is empty.
@ignore
bool get isContentEmpty;
/// Whether the preview of the content is empty.
@ignore
bool get isContentPreviewEmpty => contentPreview.isEmpty;
/// Whether the note is empty (title and content).
@ignore
bool get isEmpty => isTitleEmpty && isContentEmpty;
/// Returns the visible [labels] of the note as a sorted list.
@ignore
List<Label> get labelsVisibleSorted => labels.toList().where((label) => label.visible).sorted();
/// The names of the visible [labels] of the note as a sorted list.
@ignore
List<String> get labelsNamesVisibleSorted => labelsVisibleSorted.map((label) => label.name).toList();
/// The number of labels of the note.
@ignore
int get labelsCount => labels.toList().length;
/// Whether at least one label is locked.
@ignore
bool get hasLockedLabel => labels.any((label) => label.locked);
/// Whether the note requires authentication to be accessed.
@ignore
bool get requiresAuthentication {
final lockNote = PreferenceKey.lockNote.preferenceOrDefault;
final lockLabel = PreferenceKey.lockLabel.preferenceOrDefault;
final shouldLockNote = lockNote && locked;
final shouldLockLabel = lockLabel && hasLockedLabel;
return shouldLockNote || shouldLockLabel;
}
/// The [labels] as markdown.
@ignore
String get labelsAsMarkdown => '> ${labelsNamesVisibleSorted.join(', ')}';
/// The [title] indexed for full-text search.
@Index(type: IndexType.value, caseSensitive: false)
List<String> get titleIndexed => Isar.splitWords(title.toLowerCase());
/// The [contentAsText] indexed for full-text search.
@Index(type: IndexType.value, caseSensitive: false)
List<String> get contentIndexed => Isar.splitWords(contentAsText.toLowerCase());
/// Notes are sorted according to:
/// 1. Their pin state.
/// 2. The sort method chosen by the user.
@override
int compareTo(Note other) {
final sortMethod = SortMethod.fromPreference();
final sortAscending = PreferenceKey.sortAscending.preferenceOrDefault;
if (pinned && !other.pinned) {
return -1;
} else if (!pinned && other.pinned) {
return 1;
} else {
switch (sortMethod) {
case SortMethod.createdDate:
return sortAscending ? createdTime.compareTo(other.createdTime) : other.createdTime.compareTo(createdTime);
case SortMethod.editedDate:
return sortAscending ? editedTime.compareTo(other.editedTime) : other.editedTime.compareTo(editedTime);
case SortMethod.title:
return sortAscending ? title.compareTo(other.title) : other.title.compareTo(title);
default:
throw Exception('The sort method is not valid: $sortMethod');
}
}
}
@override
bool operator ==(Object other) => other is Note && other.runtimeType == runtimeType && other.id == id;
@ignore
@override
int get hashCode => id.hashCode;
@ignore
int get _idHash {
var hash = 0xcbf29ce484222325;
var i = 0;
while (i < id.length) {
final codeUnit = id.codeUnitAt(i++);
hash ^= codeUnit >> 8;
hash *= 0x100000001b3;
hash ^= codeUnit & 0xFF;
hash *= 0x100000001b3;
}
return hash;
}
}