Skip to content

Commit 04456f3

Browse files
committed
[303] feat: add notes types preference
1 parent fd993a1 commit 04456f3

9 files changed

Lines changed: 272 additions & 51 deletions

File tree

lib/common/preferences/preference_key.dart

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,22 @@ import 'preferences_utils.dart';
33
// ignore_for_file: public_member_api_docs
44

55
/// Keys of preferences.
6-
enum PreferenceKey<T> {
6+
enum PreferenceKey<T extends Object> {
77
// Appearance
88
locale<String>('en', backup: false),
99
theme<String>('system'),
1010
dynamicTheming<bool>(true),
1111
blackTheming<bool>(false),
1212
appFont<String>('systemDefault'),
1313
editorFont<String>('systemDefault'),
14+
15+
// Notes creation
16+
availableNotesTypes<List<String>>([
17+
'PlainTextNote',
18+
'RichTextNote',
19+
]),
20+
21+
// Notes tiles
1422
showTilesBackground<bool>(false),
1523
showSeparators<bool>(false),
1624
showTitlesOnly<bool>(false),

lib/common/preferences/preferences_utils.dart

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
2+
import 'package:shared_preferences/shared_preferences.dart';
3+
24
import '../constants/constants.dart';
35
import 'preference_key.dart';
4-
import 'package:shared_preferences/shared_preferences.dart';
56

67
/// Manages user preferences.
78
class PreferencesUtils {
@@ -36,16 +37,19 @@ class PreferencesUtils {
3637
return;
3738
}
3839

39-
if (T == bool) {
40-
await _preferences.setBool(preferenceKey.name, value as bool);
41-
} else if (T == int) {
42-
await _preferences.setInt(preferenceKey.name, value as int);
43-
} else if (T == double) {
44-
await _preferences.setDouble(preferenceKey.name, value as double);
45-
} else if (T == String) {
46-
await _preferences.setString(preferenceKey.name, value as String);
47-
} else if (T == List<String>) {
48-
await _preferences.setStringList(preferenceKey.name, value as List<String>);
40+
switch (T) {
41+
case == bool:
42+
await _preferences.setBool(preferenceKey.name, value as bool);
43+
case == int:
44+
await _preferences.setInt(preferenceKey.name, value as int);
45+
case == double:
46+
await _preferences.setDouble(preferenceKey.name, value as double);
47+
case == String:
48+
await _preferences.setString(preferenceKey.name, value as String);
49+
case == List<String>:
50+
await _preferences.setStringList(preferenceKey.name, value as List<String>);
51+
default:
52+
throw ArgumentError('Invalid preference type: $T');
4953
}
5054
}
5155

@@ -58,7 +62,17 @@ class PreferencesUtils {
5862
}
5963

6064
try {
61-
return _preferences.get(preferenceKey.name) as T?;
65+
if (T == List<String>) {
66+
return _preferences.getStringList(preferenceKey.name) as T?;
67+
}
68+
69+
return switch (T) {
70+
== bool => _preferences.getBool(preferenceKey.name),
71+
== int => _preferences.getInt(preferenceKey.name),
72+
== double => _preferences.getDouble(preferenceKey.name),
73+
== String => _preferences.getString(preferenceKey.name),
74+
_ => throw ArgumentError('Invalid preference type: $T'),
75+
} as T?;
6276
}
6377
// On type conversion error, reset the preference to its default value
6478
on TypeError catch (error) {

lib/common/preferences/watched_preferences.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:flutter/material.dart';
22

3+
import '../../models/note/notes_types.dart';
34
import '../../utils/theme_utils.dart';
45
import 'enums/bin_swipe_action.dart';
56
import 'enums/font.dart';
@@ -16,6 +17,11 @@ class WatchedPreferences {
1617
late bool dynamicTheming;
1718
late bool blackTheming;
1819
late Font appFont;
20+
21+
// Notes types
22+
late List<Type> availableNotesTypes;
23+
24+
// Notes tiles
1925
late bool showTitlesOnly;
2026
late bool showTilesBackground;
2127
late bool showSeparators;
@@ -41,6 +47,7 @@ class WatchedPreferences {
4147
bool? dynamicTheming,
4248
bool? blackTheming,
4349
Font? appFont,
50+
List<Type>? availableNotesTypes,
4451
bool? showTitlesOnly,
4552
bool? showTilesBackground,
4653
bool? showSeparators,
@@ -59,6 +66,9 @@ class WatchedPreferences {
5966
this.dynamicTheming = dynamicTheming ?? PreferenceKey.dynamicTheming.getPreferenceOrDefault();
6067
this.blackTheming = blackTheming ?? PreferenceKey.blackTheming.getPreferenceOrDefault();
6168
this.appFont = appFont ?? Font.appFromPreference();
69+
70+
this.availableNotesTypes = availableNotesTypes ?? NotesTypes.fromPreference();
71+
6272
this.showTitlesOnly = showTitlesOnly ?? PreferenceKey.showTitlesOnly.getPreferenceOrDefault();
6373
this.showTilesBackground = showTilesBackground ?? PreferenceKey.showTilesBackground.getPreferenceOrDefault();
6474
this.showSeparators = showSeparators ?? PreferenceKey.showSeparators.getPreferenceOrDefault();

lib/models/note/notes_types.dart

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import 'package:flutter_helper_utils/flutter_helper_utils.dart';
2+
import 'package:settings_tiles/settings_tiles.dart';
3+
4+
import '../../common/constants/constants.dart';
5+
import '../../common/preferences/preference_key.dart';
6+
import 'note.dart';
7+
8+
/// Types of the notes.
9+
class NotesTypes {
10+
/// Returns the list of all the notes types.
11+
static List<MultipleOptionsDetails> all() {
12+
return [
13+
(
14+
value: PlainTextNote,
15+
title: l.note_type_plain_text,
16+
subtitle: null,
17+
),
18+
(
19+
value: RichTextNote,
20+
title: l.note_type_rich_text,
21+
subtitle: null,
22+
),
23+
];
24+
}
25+
26+
/// Returns the list of notes types saved in the preferences as a list of [Type].
27+
static List<Type> fromPreference() {
28+
final availableNotesTypes = <Type>[];
29+
final availableNotesTypesPreference = PreferenceKey.availableNotesTypes.getPreferenceOrDefault();
30+
31+
for (final type in availableNotesTypesPreference) {
32+
switch (type) {
33+
case 'PlainTextNote':
34+
availableNotesTypes.add(PlainTextNote);
35+
case 'RichTextNote':
36+
availableNotesTypes.add(RichTextNote);
37+
default:
38+
throw Exception('Unknown note type: $type');
39+
}
40+
}
41+
42+
assert(availableNotesTypes.isNotEmpty, 'The list of available notes types is empty');
43+
44+
return availableNotesTypes;
45+
}
46+
47+
/// Returns the list of notes types saved in the preferences as a comma-separated [String].
48+
static String fromPreferenceAsString() {
49+
final availableNotesTypesAsString = <String>[];
50+
final availableNotesTypesPreference = PreferenceKey.availableNotesTypes.getPreferenceOrDefault();
51+
52+
for (final type in availableNotesTypesPreference) {
53+
switch (type) {
54+
case 'PlainTextNote':
55+
availableNotesTypesAsString.add(l.note_type_plain_text);
56+
case 'RichTextNote':
57+
availableNotesTypesAsString.add(l.note_type_rich_text);
58+
default:
59+
throw Exception('Unknown note type: $type');
60+
}
61+
}
62+
63+
assert(availableNotesTypesAsString.isNotEmpty, 'The list of available notes types as string is empty');
64+
65+
return availableNotesTypesAsString.join(', ').capitalizeFirstLowerRest;
66+
}
67+
68+
/// Returns the list of [notesTypes] as a list of [String] to be saved in the preferences.
69+
static List<String> toPreference(List<Type> notesTypes) {
70+
return notesTypes.map((type) => type.toString()).toList();
71+
}
72+
}

lib/pages/notes/notes_page.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import '../../common/navigation/top_navigation.dart';
88
import '../../common/widgets/notes/notes_list.dart';
99
import '../../models/label/label.dart';
1010
import '../../providers/notifiers/notifiers.dart';
11+
import '../../providers/preferences/preferences_provider.dart';
1112
import '../../utils/keys.dart';
1213
import 'widgets/add_note_fab.dart';
1314

@@ -37,6 +38,10 @@ class _NotesPageState extends ConsumerState<NotesPage> {
3738

3839
@override
3940
Widget build(BuildContext context) {
41+
final availableNotesTypes = ref.watch(
42+
preferencesProvider.select((preferences) => preferences.availableNotesTypes),
43+
);
44+
4045
return Scaffold(
4146
appBar: TopNavigation(
4247
appbar: NotesAppBar(
@@ -45,7 +50,7 @@ class _NotesPageState extends ConsumerState<NotesPage> {
4550
),
4651
),
4752
drawer: SideNavigation(),
48-
floatingActionButtonLocation: ExpandableFab.location,
53+
floatingActionButtonLocation: availableNotesTypes.length > 1 ? ExpandableFab.location : null,
4954
floatingActionButton: AddNoteFab(
5055
key: Keys.fabAddNote,
5156
),

lib/pages/notes/widgets/add_note_fab.dart

Lines changed: 64 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
55
import '../../../common/actions/notes/add.dart';
66
import '../../../common/constants/constants.dart';
77
import '../../../models/note/note.dart';
8+
import '../../../providers/preferences/preferences_provider.dart';
89

910
/// Floating action button to add a note.
1011
class AddNoteFab extends ConsumerStatefulWidget {
@@ -18,50 +19,78 @@ class AddNoteFab extends ConsumerStatefulWidget {
1819
class _AddNoteFabState extends ConsumerState<AddNoteFab> {
1920
final fabKey = GlobalKey<ExpandableFabState>();
2021

21-
void onPressed<NoteType>() {
22-
final fabState = fabKey.currentState;
22+
void onFocusChange(bool hasFocus) {
23+
if (!hasFocus) {
24+
final fabState = fabKey.currentState;
25+
26+
if (fabState == null) {
27+
return;
28+
}
2329

24-
if (fabState == null) {
25-
return;
30+
if (fabState.isOpen) {
31+
fabState.toggle();
32+
}
2633
}
34+
}
2735

36+
void onPressed<NoteType>() {
2837
addNote<NoteType>(context, ref);
29-
if (fabState.isOpen) {
30-
fabState.toggle();
38+
39+
if (fabKey.currentState != null && fabKey.currentState!.isOpen) {
40+
fabKey.currentState!.toggle();
3141
}
3242
}
3343

3444
@override
3545
Widget build(BuildContext context) {
36-
return ExpandableFab(
37-
key: fabKey,
38-
type: ExpandableFabType.up,
39-
childrenAnimation: ExpandableFabAnimation.none,
40-
distance: 75,
41-
openButtonBuilder: RotateFloatingActionButtonBuilder(
42-
heroTag: '<open add note FAB hero tag>',
43-
child: const Icon(Icons.add),
44-
),
45-
closeButtonBuilder: RotateFloatingActionButtonBuilder(
46-
heroTag: '<close add note FAB hero tag>',
47-
child: const Icon(Icons.close),
48-
),
49-
children: [
50-
FloatingActionButton.extended(
51-
heroTag: '<add plain text note hero tag>',
52-
tooltip: l.tooltip_fab_add_plain_text_note,
53-
onPressed: onPressed<PlainTextNote>,
54-
icon: const Icon(Icons.text_fields),
55-
label: Text('Plain text'),
56-
),
57-
FloatingActionButton.extended(
58-
heroTag: '<add rich text note hero tag>',
59-
tooltip: l.tooltip_fab_add_rich_text_note,
60-
onPressed: onPressed<RichTextNote>,
61-
icon: const Icon(Icons.format_paint),
62-
label: Text('Rich text'),
63-
),
64-
],
46+
final availableNotesTypes = ref.watch(
47+
preferencesProvider.select((preferences) => preferences.availableNotesTypes),
6548
);
49+
50+
return availableNotesTypes.length == 1
51+
? FloatingActionButton(
52+
tooltip: l.tooltip_fab_add_note,
53+
onPressed: switch (availableNotesTypes.first) {
54+
== PlainTextNote => onPressed<PlainTextNote>,
55+
== RichTextNote => onPressed<RichTextNote>,
56+
_ => throw Exception('Unknown note type when adding a note: ${availableNotesTypes.first}'),
57+
},
58+
child: const Icon(Icons.add),
59+
)
60+
: Focus(
61+
onFocusChange: onFocusChange,
62+
child: ExpandableFab(
63+
key: fabKey,
64+
type: ExpandableFabType.up,
65+
childrenAnimation: ExpandableFabAnimation.none,
66+
distance: 75,
67+
openButtonBuilder: RotateFloatingActionButtonBuilder(
68+
heroTag: '<open add note FAB hero tag>',
69+
child: const Icon(Icons.add),
70+
),
71+
closeButtonBuilder: RotateFloatingActionButtonBuilder(
72+
heroTag: '<close add note FAB hero tag>',
73+
child: const Icon(Icons.close),
74+
),
75+
children: [
76+
if (availableNotesTypes.contains(PlainTextNote))
77+
FloatingActionButton.extended(
78+
heroTag: '<add plain text note hero tag>',
79+
tooltip: l.tooltip_fab_add_plain_text_note,
80+
onPressed: onPressed<PlainTextNote>,
81+
icon: const Icon(Icons.text_fields),
82+
label: Text('Plain text'),
83+
),
84+
if (availableNotesTypes.contains(RichTextNote))
85+
FloatingActionButton.extended(
86+
heroTag: '<add rich text note hero tag>',
87+
tooltip: l.tooltip_fab_add_rich_text_note,
88+
onPressed: onPressed<RichTextNote>,
89+
icon: const Icon(Icons.format_paint),
90+
label: Text('Rich text'),
91+
),
92+
],
93+
),
94+
);
6695
}
6796
}

lib/pages/settings/pages/settings_notes_tiles_page.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import '../../../common/preferences/watched_preferences.dart';
1212
import '../../../providers/preferences/preferences_provider.dart';
1313
import '../../../utils/keys.dart';
1414

15-
/// Settings related to the appearance of the application.
15+
/// Notes tiles settings.
1616
class SettingsNotesTilesPage extends ConsumerStatefulWidget {
17-
/// Default constructor.
17+
/// Settings page related to the notes tiles.
1818
const SettingsNotesTilesPage({super.key});
1919

2020
@override

0 commit comments

Comments
 (0)