+7
ios/Podfile.lock
+7
ios/Podfile.lock
···
13
13
- FlutterMacOS
14
14
- share_plus (0.0.1):
15
15
- Flutter
16
+
- shared_preferences_foundation (0.0.1):
17
+
- Flutter
18
+
- FlutterMacOS
16
19
- sqflite_darwin (0.0.4):
17
20
- Flutter
18
21
- FlutterMacOS
···
27
30
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
28
31
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
29
32
- share_plus (from `.symlinks/plugins/share_plus/ios`)
33
+
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
30
34
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
31
35
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
32
36
···
45
49
:path: ".symlinks/plugins/path_provider_foundation/darwin"
46
50
share_plus:
47
51
:path: ".symlinks/plugins/share_plus/ios"
52
+
shared_preferences_foundation:
53
+
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
48
54
sqflite_darwin:
49
55
:path: ".symlinks/plugins/sqflite_darwin/darwin"
50
56
url_launcher_ios:
···
58
64
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
59
65
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
60
66
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
67
+
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
61
68
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
62
69
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
63
70
+166
-3
lib/screens/explore_page.dart
+166
-3
lib/screens/explore_page.dart
···
1
1
import 'dart:async';
2
2
3
3
import 'package:flutter/material.dart';
4
+
import 'package:flutter_riverpod/flutter_riverpod.dart';
4
5
import 'package:grain/api.dart';
5
6
import 'package:grain/app_icons.dart';
6
7
import 'package:grain/models/profile.dart';
8
+
import 'package:grain/providers/profile_provider.dart';
7
9
import 'package:grain/widgets/app_image.dart';
8
10
import 'package:grain/widgets/plain_text_field.dart';
11
+
import 'package:shared_preferences/shared_preferences.dart';
9
12
10
13
import 'profile_page.dart';
11
14
12
-
class ExplorePage extends StatefulWidget {
15
+
class ExplorePage extends ConsumerStatefulWidget {
13
16
const ExplorePage({super.key});
14
17
15
18
@override
16
-
State<ExplorePage> createState() => _ExplorePageState();
19
+
ConsumerState<ExplorePage> createState() => _ExplorePageState();
17
20
}
18
21
19
-
class _ExplorePageState extends State<ExplorePage> {
22
+
class _ExplorePageState extends ConsumerState<ExplorePage> {
20
23
final TextEditingController _controller = TextEditingController();
21
24
List<Profile> _results = [];
25
+
List<Profile> _recentlySearched = [];
26
+
static const String _recentlySearchedKey = 'recently_searched_dids';
22
27
bool _loading = false;
23
28
bool _searched = false;
24
29
Timer? _debounce;
···
26
31
@override
27
32
void initState() {
28
33
super.initState();
34
+
_loadRecentlySearched();
35
+
}
36
+
37
+
Future<void> _loadRecentlySearched() async {
38
+
final prefs = await SharedPreferences.getInstance();
39
+
final dids = prefs.getStringList(_recentlySearchedKey) ?? [];
40
+
if (dids.isNotEmpty) {
41
+
final profiles = <Profile>[];
42
+
for (final did in dids) {
43
+
try {
44
+
final asyncProfile = ref.watch(profileNotifierProvider(did));
45
+
if (asyncProfile.hasValue && asyncProfile.value != null) {
46
+
profiles.add(asyncProfile.value!.profile);
47
+
} else {
48
+
final profileWithGalleries = await ref.refresh(profileNotifierProvider(did).future);
49
+
if (profileWithGalleries != null) {
50
+
profiles.add(profileWithGalleries.profile);
51
+
}
52
+
}
53
+
} catch (_) {}
54
+
}
55
+
if (mounted) {
56
+
setState(() {
57
+
_recentlySearched = profiles;
58
+
});
59
+
}
60
+
}
61
+
}
62
+
63
+
Future<void> _saveRecentlySearched() async {
64
+
final prefs = await SharedPreferences.getInstance();
65
+
final dids = _recentlySearched.map((p) => p.did).toList();
66
+
await prefs.setStringList(_recentlySearchedKey, dids);
29
67
}
30
68
31
69
void _onSearchChanged(String value) {
···
94
132
: null,
95
133
),
96
134
),
135
+
if (_controller.text.isEmpty && _recentlySearched.isNotEmpty)
136
+
Padding(
137
+
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4),
138
+
child: Column(
139
+
crossAxisAlignment: CrossAxisAlignment.start,
140
+
children: [
141
+
Padding(
142
+
padding: const EdgeInsets.only(left: 4.0, bottom: 4.0),
143
+
child: Text(
144
+
'Recent Searches',
145
+
style: theme.textTheme.titleSmall?.copyWith(
146
+
fontWeight: FontWeight.bold,
147
+
color: theme.textTheme.bodyMedium?.color,
148
+
),
149
+
),
150
+
),
151
+
SizedBox(
152
+
height: 96,
153
+
child: ListView.separated(
154
+
scrollDirection: Axis.horizontal,
155
+
itemCount: _recentlySearched.length,
156
+
separatorBuilder: (context, index) => SizedBox(width: 16),
157
+
itemBuilder: (context, index) {
158
+
final profile = _recentlySearched[index];
159
+
return SizedBox(
160
+
width: 80,
161
+
child: Stack(
162
+
children: [
163
+
SizedBox(
164
+
width: 80,
165
+
height: 96,
166
+
child: Column(
167
+
mainAxisAlignment: MainAxisAlignment.center,
168
+
children: [
169
+
Expanded(
170
+
child: GestureDetector(
171
+
onTap: () async {
172
+
FocusScope.of(context).unfocus();
173
+
_debounce?.cancel();
174
+
setState(() {
175
+
_searched = false;
176
+
_loading = false;
177
+
});
178
+
if (context.mounted) {
179
+
Navigator.of(context).push(
180
+
MaterialPageRoute(
181
+
builder: (context) =>
182
+
ProfilePage(did: profile.did, showAppBar: true),
183
+
),
184
+
);
185
+
}
186
+
},
187
+
child: CircleAvatar(
188
+
radius: 32,
189
+
backgroundColor: theme.colorScheme.surfaceContainerHighest,
190
+
backgroundImage: profile.avatar?.isNotEmpty == true
191
+
? NetworkImage(profile.avatar!)
192
+
: null,
193
+
child: profile.avatar?.isNotEmpty == true
194
+
? null
195
+
: Icon(
196
+
AppIcons.accountCircle,
197
+
color: theme.iconTheme.color,
198
+
size: 40,
199
+
),
200
+
),
201
+
),
202
+
),
203
+
SizedBox(height: 4),
204
+
SizedBox(
205
+
width: 80,
206
+
child: Text(
207
+
profile.displayName?.isNotEmpty == true
208
+
? profile.displayName!
209
+
: '@${profile.handle}',
210
+
style: theme.textTheme.bodyMedium,
211
+
maxLines: 1,
212
+
overflow: TextOverflow.ellipsis,
213
+
textAlign: TextAlign.center,
214
+
),
215
+
),
216
+
],
217
+
),
218
+
),
219
+
Positioned(
220
+
top: 4,
221
+
right: 4,
222
+
child: GestureDetector(
223
+
onTap: () async {
224
+
setState(() {
225
+
_recentlySearched.removeAt(index);
226
+
});
227
+
await _saveRecentlySearched();
228
+
},
229
+
child: Container(
230
+
padding: const EdgeInsets.all(2),
231
+
decoration: BoxDecoration(
232
+
color: theme.colorScheme.surface,
233
+
shape: BoxShape.circle,
234
+
boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 2)],
235
+
),
236
+
child: Icon(Icons.close, size: 18, color: theme.iconTheme.color),
237
+
),
238
+
),
239
+
),
240
+
],
241
+
),
242
+
);
243
+
},
244
+
),
245
+
),
246
+
],
247
+
),
248
+
),
97
249
if (_controller.text.isNotEmpty)
98
250
Padding(
99
251
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4),
···
159
311
setState(() {
160
312
_searched = false;
161
313
_loading = false;
314
+
// Move to front if already present, else add to front
315
+
final existingIndex = _recentlySearched.indexWhere((p) => p.did == profile.did);
316
+
if (existingIndex != -1) {
317
+
_recentlySearched.removeAt(existingIndex);
318
+
}
319
+
_recentlySearched.insert(0, profile);
320
+
// Limit to 10 recent users
321
+
if (_recentlySearched.length > 10) {
322
+
_recentlySearched.removeLast();
323
+
}
162
324
});
325
+
await _saveRecentlySearched();
163
326
if (context.mounted) {
164
327
Navigator.of(context).push(
165
328
MaterialPageRoute(
+2
macos/Flutter/GeneratedPluginRegistrant.swift
+2
macos/Flutter/GeneratedPluginRegistrant.swift
···
12
12
import package_info_plus
13
13
import path_provider_foundation
14
14
import share_plus
15
+
import shared_preferences_foundation
15
16
import sqflite_darwin
16
17
import url_launcher_macos
17
18
import window_to_front
···
24
25
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
25
26
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
26
27
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
28
+
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
27
29
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
28
30
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
29
31
WindowToFrontPlugin.register(with: registry.registrar(forPlugin: "WindowToFrontPlugin"))
+56
pubspec.lock
+56
pubspec.lock
···
1032
1032
url: "https://pub.dev"
1033
1033
source: hosted
1034
1034
version: "6.0.0"
1035
+
shared_preferences:
1036
+
dependency: "direct main"
1037
+
description:
1038
+
name: shared_preferences
1039
+
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
1040
+
url: "https://pub.dev"
1041
+
source: hosted
1042
+
version: "2.5.3"
1043
+
shared_preferences_android:
1044
+
dependency: transitive
1045
+
description:
1046
+
name: shared_preferences_android
1047
+
sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac"
1048
+
url: "https://pub.dev"
1049
+
source: hosted
1050
+
version: "2.4.10"
1051
+
shared_preferences_foundation:
1052
+
dependency: transitive
1053
+
description:
1054
+
name: shared_preferences_foundation
1055
+
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
1056
+
url: "https://pub.dev"
1057
+
source: hosted
1058
+
version: "2.5.4"
1059
+
shared_preferences_linux:
1060
+
dependency: transitive
1061
+
description:
1062
+
name: shared_preferences_linux
1063
+
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
1064
+
url: "https://pub.dev"
1065
+
source: hosted
1066
+
version: "2.4.1"
1067
+
shared_preferences_platform_interface:
1068
+
dependency: transitive
1069
+
description:
1070
+
name: shared_preferences_platform_interface
1071
+
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
1072
+
url: "https://pub.dev"
1073
+
source: hosted
1074
+
version: "2.4.1"
1075
+
shared_preferences_web:
1076
+
dependency: transitive
1077
+
description:
1078
+
name: shared_preferences_web
1079
+
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
1080
+
url: "https://pub.dev"
1081
+
source: hosted
1082
+
version: "2.4.3"
1083
+
shared_preferences_windows:
1084
+
dependency: transitive
1085
+
description:
1086
+
name: shared_preferences_windows
1087
+
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
1088
+
url: "https://pub.dev"
1089
+
source: hosted
1090
+
version: "2.4.1"
1035
1091
shelf:
1036
1092
dependency: transitive
1037
1093
description: