Skip to content

Commit bfd982c

Browse files
Feature/explore search (#194)
1 parent 79a8ec1 commit bfd982c

17 files changed

Lines changed: 344 additions & 284 deletions

File tree

lam7a/lib/core/hive_types.dart

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,51 @@
1-
import 'package:hive_flutter/adapters.dart';
1+
import 'package:hive_flutter/hive_flutter.dart';
22
import 'package:lam7a/features/messaging/adapters/chat_message_adapter.dart';
33
import 'package:lam7a/features/messaging/adapters/conversation_adapter.dart';
4+
import 'package:lam7a/core/models/user_model.dart';
5+
import 'package:lam7a/features/Explore/cache/recent_searches.dart';
46

57
class HiveTypes {
8+
// Existing type IDs
69
static const int chatMessage = 1;
710
static const int conversation = 2;
811

12+
// New type IDs
13+
static const int userModel = 3;
14+
15+
// Box names
16+
static const String autocompletesBox = "autocompletes_box";
17+
static const String usersBox = "users_box";
18+
19+
// ----------------------------------------
20+
// INITIALIZE
21+
// ----------------------------------------
922
static Future<void> initialize() async {
1023
await Hive.initFlutter();
24+
25+
// Register messaging adapters
1126
Hive.registerAdapter(ChatMessageAdapter());
1227
Hive.registerAdapter(ConversationAdapter());
28+
29+
// Register your UserModel adapter
30+
if (!Hive.isAdapterRegistered(userModel)) {
31+
Hive.registerAdapter(UserModelAdapter());
32+
}
33+
34+
// Open boxes if they are not opened
35+
await _openIfNotOpen<String>(autocompletesBox);
36+
await _openIfNotOpen<UserModel>(usersBox);
37+
}
38+
39+
// ----------------------------------------
40+
// UTILITY: open box only once
41+
// ----------------------------------------
42+
static Future<Box<T>> _openIfNotOpen<T>(String name) async {
43+
if (Hive.isBoxOpen(name)) return Hive.box<T>(name);
44+
return Hive.openBox<T>(name);
1345
}
1446

1547
Future<Box<T>> openBoxIfNeeded<T>(String name) async {
1648
if (Hive.isBoxOpen(name)) return Hive.box<T>(name);
17-
return await Hive.openBox<T>(name);
49+
return Hive.openBox<T>(name);
1850
}
1951
}

lam7a/lib/core/models/user_model.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:freezed_annotation/freezed_annotation.dart';
2+
import 'package:hive/hive.dart';
23

34
part 'user_model.freezed.dart';
45
part 'user_model.g.dart';
Lines changed: 60 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,70 @@
1-
// import 'package:hive/hive.dart';
2-
// import 'package:lam7a/core/hive_types.dart';
1+
import 'package:hive/hive.dart';
2+
import 'package:lam7a/core/models/user_model.dart';
33

4-
// class RecentSearchesService {
5-
// static const String _key = 'items';
4+
class UserModelAdapter extends TypeAdapter<UserModel> {
5+
@override
6+
final int typeId = 3;
67

7-
// Future<Box> _box() async {
8-
// return HiveTypes().openBoxIfNeeded(HiveTypes.recentSearchesBox);
9-
// }
8+
@override
9+
UserModel read(BinaryReader reader) {
10+
// id (int?)
11+
final isIdNull = reader.readBool();
12+
final int? id = isIdNull ? null : reader.readInt();
1013

11-
// Future<List<String>> getSearches() async {
12-
// final box = await _box();
13-
// return List<String>.from(box.get(_key, defaultValue: []));
14-
// }
14+
// name (String?)
15+
final isNameNull = reader.readBool();
16+
final String? name = isNameNull ? null : reader.readString();
1517

16-
// Future<void> addSearch(String query) async {
17-
// final box = await _box();
18-
// final list = List<String>.from(box.get(_key, defaultValue: []));
18+
// username (String?)
19+
final isUsernameNull = reader.readBool();
20+
final String? username = isUsernameNull ? null : reader.readString();
1921

20-
// if (list.contains(query)) list.remove(query);
21-
// list.insert(0, query);
22+
// profileImageUrl (String?)
23+
final isProfileUrlNull = reader.readBool();
24+
final String? profileImageUrl = isProfileUrlNull
25+
? null
26+
: reader.readString();
2227

23-
// await box.put(_key, list.take(20).toList()); // keep 20 max
24-
// }
28+
return UserModel(
29+
id: id,
30+
name: name,
31+
username: username,
32+
profileImageUrl: profileImageUrl,
33+
);
34+
}
2535

26-
// Future<void> clear() async {
27-
// final box = await _box();
28-
// await box.delete(_key);
29-
// }
30-
// }
36+
@override
37+
void write(BinaryWriter writer, UserModel obj) {
38+
// id
39+
if (obj.id == null) {
40+
writer.writeBool(true);
41+
} else {
42+
writer.writeBool(false);
43+
writer.writeInt(obj.id!);
44+
}
3145

32-
// class RecentProfilesService {
33-
// static const String _key = 'profiles';
46+
// name
47+
if (obj.name == null) {
48+
writer.writeBool(true);
49+
} else {
50+
writer.writeBool(false);
51+
writer.writeString(obj.name!);
52+
}
3453

35-
// Future<Box> _box() async {
36-
// return HiveTypes().openBoxIfNeeded(HiveTypes.recentProfilesBox);
37-
// }
54+
// username
55+
if (obj.username == null) {
56+
writer.writeBool(true);
57+
} else {
58+
writer.writeBool(false);
59+
writer.writeString(obj.username!);
60+
}
3861

39-
// Future<List<String>> getProfiles() async {
40-
// final box = await _box();
41-
// return List<String>.from(box.get(_key, defaultValue: []));
42-
// }
43-
44-
// Future<void> addProfile(String userId) async {
45-
// final box = await _box();
46-
// final list = List<String>.from(box.get(_key, defaultValue: []));
47-
48-
// if (list.contains(userId)) list.remove(userId);
49-
// list.insert(0, userId);
50-
51-
// await box.put(_key, list.take(20).toList());
52-
// }
53-
54-
// Future<void> clear() async {
55-
// final box = await _box();
56-
// await box.delete(_key);
57-
// }
58-
// }
62+
// profileImageUrl
63+
if (obj.profileImageUrl == null) {
64+
writer.writeBool(true);
65+
} else {
66+
writer.writeBool(false);
67+
writer.writeString(obj.profileImageUrl!);
68+
}
69+
}
70+
}
Lines changed: 62 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import 'package:riverpod_annotation/riverpod_annotation.dart';
2+
import 'package:hive/hive.dart';
3+
24
import '../services/search_api_service.dart';
35
import 'package:lam7a/core/models/user_model.dart';
46
import 'package:lam7a/features/common/models/tweet_model.dart';
@@ -13,33 +15,70 @@ SearchRepository searchRepository(Ref ref) {
1315
class SearchRepository {
1416
final SearchApiService _api;
1517

16-
final List<String> _autocompletesCache = [
17-
'hello',
18-
'this',
19-
'is',
20-
'autocomplete',
21-
'welcome',
22-
];
23-
24-
final List<UserModel> _usersCache = [
25-
UserModel(
26-
id: 1,
27-
name: 'John Doe',
28-
username: 'johndoe',
29-
profileImageUrl: 'https://example.com/avatar1.png',
30-
),
31-
UserModel(
32-
id: 2,
33-
name: 'Jane Smith',
34-
username: 'janesmith',
35-
profileImageUrl: 'https://example.com/avatar2.png',
36-
),
37-
];
18+
static const String autocompletesBox = 'autocomplete_stack';
19+
static const String usersBox = 'recent_users_stack';
20+
static const int maxSize = 8;
3821

3922
SearchRepository(this._api);
4023

24+
// -------------------------------
25+
// HIVE STACK HELPER FUNCTIONS
26+
// -------------------------------
27+
28+
Future<void> pushAutocomplete(String value) async {
29+
final box = await Hive.openBox<String>(autocompletesBox);
30+
31+
box.values.where((v) => v == value).toList().forEach((v) {
32+
final index = box.values.toList().indexOf(v);
33+
if (index != -1) box.deleteAt(index);
34+
});
35+
36+
await box.add(value);
37+
38+
if (box.length > maxSize) {
39+
await box.deleteAt(0); // remove oldest
40+
}
41+
}
42+
43+
Future<List<String>> getCachedAutocompletes() async {
44+
final box = await Hive.openBox<String>(autocompletesBox);
45+
return box.values.toList().reversed.toList();
46+
}
47+
48+
Future<void> pushUser(UserModel user) async {
49+
final box = await Hive.openBox<UserModel>(usersBox);
50+
51+
// Remove duplicates by id
52+
final existingIndex = box.values.toList().indexWhere(
53+
(u) => u.id != null && u.id == user.id,
54+
);
55+
56+
if (existingIndex != -1) {
57+
await box.deleteAt(existingIndex);
58+
}
59+
60+
// Insert newest user at top
61+
await box.add(user);
62+
63+
// Enforce max size
64+
if (box.length > maxSize) {
65+
await box.deleteAt(0);
66+
}
67+
}
68+
69+
/// GET all recent cached users (LIFO)
70+
Future<List<UserModel>> getCachedUsers() async {
71+
final box = await Hive.openBox<UserModel>(usersBox);
72+
return box.values.toList().reversed.toList();
73+
}
74+
75+
// --------------------------------------
76+
// API FUNCTIONS (unchanged)
77+
// --------------------------------------
78+
4179
Future<List<UserModel>> searchUsers(String query, int limit, int page) =>
4280
_api.searchUsers(query, limit, page);
81+
4382
Future<List<TweetModel>> searchTweets(
4483
String query,
4584
int limit,
@@ -53,6 +92,7 @@ class SearchRepository {
5392
tweetsOrder: tweetsOrder,
5493
time: time,
5594
);
95+
5696
Future<List<TweetModel>> searchHashtagTweets(
5797
String hashtag,
5898
int limit,
@@ -66,16 +106,4 @@ class SearchRepository {
66106
tweetsOrder: tweetsOrder,
67107
time: time,
68108
);
69-
70-
Future<List<String>> getCachedAutocompletes() async {
71-
// Simulate network delay
72-
await Future.delayed(const Duration(seconds: 1));
73-
return _autocompletesCache;
74-
}
75-
76-
Future<List<UserModel>> getCachedUsers() async {
77-
// Simulate network delay
78-
await Future.delayed(const Duration(seconds: 1));
79-
return _usersCache;
80-
}
81109
}

lam7a/lib/features/Explore/ui/view/explore_and_trending/for_you_view.dart

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,14 @@ class ForYouView extends StatelessWidget {
4444

4545
// Divider after trending hashtags
4646
Divider(
47-
height: 1,
48-
thickness: 0.3,
47+
height: 2,
48+
thickness: 0.2,
4949
color: theme.brightness == Brightness.light
5050
? const Color.fromARGB(120, 83, 99, 110)
5151
: const Color(0xFF8B98A5),
5252
),
5353

54-
const SizedBox(height: 24),
54+
const SizedBox(height: 16),
5555

5656
// ----- Who to follow Section -----
5757
Padding(
@@ -62,13 +62,13 @@ class ForYouView extends StatelessWidget {
6262
color: theme.brightness == Brightness.light
6363
? const Color(0xFF0D0D0D)
6464
: const Color(0xFFFFFFFF),
65-
fontSize: 18,
66-
fontWeight: FontWeight.bold,
65+
fontSize: 19,
66+
fontWeight: FontWeight.w600,
6767
),
6868
),
6969
),
7070

71-
const SizedBox(height: 16),
71+
const SizedBox(height: 10),
7272

7373
// ----- Suggested Users List -----
7474
ListView.builder(
@@ -78,7 +78,7 @@ class ForYouView extends StatelessWidget {
7878
itemBuilder: (context, index) {
7979
final user = suggestedUsers[index];
8080
return Padding(
81-
padding: const EdgeInsets.only(bottom: 8),
81+
padding: const EdgeInsets.only(bottom: 8, left: 8, right: 4),
8282
child: UserTile(user: user),
8383
);
8484
},
@@ -119,15 +119,13 @@ class ForYouView extends StatelessWidget {
119119

120120
// Divider after who to follow section
121121
Divider(
122-
height: 1,
123-
thickness: 0.3,
122+
height: 2,
123+
thickness: 0.2,
124124
color: theme.brightness == Brightness.light
125125
? const Color.fromARGB(120, 83, 99, 110)
126126
: const Color(0xFF8B98A5),
127127
),
128128

129-
const SizedBox(height: 16),
130-
131129
// ----- For You Tweets Sections -----
132130
// Map through each interest and its tweets
133131
...forYouTweetsMap.entries.map((entry) {
@@ -139,8 +137,11 @@ class ForYouView extends StatelessWidget {
139137

140138
return Column(
141139
children: [
142-
StaticTweetsListView(interest: interest, tweets: tweets),
143-
const SizedBox(height: 16),
140+
StaticTweetsListView(
141+
interest: interest,
142+
tweets: tweets,
143+
selfScrolling: false,
144+
),
144145
],
145146
);
146147
}),

0 commit comments

Comments
 (0)