Skip to content

Commit af36c27

Browse files
committed
- Implemented favorite using hive in movie, tv series and celebrity
1 parent ba554f0 commit af36c27

File tree

11 files changed

+532
-13
lines changed

11 files changed

+532
-13
lines changed

lib/core/hive/favorite_model.dart

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import 'package:hive/hive.dart';
2+
3+
part 'favorite_model.g.dart';
4+
5+
@HiveType(typeId: 0)
6+
class Favorite extends HiveObject {
7+
@HiveField(0)
8+
late int id;
9+
10+
@HiveField(1)
11+
late int itemId;
12+
13+
@HiveField(2)
14+
late String title;
15+
16+
@HiveField(3)
17+
late String posterPath;
18+
19+
@HiveField(4)
20+
late String type; // 'movie', 'tv', or 'celebrity'
21+
22+
@HiveField(5)
23+
String? overview;
24+
25+
@HiveField(6)
26+
String? releaseDate;
27+
28+
Favorite({
29+
required this.id,
30+
required this.itemId,
31+
required this.title,
32+
required this.posterPath,
33+
required this.type,
34+
this.overview,
35+
this.releaseDate,
36+
});
37+
}

lib/core/hive/hive_helper.dart

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import 'package:flutter/foundation.dart';
2+
import 'package:hive_flutter/hive_flutter.dart';
3+
import 'package:flutter_movie_clean_architecture/core/hive/favorite_model.dart';
4+
5+
class HiveHelper {
6+
static const String _boxName = 'favorites_box';
7+
static Box<Favorite>? _box;
8+
9+
static Future<void> init() async {
10+
// Initialize Hive
11+
await Hive.initFlutter();
12+
13+
// Register adapter
14+
Hive.registerAdapter(FavoriteAdapter());
15+
16+
// Open box
17+
_box = await Hive.openBox<Favorite>(_boxName);
18+
}
19+
20+
static Box<Favorite> _getBox() {
21+
if (_box == null) {
22+
throw Exception('HiveHelper not initialized. Call init() first.');
23+
}
24+
return _box!;
25+
}
26+
27+
// Insert a favorite
28+
static Future<void> insertFavorite(Favorite favorite) async {
29+
final box = _getBox();
30+
await box.put('${favorite.itemId}_${favorite.type}', favorite);
31+
}
32+
33+
// Delete a favorite
34+
static Future<void> deleteFavorite(int itemId, String type) async {
35+
final box = _getBox();
36+
await box.delete('${itemId}_$type');
37+
}
38+
39+
// Check if an item is favorite
40+
static Future<bool> isFavorite(int itemId, String type) async {
41+
final box = _getBox();
42+
return box.containsKey('${itemId}_$type');
43+
}
44+
45+
// Get all favorites
46+
static Future<List<Favorite>> getAllFavorites() async {
47+
final box = _getBox();
48+
return box.values.toList();
49+
}
50+
51+
// Get favorites by type
52+
static Future<List<Favorite>> getFavoritesByType(String type) async {
53+
final box = _getBox();
54+
return box.values.where((favorite) => favorite.type == type).toList();
55+
}
56+
57+
// Close Hive
58+
static Future<void> close() async {
59+
await _box?.close();
60+
}
61+
}
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_movie_clean_architecture/core/config/app_constant.dart';
3+
import 'package:flutter_movie_clean_architecture/core/hive/favorite_model.dart';
4+
import 'package:flutter_movie_clean_architecture/core/hive/hive_helper.dart';
5+
import 'package:go_router/go_router.dart';
6+
7+
class FavoritesPage extends StatefulWidget {
8+
const FavoritesPage({super.key});
9+
10+
@override
11+
State<FavoritesPage> createState() => _FavoritesPageState();
12+
}
13+
14+
class _FavoritesPageState extends State<FavoritesPage> {
15+
int _currentIndex = 0;
16+
late Future<List<Favorite>> _favoritesFuture;
17+
18+
@override
19+
void initState() {
20+
super.initState();
21+
_loadFavorites();
22+
}
23+
24+
Future<void> _loadFavorites() async {
25+
setState(() {
26+
_favoritesFuture = _getFavoritesByType();
27+
});
28+
}
29+
30+
Future<List<Favorite>> _getFavoritesByType() async {
31+
switch (_currentIndex) {
32+
case 0:
33+
return await HiveHelper.getFavoritesByType('movie');
34+
case 1:
35+
return await HiveHelper.getFavoritesByType('tv');
36+
case 2:
37+
return await HiveHelper.getFavoritesByType('celebrity');
38+
default:
39+
return await HiveHelper.getAllFavorites();
40+
}
41+
}
42+
43+
Future<void> _refreshFavorites() async {
44+
await _loadFavorites();
45+
}
46+
47+
@override
48+
Widget build(BuildContext context) {
49+
return Scaffold(
50+
body: FutureBuilder<List<Favorite>>(
51+
future: _favoritesFuture,
52+
builder: (context, snapshot) {
53+
if (snapshot.connectionState == ConnectionState.waiting) {
54+
return const Center(child: CircularProgressIndicator());
55+
}
56+
57+
if (snapshot.hasError) {
58+
return Center(
59+
child: Text('Error loading favorites: ${snapshot.error}'),
60+
);
61+
}
62+
63+
final favorites = snapshot.data ?? [];
64+
65+
if (favorites.isEmpty) {
66+
return const Center(
67+
child: Column(
68+
mainAxisAlignment: MainAxisAlignment.center,
69+
children: [
70+
Icon(Icons.favorite_border, size: 64, color: Colors.grey),
71+
SizedBox(height: 16),
72+
Text(
73+
'No favorites yet',
74+
style: TextStyle(fontSize: 18, color: Colors.grey),
75+
),
76+
SizedBox(height: 8),
77+
Text(
78+
'Start adding favorites from movie, TV series, or artist details',
79+
textAlign: TextAlign.center,
80+
style: TextStyle(color: Colors.grey),
81+
),
82+
],
83+
),
84+
);
85+
}
86+
87+
return RefreshIndicator(
88+
onRefresh: _refreshFavorites,
89+
child: GridView.builder(
90+
padding: const EdgeInsets.all(16),
91+
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
92+
crossAxisCount: 2,
93+
crossAxisSpacing: 16,
94+
mainAxisSpacing: 16,
95+
childAspectRatio: 0.7,
96+
),
97+
itemCount: favorites.length,
98+
itemBuilder: (context, index) {
99+
final favorite = favorites[index];
100+
return Card(
101+
elevation: 4,
102+
shape: RoundedRectangleBorder(
103+
borderRadius: BorderRadius.circular(12),
104+
),
105+
child: InkWell(
106+
onTap: () {
107+
switch (favorite.type) {
108+
case 'movie':
109+
context.push('/movie/${favorite.itemId}');
110+
break;
111+
case 'tv':
112+
context.push('/tv/${favorite.itemId}');
113+
break;
114+
case 'celebrity':
115+
context.push('/artistId/${favorite.itemId}');
116+
break;
117+
}
118+
},
119+
borderRadius: BorderRadius.circular(12),
120+
child: Column(
121+
crossAxisAlignment: CrossAxisAlignment.start,
122+
children: [
123+
Expanded(
124+
child: ClipRRect(
125+
borderRadius: const BorderRadius.vertical(
126+
top: Radius.circular(12),
127+
),
128+
child: favorite.posterPath.isNotEmpty
129+
? Image.network(
130+
'$IMAGE_URL${favorite.posterPath}',
131+
fit: BoxFit.cover,
132+
width: double.infinity,
133+
errorBuilder: (_, __, ___) =>
134+
_buildPlaceholderImage(),
135+
)
136+
: _buildPlaceholderImage(),
137+
),
138+
),
139+
Padding(
140+
padding: const EdgeInsets.all(8),
141+
child: Column(
142+
crossAxisAlignment: CrossAxisAlignment.start,
143+
children: [
144+
Text(
145+
favorite.title,
146+
maxLines: 2,
147+
overflow: TextOverflow.ellipsis,
148+
style: const TextStyle(
149+
fontWeight: FontWeight.bold,
150+
fontSize: 14,
151+
),
152+
),
153+
const SizedBox(height: 4),
154+
Row(
155+
mainAxisAlignment:
156+
MainAxisAlignment.spaceBetween,
157+
children: [
158+
Text(
159+
favorite.type.toUpperCase(),
160+
style: TextStyle(
161+
color: Colors.grey[600],
162+
fontSize: 10,
163+
),
164+
),
165+
IconButton(
166+
icon: const Icon(
167+
Icons.favorite,
168+
color: Colors.red,
169+
size: 18,
170+
),
171+
padding: EdgeInsets.zero,
172+
onPressed: () async {
173+
await HiveHelper.deleteFavorite(
174+
favorite.itemId, favorite.type);
175+
_refreshFavorites();
176+
},
177+
),
178+
],
179+
),
180+
],
181+
),
182+
),
183+
],
184+
),
185+
),
186+
);
187+
},
188+
),
189+
);
190+
},
191+
),
192+
bottomNavigationBar: BottomNavigationBar(
193+
currentIndex: _currentIndex,
194+
onTap: (index) {
195+
setState(() {
196+
_currentIndex = index;
197+
_loadFavorites();
198+
});
199+
},
200+
items: const [
201+
BottomNavigationBarItem(
202+
icon: Icon(Icons.movie),
203+
label: 'Movies',
204+
),
205+
BottomNavigationBarItem(
206+
icon: Icon(Icons.tv),
207+
label: 'TV Series',
208+
),
209+
BottomNavigationBarItem(
210+
icon: Icon(Icons.people),
211+
label: 'Celebrities',
212+
),
213+
],
214+
),
215+
);
216+
}
217+
218+
Widget _buildPlaceholderImage() {
219+
return Container(
220+
width: double.infinity,
221+
height: double.infinity,
222+
color: Colors.grey[300],
223+
child: const Icon(Icons.image, color: Colors.grey),
224+
);
225+
}
226+
}

lib/features/movie/data/models/credit_model.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ class Cast with _$Cast {
3030
String? character,
3131
@JsonKey(name: 'credit_id') String? creditId,
3232
int? order,
33+
@JsonKey(name: 'media_type') String? mediaType,
34+
@JsonKey(name: 'first_air_date') String? firstAirDate,
3335
}) = _Cast;
3436

3537
factory Cast.fromJson(Map<String, dynamic> json) => _$CastFromJson(json);

0 commit comments

Comments
 (0)