Skip to content

Commit 308b07a

Browse files
committed
implemented search notes according to pull request saber-notes#1137
1 parent 24cbe1c commit 308b07a

File tree

4 files changed

+216
-0
lines changed

4 files changed

+216
-0
lines changed

lib/data/routes.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ abstract class HomeRoutes {
4949
cupertinoIcon: CupertinoIcons.folder_fill,
5050
),
5151
),
52+
_Route(
53+
routePath: _homeFunction({'subpage': HomePage.searchSubpage}),
54+
label: t.home.tabs.search,
55+
icon: const AdaptiveIcon(
56+
icon: Icons.search,
57+
cupertinoIcon: CupertinoIcons.search_circle_fill,
58+
),
59+
),
5260
_Route(
5361
routePath: _homeFunction({'subpage': HomePage.whiteboardSubpage}),
5462
label: t.home.tabs.whiteboard,

lib/i18n/strings_en.g.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ class TranslationsHomeTabsEn {
226226
// Translations
227227
String get home => 'Home';
228228
String get browse => 'Browse';
229+
String get search => 'Search';
229230
String get whiteboard => 'Whiteboard';
230231
String get settings => 'Settings';
231232
}
@@ -239,6 +240,7 @@ class TranslationsHomeTitlesEn {
239240
// Translations
240241
String get home => 'Recent notes';
241242
String get browse => 'Browse';
243+
String get search => 'Search for Notes';
242244
String get whiteboard => 'Whiteboard';
243245
String get settings => 'Settings';
244246
}

lib/pages/home/home.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:saber/components/settings/update_manager.dart';
55
import 'package:saber/components/theming/dynamic_material_app.dart';
66
import 'package:saber/pages/home/browse.dart';
77
import 'package:saber/pages/home/recent_notes.dart';
8+
import 'package:saber/pages/home/search.dart';
89
import 'package:saber/pages/home/settings.dart';
910
import 'package:saber/pages/home/whiteboard.dart';
1011

@@ -25,8 +26,10 @@ class HomePage extends StatefulWidget {
2526
static const String browseSubpage = 'browse';
2627
static const String whiteboardSubpage = 'whiteboard';
2728
static const String settingsSubpage = 'settings';
29+
static const String searchSubpage = 'search';
2830
static const List<String> subpages = [
2931
recentSubpage,
32+
searchSubpage,
3033
browseSubpage,
3134
whiteboardSubpage,
3235
settingsSubpage
@@ -51,6 +54,8 @@ class _HomePageState extends State<HomePage> {
5154
return const Whiteboard();
5255
case HomePage.settingsSubpage:
5356
return const SettingsPage();
57+
case HomePage.searchSubpage:
58+
return const SearchPage();
5459
default:
5560
return const RecentPage();
5661
}

lib/pages/home/search.dart

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import 'dart:async';
2+
3+
import 'package:collapsible/collapsible.dart';
4+
import 'package:flutter/material.dart';
5+
import 'package:go_router/go_router.dart';
6+
import 'package:logging/logging.dart';
7+
import 'package:saber/components/home/export_note_button.dart';
8+
import 'package:saber/components/home/masonry_files.dart';
9+
import 'package:saber/components/home/move_note_button.dart';
10+
import 'package:saber/components/home/new_note_button.dart';
11+
import 'package:saber/components/home/rename_note_button.dart';
12+
import 'package:saber/data/file_manager/file_manager.dart';
13+
import 'package:saber/data/prefs.dart';
14+
import 'package:saber/data/routes.dart';
15+
import 'package:saber/i18n/strings.g.dart';
16+
import 'package:saber/pages/editor/editor.dart';
17+
18+
class SearchPage extends StatefulWidget {
19+
const SearchPage({super.key});
20+
21+
@override
22+
State<SearchPage> createState() => _SearchPageState();
23+
}
24+
25+
class _SearchPageState extends State<SearchPage> {
26+
final List<String> filePaths = [];
27+
List<String> filteredFiles = [];
28+
bool failed = false;
29+
30+
ValueNotifier<List<String>> selectedFiles = ValueNotifier([]);
31+
final log = Logger('SearchPage');
32+
33+
/// Mitigates a bug where files got imported starting with `null/` instead of `/`.
34+
///
35+
/// This caused them to be written to `Documents/Sabernull/...` instead of `Documents/Saber/...`.
36+
///
37+
/// See https://github.com/saber-notes/saber/issues/996
38+
/// and https://github.com/saber-notes/saber/pull/977.
39+
void moveIncorrectlyImportedFiles() async {
40+
for (final filePath in Prefs.recentFiles.value) {
41+
if (filePath.startsWith('/')) continue;
42+
43+
final String newFilePath;
44+
if (filePath.startsWith('null/')) {
45+
newFilePath = await FileManager.suffixFilePathToMakeItUnique(
46+
filePath.substring('null'.length));
47+
} else {
48+
newFilePath =
49+
await FileManager.suffixFilePathToMakeItUnique('/$filePath');
50+
}
51+
52+
log.warning(
53+
'Found incorrectly imported file at `$filePath`; moving to `$newFilePath`');
54+
await FileManager.moveFile(filePath, newFilePath);
55+
}
56+
}
57+
58+
@override
59+
void initState() {
60+
findAllNotes();
61+
fileWriteSubscription =
62+
FileManager.fileWriteStream.stream.listen(fileWriteListener);
63+
selectedFiles.addListener(_setState);
64+
65+
super.initState();
66+
moveIncorrectlyImportedFiles();
67+
}
68+
69+
@override
70+
void dispose() {
71+
selectedFiles.removeListener(_setState);
72+
fileWriteSubscription?.cancel();
73+
super.dispose();
74+
}
75+
76+
void filterFiles(String search) {
77+
if (search== '') {
78+
filteredFiles = filePaths;
79+
}
80+
search = search.toLowerCase();
81+
filteredFiles = filePaths.where((file) => file.toLowerCase().contains(search)).toList();
82+
}
83+
84+
StreamSubscription? fileWriteSubscription;
85+
void fileWriteListener(FileOperation event) {
86+
findAllNotes(fromFileListener: true);
87+
}
88+
89+
void _setState() => setState(() {});
90+
91+
Future findAllNotes({bool fromFileListener = false}) async {
92+
if (!mounted) return;
93+
94+
if (fromFileListener) {
95+
// don't refresh if we're not on the home page
96+
final location = GoRouterState.of(context).uri.toString();
97+
if (!location.startsWith(RoutePaths.prefixOfHome)) return;
98+
}
99+
100+
final children = await FileManager.getAllFiles();
101+
filePaths.clear();
102+
if (children.isEmpty) {
103+
failed = true;
104+
} else {
105+
failed = false;
106+
filePaths.addAll(children);
107+
filteredFiles = filePaths;
108+
}
109+
110+
if (mounted) setState(() {});
111+
}
112+
113+
@override
114+
Widget build(BuildContext context) {
115+
final platform = Theme.of(context).platform;
116+
final cupertino =
117+
platform == TargetPlatform.iOS || platform == TargetPlatform.macOS;
118+
final crossAxisCount = MediaQuery.sizeOf(context).width ~/ 300 + 1;
119+
return Scaffold(
120+
body: RefreshIndicator(
121+
onRefresh: () => Future.wait(
122+
[Future.delayed(const Duration(milliseconds: 500))]),
123+
child: CustomScrollView(
124+
slivers: [
125+
SliverPadding(
126+
padding: const EdgeInsets.only(
127+
bottom: 8,
128+
),
129+
sliver: SliverAppBar(
130+
toolbarHeight: 70,
131+
title: Center(
132+
child: SearchBar(
133+
keyboardType: TextInputType.text,
134+
hintText: t.home.titles.search,
135+
onChanged: (value) {
136+
setState(() {
137+
filterFiles(value);
138+
});
139+
},
140+
),
141+
)
142+
)
143+
),
144+
SliverSafeArea(
145+
minimum: const EdgeInsets.only(
146+
bottom: 70
147+
),
148+
sliver: MasonryFiles(
149+
crossAxisCount: crossAxisCount,
150+
files: [
151+
for (String filePath in filteredFiles) filePath,
152+
],
153+
selectedFiles: selectedFiles,
154+
),
155+
)
156+
]
157+
)
158+
),floatingActionButton: NewNoteButton(
159+
cupertino: cupertino,
160+
),
161+
persistentFooterButtons: selectedFiles.value.isEmpty
162+
? null
163+
: [
164+
Collapsible(
165+
axis: CollapsibleAxis.vertical,
166+
collapsed: selectedFiles.value.length != 1,
167+
child: RenameNoteButton(
168+
existingPath: selectedFiles.value.isEmpty
169+
? ''
170+
: selectedFiles.value.first,
171+
unselectNotes: () => selectedFiles.value = [],
172+
),
173+
),
174+
MoveNoteButton(
175+
filesToMove: selectedFiles.value,
176+
unselectNotes: () => selectedFiles.value = [],
177+
),
178+
IconButton(
179+
padding: EdgeInsets.zero,
180+
tooltip: t.home.deleteNote,
181+
onPressed: () async {
182+
await Future.wait([
183+
for (String filePath in selectedFiles.value)
184+
() async {
185+
bool oldExtension = FileManager.doesFileExist(filePath + Editor.extensionOldJson);
186+
await FileManager.deleteFile(filePath + (oldExtension
187+
? Editor.extensionOldJson
188+
: Editor.extension));
189+
}(),
190+
]);
191+
selectedFiles.value = [];
192+
},
193+
icon: const Icon(Icons.delete_forever),
194+
),
195+
ExportNoteButton(
196+
selectedFiles: selectedFiles.value,
197+
),
198+
],
199+
);
200+
}
201+
}

0 commit comments

Comments
 (0)