Skip to content

Commit db71b3c

Browse files
committed
Peanmbahan fitur add/remove to/from Favorite Meal di UI Search Meals
1 parent eee538d commit db71b3c

File tree

4 files changed

+182
-94
lines changed

4 files changed

+182
-94
lines changed

lib/src/blocs/searchmeals/search_meals_bloc.dart

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1+
import 'package:food_recipe/src/database/entity/favorite_meal.dart';
2+
import 'package:food_recipe/src/database/repository/favorite_meal_repository.dart';
3+
import 'package:food_recipe/src/models/lookupmealsbyid/lookup_meals_by_id.dart';
14
import 'package:food_recipe/src/models/searchmeals/search_meals.dart';
25
import 'package:food_recipe/src/resources/food_api_repository.dart';
36
import 'package:rxdart/rxdart.dart';
47

58
class SearchMealsBloc {
6-
final _repository = FoodApiRepository();
79
// ignore: close_sinks
810
final _publishSubjectSearchMealsByKeyword = PublishSubject<SearchMeals>();
11+
final _foodApiRepository = FoodApiRepository();
12+
final _favoriteMealRepository = FavoriteMealRepository();
913

1014
dispose() {
1115
/*_publishSubjectSearchMealsByKeyword.close();*/
@@ -21,10 +25,37 @@ class SearchMealsBloc {
2125
.add(SearchMeals(searchMealsItems: []));
2226
} else {
2327
SearchMeals searchMeals =
24-
await _repository.getSearchMealsByKeyword(keyword);
28+
await _foodApiRepository.getSearchMealsByKeyword(keyword);
29+
List<FavoriteMeal> listFavoriteMeals =
30+
await _favoriteMealRepository.getAllFavoriteMeals();
31+
List<SearchMealsItem> listSearchMealsItem =
32+
searchMeals.searchMealsItems.where((searchMealsItem) {
33+
for (FavoriteMeal favoriteMeal in listFavoriteMeals) {
34+
if (favoriteMeal.idMeal == searchMealsItem.idMeal) {
35+
searchMealsItem.isFavorite = true;
36+
break;
37+
}
38+
}
39+
return true;
40+
}).toList();
41+
searchMeals.searchMealsItems = listSearchMealsItem;
2542
_publishSubjectSearchMealsByKeyword.sink.add(searchMeals);
2643
}
2744
}
45+
46+
Future<LookupMealsById> getDetailMealById(String id) async {
47+
LookupMealsById lookupMealsById =
48+
await _foodApiRepository.getLookupMealsById(id);
49+
return lookupMealsById;
50+
}
51+
52+
Future<int> addFavoriteMeal(FavoriteMeal favoriteMeal) async {
53+
return await _favoriteMealRepository.insertFavoriteMeal(favoriteMeal);
54+
}
55+
56+
Future<int> deleteFavoriteMealById(String id) async {
57+
return await _favoriteMealRepository.deleteFavoriteMealById(id);
58+
}
2859
}
2960

3061
final searchMealsBloc = SearchMealsBloc();

lib/src/models/searchmeals/search_meals.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ class SearchMealsItem {
7474
String strMeasure20;
7575
String strSource;
7676
String dateModified;
77+
@JsonKey(ignore: true)
78+
bool isFavorite;
7779

7880
SearchMealsItem({this.idMeal, this.strMeal, this.strDrinkAlternate,
7981
this.strCategory, this.strArea, this.strInstructions, this.strMealThumb,
@@ -89,7 +91,7 @@ class SearchMealsItem {
8991
this.strMeasure9, this.strMeasure10, this.strMeasure11, this.strMeasure12,
9092
this.strMeasure13, this.strMeasure14, this.strMeasure15,
9193
this.strMeasure16, this.strMeasure17, this.strMeasure18,
92-
this.strMeasure19, this.strMeasure20, this.strSource, this.dateModified});
94+
this.strMeasure19, this.strMeasure20, this.strSource, this.dateModified, this.isFavorite = false});
9395

9496
@override
9597
String toString() {

lib/src/ui/listmeals/list_meals_screen.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,6 @@ class _CardMealState extends State<CardMeal> {
183183
),
184184
GestureDetector(
185185
onTap: () {
186-
print("tap favorite");
187186
var isFavorite = widget.filterCategoryItem.isFavorite;
188187
if (isFavorite) {
189188
listMealsBloc

lib/src/ui/searchmeals/search_meals_screen.dart

Lines changed: 146 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import 'package:flutter/material.dart';
22
import 'package:food_recipe/src/blocs/searchmeals/search_meals_bloc.dart';
3+
import 'package:food_recipe/src/database/entity/favorite_meal.dart';
4+
import 'package:food_recipe/src/models/lookupmealsbyid/lookup_meals_by_id.dart';
35
import 'package:food_recipe/src/models/searchmeals/search_meals.dart';
46
import 'package:food_recipe/src/ui/detailmeals/detail_meals_screen.dart';
57
import 'package:food_recipe/src/utils/utils.dart';
@@ -11,7 +13,6 @@ class SearchMealsScreen extends StatefulWidget {
1113
}
1214

1315
class _SearchMealsScreenState extends State<SearchMealsScreen> {
14-
1516
@override
1617
void dispose() {
1718
searchMealsBloc.dispose();
@@ -68,102 +69,16 @@ class _SearchMealsScreenState extends State<SearchMealsScreen> {
6869
SearchMeals searchMeals = snapshot.data;
6970
if (searchMeals.isLoading) {
7071
return Center(child: buildCircularProgressIndicator());
71-
} else if (searchMeals.searchMealsItems == null || searchMeals.searchMealsItems.isEmpty) {
72+
} else if (searchMeals.searchMealsItems == null ||
73+
searchMeals.searchMealsItems.isEmpty) {
7274
return Container();
7375
}
7476
return ListView.builder(
7577
shrinkWrap: true,
7678
itemCount: searchMeals.searchMealsItems.length,
7779
itemBuilder: (context, index) {
7880
var searchMealsItem = searchMeals.searchMealsItems[index];
79-
return Padding(
80-
padding: EdgeInsets.only(bottom: 16.0),
81-
child: GestureDetector(
82-
onTap: () {
83-
Navigator.push(
84-
context,
85-
MaterialPageRoute(
86-
builder: (context) {
87-
return DetailMealsScreen(
88-
searchMealsItem.idMeal,
89-
searchMealsItem.strMeal,
90-
searchMealsItem.strMealThumb,
91-
);
92-
},
93-
),
94-
);
95-
},
96-
child: Card(
97-
elevation: 8.0,
98-
shape: RoundedRectangleBorder(
99-
borderRadius: BorderRadius.circular(16.0),
100-
),
101-
child: ClipRRect(
102-
borderRadius: BorderRadius.circular(16.0),
103-
child: Stack(
104-
children: <Widget>[
105-
Hero(
106-
tag: "image_detail_meals_${searchMealsItem.idMeal}",
107-
child: FadeInImage(
108-
image: NetworkImage(searchMealsItem.strMealThumb),
109-
placeholder: AssetImage(
110-
"assets/images/img_placeholder.jpg"),
111-
fit: BoxFit.cover,
112-
width: double.infinity,
113-
height: mediaQuery.size.width / 1.5,
114-
),
115-
),
116-
Container(
117-
width: double.infinity,
118-
height: mediaQuery.size.width / 1.5,
119-
decoration: BoxDecoration(
120-
gradient: LinearGradient(
121-
begin: Alignment.topCenter,
122-
end: Alignment.bottomCenter,
123-
stops: [
124-
0.1,
125-
0.9
126-
],
127-
colors: [
128-
Color(0xFFFFFFFF),
129-
Color(0x00FFFFFF),
130-
]),
131-
),
132-
),
133-
Padding(
134-
padding: EdgeInsets.all(16.0),
135-
child: Row(
136-
crossAxisAlignment: CrossAxisAlignment.start,
137-
children: <Widget>[
138-
Expanded(
139-
child: Text(
140-
searchMealsItem.strMeal,
141-
style: Theme.of(context).textTheme.title,
142-
maxLines: 2,
143-
),
144-
),
145-
GestureDetector(
146-
onTap: () {
147-
// TODO: do something in here
148-
print("tap favorite");
149-
},
150-
child: CircleAvatar(
151-
backgroundColor: Color(0xAFE8364B),
152-
child: Icon(
153-
Icons.favorite_border,
154-
color: Colors.white,
155-
),
156-
),
157-
),
158-
],
159-
),
160-
),
161-
],
162-
),
163-
),
164-
),
165-
),
166-
);
81+
return CardMeal(searchMealsItem);
16782
},
16883
);
16984
} else if (snapshot.hasError) {
@@ -208,6 +123,147 @@ class _SearchMealsScreenState extends State<SearchMealsScreen> {
208123
}
209124
}
210125

126+
class CardMeal extends StatefulWidget {
127+
final SearchMealsItem searchMealsItem;
128+
129+
CardMeal(this.searchMealsItem);
130+
131+
@override
132+
_CardMealState createState() => _CardMealState();
133+
}
134+
135+
class _CardMealState extends State<CardMeal> {
136+
@override
137+
Widget build(BuildContext context) {
138+
var mediaQuery = MediaQuery.of(context);
139+
return Padding(
140+
padding: EdgeInsets.only(bottom: 16.0),
141+
child: GestureDetector(
142+
onTap: () {
143+
Navigator.push(
144+
context,
145+
MaterialPageRoute(
146+
builder: (context) {
147+
return DetailMealsScreen(
148+
widget.searchMealsItem.idMeal,
149+
widget.searchMealsItem.strMeal,
150+
widget.searchMealsItem.strMealThumb,
151+
);
152+
},
153+
),
154+
);
155+
},
156+
child: Card(
157+
elevation: 8.0,
158+
shape: RoundedRectangleBorder(
159+
borderRadius: BorderRadius.circular(16.0),
160+
),
161+
child: ClipRRect(
162+
borderRadius: BorderRadius.circular(16.0),
163+
child: Stack(
164+
children: <Widget>[
165+
Hero(
166+
tag: "image_detail_meals_${widget.searchMealsItem.idMeal}",
167+
child: FadeInImage(
168+
image: NetworkImage(widget.searchMealsItem.strMealThumb),
169+
placeholder:
170+
AssetImage("assets/images/img_placeholder.jpg"),
171+
fit: BoxFit.cover,
172+
width: double.infinity,
173+
height: mediaQuery.size.width / 1.5,
174+
),
175+
),
176+
Container(
177+
width: double.infinity,
178+
height: mediaQuery.size.width / 1.5,
179+
decoration: BoxDecoration(
180+
gradient: LinearGradient(
181+
begin: Alignment.topCenter,
182+
end: Alignment.bottomCenter,
183+
stops: [
184+
0.1,
185+
0.9
186+
],
187+
colors: [
188+
Color(0xFFFFFFFF),
189+
Color(0x00FFFFFF),
190+
]),
191+
),
192+
),
193+
Padding(
194+
padding: EdgeInsets.all(16.0),
195+
child: Row(
196+
crossAxisAlignment: CrossAxisAlignment.start,
197+
children: <Widget>[
198+
Expanded(
199+
child: Text(
200+
widget.searchMealsItem.strMeal,
201+
style: Theme.of(context).textTheme.title,
202+
maxLines: 2,
203+
),
204+
),
205+
GestureDetector(
206+
onTap: () {
207+
var isFavorite = widget.searchMealsItem.isFavorite;
208+
if (isFavorite) {
209+
searchMealsBloc
210+
.deleteFavoriteMealById(
211+
widget.searchMealsItem.idMeal)
212+
.then((status) {
213+
setState(() {
214+
widget.searchMealsItem.isFavorite = !isFavorite;
215+
});
216+
});
217+
} else {
218+
Future<LookupMealsById> lookupMealsById =
219+
searchMealsBloc.getDetailMealById(
220+
widget.searchMealsItem.idMeal);
221+
lookupMealsById.then((value) {
222+
if (value != null) {
223+
var item = value.lookupMealsbyIdItems[0];
224+
FavoriteMeal favoriteMeal =
225+
FavoriteMeal.fromJson(item.toJson());
226+
searchMealsBloc
227+
.addFavoriteMeal(favoriteMeal)
228+
.then((status) {
229+
setState(() {
230+
widget.searchMealsItem.isFavorite =
231+
!isFavorite;
232+
});
233+
});
234+
} else {
235+
Scaffold.of(context).showSnackBar(SnackBar(
236+
content:
237+
Text("Failed added to favorite meal")));
238+
}
239+
});
240+
}
241+
},
242+
child: CircleAvatar(
243+
backgroundColor: Color(0xAFE8364B),
244+
child: widget.searchMealsItem.isFavorite
245+
? Icon(
246+
Icons.favorite,
247+
color: Colors.white,
248+
)
249+
: Icon(
250+
Icons.favorite_border,
251+
color: Colors.white,
252+
),
253+
),
254+
),
255+
],
256+
),
257+
),
258+
],
259+
),
260+
),
261+
),
262+
),
263+
);
264+
}
265+
}
266+
211267
class TextFieldSearch extends StatefulWidget {
212268
@override
213269
_TextFieldSearchState createState() => _TextFieldSearchState();

0 commit comments

Comments
 (0)