diff --git a/android/gradle.properties b/android/gradle.properties index 53ae0ae..b6e61b6 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,4 @@ android.enableJetifier=true android.useAndroidX=true org.gradle.jvmargs=-Xmx1536M +android.enableR8=true diff --git a/assets/images/img_beef.jpg b/assets/images/img_beef.jpg new file mode 100644 index 0000000..dc82df1 Binary files /dev/null and b/assets/images/img_beef.jpg differ diff --git a/assets/images/img_breakfast.jpg b/assets/images/img_breakfast.jpg new file mode 100644 index 0000000..a579074 Binary files /dev/null and b/assets/images/img_breakfast.jpg differ diff --git a/assets/images/img_chicken.jpg b/assets/images/img_chicken.jpg new file mode 100644 index 0000000..62d174b Binary files /dev/null and b/assets/images/img_chicken.jpg differ diff --git a/assets/images/img_dessert.jpg b/assets/images/img_dessert.jpg new file mode 100644 index 0000000..cafc249 Binary files /dev/null and b/assets/images/img_dessert.jpg differ diff --git a/assets/images/img_goat.jpg b/assets/images/img_goat.jpg new file mode 100644 index 0000000..c9e51d5 Binary files /dev/null and b/assets/images/img_goat.jpg differ diff --git a/assets/images/img_lamb.jpg b/assets/images/img_lamb.jpg new file mode 100644 index 0000000..90d06f9 Binary files /dev/null and b/assets/images/img_lamb.jpg differ diff --git a/assets/images/img_miscellaneous.jpg b/assets/images/img_miscellaneous.jpg new file mode 100644 index 0000000..2412298 Binary files /dev/null and b/assets/images/img_miscellaneous.jpg differ diff --git a/assets/images/img_pasta.jpg b/assets/images/img_pasta.jpg new file mode 100644 index 0000000..6a54ab8 Binary files /dev/null and b/assets/images/img_pasta.jpg differ diff --git a/assets/images/img_pork.jpg b/assets/images/img_pork.jpg new file mode 100644 index 0000000..52d7bee Binary files /dev/null and b/assets/images/img_pork.jpg differ diff --git a/assets/images/img_seafood.jpg b/assets/images/img_seafood.jpg new file mode 100644 index 0000000..5535425 Binary files /dev/null and b/assets/images/img_seafood.jpg differ diff --git a/assets/images/img_side.jpg b/assets/images/img_side.jpg new file mode 100644 index 0000000..048d57b Binary files /dev/null and b/assets/images/img_side.jpg differ diff --git a/assets/images/img_starter.jpg b/assets/images/img_starter.jpg new file mode 100644 index 0000000..f7a6f56 Binary files /dev/null and b/assets/images/img_starter.jpg differ diff --git a/assets/images/img_vegan.jpg b/assets/images/img_vegan.jpg new file mode 100644 index 0000000..24fea6e Binary files /dev/null and b/assets/images/img_vegan.jpg differ diff --git a/assets/images/img_vegetarian.jpg b/assets/images/img_vegetarian.jpg new file mode 100644 index 0000000..e55e4f0 Binary files /dev/null and b/assets/images/img_vegetarian.jpg differ diff --git a/design/cover google play.sketch b/design/cover google play.sketch deleted file mode 100644 index 2c65a36..0000000 Binary files a/design/cover google play.sketch and /dev/null differ diff --git a/design/food recipe 1.png b/design/food recipe 1.png deleted file mode 100644 index 12eaebe..0000000 Binary files a/design/food recipe 1.png and /dev/null differ diff --git a/design/food recipe 2.png b/design/food recipe 2.png deleted file mode 100644 index 1112c5b..0000000 Binary files a/design/food recipe 2.png and /dev/null differ diff --git a/design/food recipe 3.png b/design/food recipe 3.png deleted file mode 100644 index 3817b78..0000000 Binary files a/design/food recipe 3.png and /dev/null differ diff --git a/design/food recipe 4.png b/design/food recipe 4.png deleted file mode 100644 index 6fb4efa..0000000 Binary files a/design/food recipe 4.png and /dev/null differ diff --git a/design/food recipe cover 1.png b/design/food recipe cover 1.png deleted file mode 100644 index d20d4e1..0000000 Binary files a/design/food recipe cover 1.png and /dev/null differ diff --git a/design/food recipe cover 2.png b/design/food recipe cover 2.png deleted file mode 100644 index e839f2d..0000000 Binary files a/design/food recipe cover 2.png and /dev/null differ diff --git a/design/food recipe cover 3.png b/design/food recipe cover 3.png deleted file mode 100644 index 72eab1d..0000000 Binary files a/design/food recipe cover 3.png and /dev/null differ diff --git a/design/food recipe cover 4.png b/design/food recipe cover 4.png deleted file mode 100644 index 7ced46b..0000000 Binary files a/design/food recipe cover 4.png and /dev/null differ diff --git a/design/food recipe cover 5.png b/design/food recipe cover 5.png deleted file mode 100644 index e3bdb18..0000000 Binary files a/design/food recipe cover 5.png and /dev/null differ diff --git a/design/ic_launcher/res/mipmap-hdpi/ic_launcher.png b/design/ic_launcher/res/mipmap-hdpi/ic_launcher.png deleted file mode 100755 index 45ac9f4..0000000 Binary files a/design/ic_launcher/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/design/ic_launcher/res/mipmap-mdpi/ic_launcher.png b/design/ic_launcher/res/mipmap-mdpi/ic_launcher.png deleted file mode 100755 index 91295e1..0000000 Binary files a/design/ic_launcher/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/design/ic_launcher/res/mipmap-xhdpi/ic_launcher.png b/design/ic_launcher/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100755 index 2c62945..0000000 Binary files a/design/ic_launcher/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/design/ic_launcher/res/mipmap-xxhdpi/ic_launcher.png b/design/ic_launcher/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100755 index d0151e6..0000000 Binary files a/design/ic_launcher/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/design/ic_launcher/res/mipmap-xxxhdpi/ic_launcher.png b/design/ic_launcher/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100755 index 22db27a..0000000 Binary files a/design/ic_launcher/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/design/ic_launcher/web_hi_res_1024.png b/design/ic_launcher/web_hi_res_1024.png deleted file mode 100644 index 54a34e2..0000000 Binary files a/design/ic_launcher/web_hi_res_1024.png and /dev/null differ diff --git a/design/ic_launcher/web_hi_res_120.png b/design/ic_launcher/web_hi_res_120.png deleted file mode 100644 index 00aa0f1..0000000 Binary files a/design/ic_launcher/web_hi_res_120.png and /dev/null differ diff --git a/design/ic_launcher/web_hi_res_152.png b/design/ic_launcher/web_hi_res_152.png deleted file mode 100644 index a8310fa..0000000 Binary files a/design/ic_launcher/web_hi_res_152.png and /dev/null differ diff --git a/design/ic_launcher/web_hi_res_167.png b/design/ic_launcher/web_hi_res_167.png deleted file mode 100644 index 4ed350c..0000000 Binary files a/design/ic_launcher/web_hi_res_167.png and /dev/null differ diff --git a/design/ic_launcher/web_hi_res_180.png b/design/ic_launcher/web_hi_res_180.png deleted file mode 100644 index c4f175e..0000000 Binary files a/design/ic_launcher/web_hi_res_180.png and /dev/null differ diff --git a/design/ic_launcher/web_hi_res_20.png b/design/ic_launcher/web_hi_res_20.png deleted file mode 100644 index 258ab00..0000000 Binary files a/design/ic_launcher/web_hi_res_20.png and /dev/null differ diff --git a/design/ic_launcher/web_hi_res_29.png b/design/ic_launcher/web_hi_res_29.png deleted file mode 100644 index f1b6836..0000000 Binary files a/design/ic_launcher/web_hi_res_29.png and /dev/null differ diff --git a/design/ic_launcher/web_hi_res_40.png b/design/ic_launcher/web_hi_res_40.png deleted file mode 100644 index 9235450..0000000 Binary files a/design/ic_launcher/web_hi_res_40.png and /dev/null differ diff --git a/design/ic_launcher/web_hi_res_512.png b/design/ic_launcher/web_hi_res_512.png deleted file mode 100755 index aee34c6..0000000 Binary files a/design/ic_launcher/web_hi_res_512.png and /dev/null differ diff --git a/design/ic_launcher/web_hi_res_58.png b/design/ic_launcher/web_hi_res_58.png deleted file mode 100644 index cf65578..0000000 Binary files a/design/ic_launcher/web_hi_res_58.png and /dev/null differ diff --git a/design/ic_launcher/web_hi_res_60.png b/design/ic_launcher/web_hi_res_60.png deleted file mode 100644 index 368289b..0000000 Binary files a/design/ic_launcher/web_hi_res_60.png and /dev/null differ diff --git a/design/ic_launcher/web_hi_res_76.png b/design/ic_launcher/web_hi_res_76.png deleted file mode 100644 index 7e6b171..0000000 Binary files a/design/ic_launcher/web_hi_res_76.png and /dev/null differ diff --git a/design/ic_launcher/web_hi_res_80.png b/design/ic_launcher/web_hi_res_80.png deleted file mode 100644 index da9e847..0000000 Binary files a/design/ic_launcher/web_hi_res_80.png and /dev/null differ diff --git a/design/ic_launcher/web_hi_res_87.png b/design/ic_launcher/web_hi_res_87.png deleted file mode 100644 index 13b9b5a..0000000 Binary files a/design/ic_launcher/web_hi_res_87.png and /dev/null differ diff --git a/lib/app.dart b/lib/app.dart new file mode 100644 index 0000000..717c288 --- /dev/null +++ b/lib/app.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'feature/presentation/page/home/home_page.dart'; + +class App extends StatefulWidget { + @override + _AppState createState() => _AppState(); +} + +class _AppState extends State { + @override + void initState() { + WidgetsBinding.instance.addPostFrameCallback((_) async { + await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + theme: ThemeData( + primaryColor: Color(0xFFE8364B), + accentColor: Color(0xFFEE8F3B), + ), + title: 'Food Recipe', + home: HomePage(), + builder: (context, child) { + return MediaQuery( + child: child, + data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0), + ); + }, + ); + } +} diff --git a/lib/config/base_url_config.dart b/lib/config/base_url_config.dart new file mode 100644 index 0000000..dc3e1ac --- /dev/null +++ b/lib/config/base_url_config.dart @@ -0,0 +1,3 @@ +class BaseUrlConfig { + final baseUrlMealDb = 'https://www.themealdb.com/api/json/v1/1'; +} \ No newline at end of file diff --git a/lib/config/build_config.dart b/lib/config/build_config.dart new file mode 100644 index 0000000..8eb41a9 --- /dev/null +++ b/lib/config/build_config.dart @@ -0,0 +1,31 @@ +import 'package:device_info/device_info.dart'; + +enum BuildMode { + DEBUG, + PROFILE, + RELEASE, +} + +class BuildConfig { + static BuildMode currentBuildMode() { + if (const bool.fromEnvironment('dart.vm.product')) { + return BuildMode.RELEASE; + } + var result = BuildMode.PROFILE; + assert(() { + result = BuildMode.DEBUG; + return true; + }()); + return result; + } + + static Future androidDeviceInfo() async { + var deviceInfoPlugin = DeviceInfoPlugin(); + return deviceInfoPlugin.androidInfo; + } + + static Future iosDeviceInfo() async { + var deviceInfoPlugin = DeviceInfoPlugin(); + return deviceInfoPlugin.iosInfo; + } +} \ No newline at end of file diff --git a/lib/core/error/exception.dart b/lib/core/error/exception.dart new file mode 100644 index 0000000..77545f8 --- /dev/null +++ b/lib/core/error/exception.dart @@ -0,0 +1 @@ +class Connection implements Exception {} \ No newline at end of file diff --git a/lib/core/error/failure.dart b/lib/core/error/failure.dart new file mode 100644 index 0000000..efda7f4 --- /dev/null +++ b/lib/core/error/failure.dart @@ -0,0 +1,30 @@ +import 'package:equatable/equatable.dart'; +import 'package:food_recipe/core/util/constant_error_message.dart'; + +abstract class Failure extends Equatable {} + +class ServerFailure extends Failure { + final String errorMessage; + + ServerFailure(this.errorMessage); + + @override + List get props => [errorMessage]; + + @override + String toString() { + return 'ServerFailure{errorMessage: $errorMessage}'; + } +} + +class ConnectionFailure extends Failure { + final String errorMessage = ConstantErrorMessage().connectionError; + + @override + List get props => [errorMessage]; + + @override + String toString() { + return 'ConnectionFailure{errorMessage: $errorMessage}'; + } +} \ No newline at end of file diff --git a/lib/core/network/network_info.dart b/lib/core/network/network_info.dart new file mode 100644 index 0000000..f55fb4d --- /dev/null +++ b/lib/core/network/network_info.dart @@ -0,0 +1,14 @@ +import 'package:data_connection_checker/data_connection_checker.dart'; + +abstract class NetworkInfo { + Future get isConnected; +} + +class NetworkInfoImpl implements NetworkInfo { + final DataConnectionChecker dataConnectionChecker; + + NetworkInfoImpl(this.dataConnectionChecker); + + @override + Future get isConnected => dataConnectionChecker.hasConnection; +} \ No newline at end of file diff --git a/lib/core/usecase/usecase.dart b/lib/core/usecase/usecase.dart new file mode 100644 index 0000000..570803e --- /dev/null +++ b/lib/core/usecase/usecase.dart @@ -0,0 +1,12 @@ +import 'package:dartz/dartz.dart'; +import 'package:equatable/equatable.dart'; +import 'package:food_recipe/core/error/failure.dart'; + +abstract class UseCase { + Future> call(Params params); +} + +class NoParams extends Equatable { + @override + List get props => []; +} \ No newline at end of file diff --git a/lib/core/util/constant_error_message.dart b/lib/core/util/constant_error_message.dart new file mode 100644 index 0000000..d4b8ea0 --- /dev/null +++ b/lib/core/util/constant_error_message.dart @@ -0,0 +1,3 @@ +class ConstantErrorMessage { + final connectionError = 'connectionError'; +} \ No newline at end of file diff --git a/lib/feature/data/datasource/themealdb/themealdb_remote_data_source.dart b/lib/feature/data/datasource/themealdb/themealdb_remote_data_source.dart new file mode 100644 index 0000000..3451fa3 --- /dev/null +++ b/lib/feature/data/datasource/themealdb/themealdb_remote_data_source.dart @@ -0,0 +1,122 @@ +import 'package:dio/dio.dart'; +import 'package:food_recipe/feature/data/model/detailmeal/detail_meal_response.dart'; +import 'package:food_recipe/feature/data/model/filterbycategory/filter_by_category_response.dart'; +import 'package:food_recipe/feature/data/model/mealcategory/meal_category_response.dart'; +import 'package:food_recipe/feature/data/model/searchmealbyname/search_meal_by_name_response.dart'; + +abstract class TheMealDbRemoteDataSource { + Future getRandomMeal(); + + Future getCategoryMeal(); + + Future getFilterByCategory(String category); + + Future searchMealByName(String name); + + Future getDetailMealById(String id); +} + +class TheMealDbRemoteDataSourceImpl implements TheMealDbRemoteDataSource { + final Dio dio; + + TheMealDbRemoteDataSourceImpl({this.dio}); + + @override + Future getRandomMeal() async { + var response = await dio.get( + '/random.php', + ); + if (response.statusCode == 200) { + Map responseData = response.data; + var detailMealResponse = convertJsonToDetailMealResponse(responseData); + return detailMealResponse; + } else { + throw DioError(); + } + } + + DetailMealResponse convertJsonToDetailMealResponse(Map responseData) { + var index = 1; + var listIngredients = []; + List responseMeals = responseData['meals']; + responseMeals[0].forEach((key, value) { + if (key.contains('strIngredient') && value.toString().isNotEmpty) { + var ingredient = value; + var measure = responseMeals[0]['strMeasure$index']; + listIngredients.add('$ingredient $measure'); + index++; + } + }); + return DetailMealResponse( + idMeal: responseMeals[0]['idMeal'], + strMeal: responseMeals[0]['strMeal'], + strCategory: responseMeals[0]['strCategory'], + strArea: responseMeals[0]['strArea'], + strInstructions: responseMeals[0]['strInstructions'], + strMealThumb: responseMeals[0]['strMealThumb'], + strTags: responseMeals[0]['strTags'], + strYoutube: responseMeals[0]['strYoutube'], + strSource: responseMeals[0]['strSource'], + listIngredients: listIngredients, + ); + } + + @override + Future getCategoryMeal() async { + var response = await dio.get( + '/categories.php', + ); + if (response.statusCode == 200) { + return MealCategoryResponse.fromJson(response.data); + } else { + throw DioError(); + } + } + + @override + Future getFilterByCategory(String category) async { + var response = await dio.get( + '/filter.php', + queryParameters: { + 'c': category, + }, + ); + if (response.statusCode == 200) { + return FilterByCategoryResponse.fromJson(response.data); + } else { + throw DioError(); + } + } + + @override + Future searchMealByName(String name) async { + var response = await dio.get( + '/search.php', + queryParameters: { + 's': name, + }, + ); + if (response.statusCode == 200) { + return SearchMealByNameResponse.fromJson(response.data); + } else { + throw DioError(); + } + } + + @override + Future getDetailMealById(String id) async { + var response = await dio.get( + '/lookup.php', + queryParameters: { + 'i': id, + }, + ); + if (response.statusCode == 200) { + Map responseData = response.data; + var detailMealResponse = convertJsonToDetailMealResponse(responseData); + return detailMealResponse; + } else { + throw DioError(); + } + } +} diff --git a/lib/feature/data/model/detailmeal/detail_meal_response.dart b/lib/feature/data/model/detailmeal/detail_meal_response.dart new file mode 100644 index 0000000..b911519 --- /dev/null +++ b/lib/feature/data/model/detailmeal/detail_meal_response.dart @@ -0,0 +1,66 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'detail_meal_response.g.dart'; + +@JsonSerializable() +class DetailMealResponse extends Equatable { + @JsonKey(name: 'idMeal') + final String idMeal; + @JsonKey(name: 'strMeal') + final String strMeal; + @JsonKey(name: 'strCategory') + final String strCategory; + @JsonKey(name: 'strArea') + final String strArea; + @JsonKey(name: 'strInstructions') + final String strInstructions; + @JsonKey(name: 'strMealThumb') + final String strMealThumb; + @JsonKey(name: 'strTags') + final String strTags; + @JsonKey(name: 'strYoutube') + final String strYoutube; + final List listIngredients; + @JsonKey(name: 'strSource') + final String strSource; + + DetailMealResponse({ + @required this.idMeal, + @required this.strMeal, + @required this.strCategory, + @required this.strArea, + @required this.strInstructions, + @required this.strMealThumb, + @required this.strTags, + @required this.strYoutube, + @required this.listIngredients, + @required this.strSource, + }); + + factory DetailMealResponse.fromJson(Map json) => _$DetailMealResponseFromJson(json); + + Map toJson() => _$DetailMealResponseToJson(this); + + @override + List get props => [ + idMeal, + strMeal, + strCategory, + strArea, + strInstructions, + strMealThumb, + strTags, + strYoutube, + listIngredients, + strSource, + ]; + + @override + String toString() { + return 'DetailMealResponse{idMeal: $idMeal, strMeal: $strMeal, strCategory: $strCategory, strArea: $strArea, ' + 'strInstructions: $strInstructions, strMealThumb: $strMealThumb, strTags: $strTags, strYoutube: $strYoutube, ' + 'listIngredients: $listIngredients, strSource: $strSource}'; + } +} diff --git a/lib/feature/data/model/detailmeal/detail_meal_response.g.dart b/lib/feature/data/model/detailmeal/detail_meal_response.g.dart new file mode 100644 index 0000000..77f785a --- /dev/null +++ b/lib/feature/data/model/detailmeal/detail_meal_response.g.dart @@ -0,0 +1,37 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'detail_meal_response.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +DetailMealResponse _$DetailMealResponseFromJson(Map json) { + return DetailMealResponse( + idMeal: json['idMeal'] as String, + strMeal: json['strMeal'] as String, + strCategory: json['strCategory'] as String, + strArea: json['strArea'] as String, + strInstructions: json['strInstructions'] as String, + strMealThumb: json['strMealThumb'] as String, + strTags: json['strTags'] as String, + strYoutube: json['strYoutube'] as String, + listIngredients: + (json['listIngredients'] as List)?.map((e) => e as String)?.toList(), + strSource: json['strSource'] as String, + ); +} + +Map _$DetailMealResponseToJson(DetailMealResponse instance) => + { + 'idMeal': instance.idMeal, + 'strMeal': instance.strMeal, + 'strCategory': instance.strCategory, + 'strArea': instance.strArea, + 'strInstructions': instance.strInstructions, + 'strMealThumb': instance.strMealThumb, + 'strTags': instance.strTags, + 'strYoutube': instance.strYoutube, + 'listIngredients': instance.listIngredients, + 'strSource': instance.strSource, + }; diff --git a/lib/feature/data/model/filterbycategory/filter_by_category_response.dart b/lib/feature/data/model/filterbycategory/filter_by_category_response.dart new file mode 100644 index 0000000..32fe771 --- /dev/null +++ b/lib/feature/data/model/filterbycategory/filter_by_category_response.dart @@ -0,0 +1,53 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'filter_by_category_response.g.dart'; + +@JsonSerializable() +class FilterByCategoryResponse extends Equatable { + @JsonKey(name: 'meals') + final List meals; + + FilterByCategoryResponse({@required this.meals}); + + factory FilterByCategoryResponse.fromJson(Map json) => _$FilterByCategoryResponseFromJson(json); + + Map toJson() => _$FilterByCategoryResponseToJson(this); + + @override + List get props => [meals]; + + @override + String toString() { + return 'FilterByCategoryResponse{meals: $meals}'; + } +} + +@JsonSerializable() +class ItemFilterByCategory extends Equatable { + @JsonKey(name: 'strMeal') + final String strMeal; + @JsonKey(name: 'strMealThumb') + final String strMealThumb; + @JsonKey(name: 'idMeal') + final String idMeal; + + ItemFilterByCategory({ + @required this.strMeal, + @required this.strMealThumb, + @required this.idMeal, + }); + + factory ItemFilterByCategory.fromJson(Map json) => _$ItemFilterByCategoryFromJson(json); + + Map toJson() => _$ItemFilterByCategoryToJson(this); + + @override + List get props => [strMeal, strMealThumb, idMeal]; + + @override + String toString() { + return 'ItemFilterByCategory{strMeal: $strMeal, strMealThumb: $strMealThumb, idMeal: $idMeal}'; + } +} diff --git a/lib/feature/data/model/filterbycategory/filter_by_category_response.g.dart b/lib/feature/data/model/filterbycategory/filter_by_category_response.g.dart new file mode 100644 index 0000000..f2d227d --- /dev/null +++ b/lib/feature/data/model/filterbycategory/filter_by_category_response.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'filter_by_category_response.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +FilterByCategoryResponse _$FilterByCategoryResponseFromJson( + Map json) { + return FilterByCategoryResponse( + meals: (json['meals'] as List) + ?.map((e) => e == null + ? null + : ItemFilterByCategory.fromJson(e as Map)) + ?.toList(), + ); +} + +Map _$FilterByCategoryResponseToJson( + FilterByCategoryResponse instance) => + { + 'meals': instance.meals, + }; + +ItemFilterByCategory _$ItemFilterByCategoryFromJson(Map json) { + return ItemFilterByCategory( + strMeal: json['strMeal'] as String, + strMealThumb: json['strMealThumb'] as String, + idMeal: json['idMeal'] as String, + ); +} + +Map _$ItemFilterByCategoryToJson( + ItemFilterByCategory instance) => + { + 'strMeal': instance.strMeal, + 'strMealThumb': instance.strMealThumb, + 'idMeal': instance.idMeal, + }; diff --git a/lib/feature/data/model/mealcategory/meal_category_response.dart b/lib/feature/data/model/mealcategory/meal_category_response.dart new file mode 100644 index 0000000..ed721b4 --- /dev/null +++ b/lib/feature/data/model/mealcategory/meal_category_response.dart @@ -0,0 +1,53 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'meal_category_response.g.dart'; + +@JsonSerializable() +class MealCategoryResponse extends Equatable { + @JsonKey(name: 'categories') + final List categories; + + MealCategoryResponse({@required this.categories}); + + factory MealCategoryResponse.fromJson(Map json) => _$MealCategoryResponseFromJson(json); + + Map toJson() => _$MealCategoryResponseToJson(this); + + @override + List get props => [categories]; + + @override + String toString() { + return 'MealCategoryResponse{categories: $categories}'; + } +} + +@JsonSerializable() +class ItemMealCategory extends Equatable { + @JsonKey(name: 'idCategory') + final String idCategory; + @JsonKey(name: 'strCategory') + final String strCategory; + @JsonKey(name: 'strCategoryThumb') + final String strCategoryThumb; + + ItemMealCategory({ + @required this.idCategory, + @required this.strCategory, + @required this.strCategoryThumb, + }); + + factory ItemMealCategory.fromJson(Map json) => _$ItemMealCategoryFromJson(json); + + Map toJson() => _$ItemMealCategoryToJson(this); + + @override + List get props => [idCategory, strCategory, strCategoryThumb]; + + @override + String toString() { + return 'ItemMealCategory{idCategory: $idCategory, strCategory: $strCategory, strCategoryThumb: $strCategoryThumb}'; + } +} diff --git a/lib/feature/data/model/mealcategory/meal_category_response.g.dart b/lib/feature/data/model/mealcategory/meal_category_response.g.dart new file mode 100644 index 0000000..1254e07 --- /dev/null +++ b/lib/feature/data/model/mealcategory/meal_category_response.g.dart @@ -0,0 +1,38 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'meal_category_response.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +MealCategoryResponse _$MealCategoryResponseFromJson(Map json) { + return MealCategoryResponse( + categories: (json['categories'] as List) + ?.map((e) => e == null + ? null + : ItemMealCategory.fromJson(e as Map)) + ?.toList(), + ); +} + +Map _$MealCategoryResponseToJson( + MealCategoryResponse instance) => + { + 'categories': instance.categories, + }; + +ItemMealCategory _$ItemMealCategoryFromJson(Map json) { + return ItemMealCategory( + idCategory: json['idCategory'] as String, + strCategory: json['strCategory'] as String, + strCategoryThumb: json['strCategoryThumb'] as String, + ); +} + +Map _$ItemMealCategoryToJson(ItemMealCategory instance) => + { + 'idCategory': instance.idCategory, + 'strCategory': instance.strCategory, + 'strCategoryThumb': instance.strCategoryThumb, + }; diff --git a/lib/feature/data/model/searchmealbyname/search_meal_by_name_response.dart b/lib/feature/data/model/searchmealbyname/search_meal_by_name_response.dart new file mode 100644 index 0000000..5cabb0f --- /dev/null +++ b/lib/feature/data/model/searchmealbyname/search_meal_by_name_response.dart @@ -0,0 +1,53 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'search_meal_by_name_response.g.dart'; + +@JsonSerializable() +class SearchMealByNameResponse extends Equatable { + @JsonKey(name: 'meals') + final List meals; + + SearchMealByNameResponse({@required this.meals}); + + factory SearchMealByNameResponse.fromJson(Map json) => _$SearchMealByNameResponseFromJson(json); + + Map toJson() => _$SearchMealByNameResponseToJson(this); + + @override + List get props => [meals]; + + @override + String toString() { + return 'SearchMealByNameResponse{meals: $meals}'; + } +} + +@JsonSerializable() +class ItemSearchMeal extends Equatable { + @JsonKey(name: 'idMeal') + final String idMeal; + @JsonKey(name: 'strMeal') + final String strMeal; + @JsonKey(name: 'strMealThumb') + final String strMealThumb; + + ItemSearchMeal({ + @required this.idMeal, + @required this.strMeal, + @required this.strMealThumb, + }); + + factory ItemSearchMeal.fromJson(Map json) => _$ItemSearchMealFromJson(json); + + Map toJson() => _$ItemSearchMealToJson(this); + + @override + List get props => [idMeal, strMeal, strMealThumb]; + + @override + String toString() { + return 'ItemSearchMeal{idMeal: $idMeal, strMeal: $strMeal, strMealThumb: $strMealThumb}'; + } +} diff --git a/lib/feature/data/model/searchmealbyname/search_meal_by_name_response.g.dart b/lib/feature/data/model/searchmealbyname/search_meal_by_name_response.g.dart new file mode 100644 index 0000000..c2e741c --- /dev/null +++ b/lib/feature/data/model/searchmealbyname/search_meal_by_name_response.g.dart @@ -0,0 +1,39 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'search_meal_by_name_response.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +SearchMealByNameResponse _$SearchMealByNameResponseFromJson( + Map json) { + return SearchMealByNameResponse( + meals: (json['meals'] as List) + ?.map((e) => e == null + ? null + : ItemSearchMeal.fromJson(e as Map)) + ?.toList(), + ); +} + +Map _$SearchMealByNameResponseToJson( + SearchMealByNameResponse instance) => + { + 'meals': instance.meals, + }; + +ItemSearchMeal _$ItemSearchMealFromJson(Map json) { + return ItemSearchMeal( + idMeal: json['idMeal'] as String, + strMeal: json['strMeal'] as String, + strMealThumb: json['strMealThumb'] as String, + ); +} + +Map _$ItemSearchMealToJson(ItemSearchMeal instance) => + { + 'idMeal': instance.idMeal, + 'strMeal': instance.strMeal, + 'strMealThumb': instance.strMealThumb, + }; diff --git a/lib/feature/data/repository/themealdb/themealdb_repository_impl.dart b/lib/feature/data/repository/themealdb/themealdb_repository_impl.dart new file mode 100644 index 0000000..0be80cd --- /dev/null +++ b/lib/feature/data/repository/themealdb/themealdb_repository_impl.dart @@ -0,0 +1,95 @@ +import 'package:dartz/dartz.dart'; +import 'package:dio/dio.dart'; +import 'package:food_recipe/core/error/failure.dart'; +import 'package:food_recipe/core/network/network_info.dart'; +import 'package:food_recipe/feature/data/datasource/themealdb/themealdb_remote_data_source.dart'; +import 'package:food_recipe/feature/data/model/detailmeal/detail_meal_response.dart'; +import 'package:food_recipe/feature/data/model/filterbycategory/filter_by_category_response.dart'; +import 'package:food_recipe/feature/data/model/mealcategory/meal_category_response.dart'; +import 'package:food_recipe/feature/data/model/searchmealbyname/search_meal_by_name_response.dart'; +import 'package:food_recipe/feature/domain/repository/themealdb/themealdb_repository.dart'; + +class TheMealDbRepositoryImpl implements TheMealDbRepository { + final TheMealDbRemoteDataSource theMealDbRemoteDataSource; + final NetworkInfo networkInfo; + + TheMealDbRepositoryImpl({ + this.theMealDbRemoteDataSource, + this.networkInfo, + }); + + @override + Future> getRandomMeal() async { + var isConnected = await networkInfo.isConnected; + if (isConnected) { + try { + var response = await theMealDbRemoteDataSource.getRandomMeal(); + return Right(response); + } on DioError catch (error) { + return Left(ServerFailure(error.message)); + } + } else { + return Left(ConnectionFailure()); + } + } + + @override + Future> getCategoryMeal() async { + var isConnected = await networkInfo.isConnected; + if (isConnected) { + try { + var response = await theMealDbRemoteDataSource.getCategoryMeal(); + return Right(response); + } on DioError catch (error) { + return Left(ServerFailure(error.message)); + } + } else { + return Left(ConnectionFailure()); + } + } + + @override + Future> getFilterByCategory(String category) async { + var isConnected = await networkInfo.isConnected; + if (isConnected) { + try { + var response = await theMealDbRemoteDataSource.getFilterByCategory(category); + return Right(response); + } on DioError catch (error) { + return Left(ServerFailure(error.message)); + } + } else { + return Left(ConnectionFailure()); + } + } + + @override + Future> searchMealByName(String name) async { + var isConnected = await networkInfo.isConnected; + if (isConnected) { + try { + var response = await theMealDbRemoteDataSource.searchMealByName(name); + return Right(response); + } on DioError catch (error) { + return Left(ServerFailure(error.message)); + } + } else { + return Left(ConnectionFailure()); + } + } + + @override + Future> getDetailMealById(String id) async { + var isConnected = await networkInfo.isConnected; + if (isConnected) { + try { + var response = await theMealDbRemoteDataSource.getDetailMealById(id); + return Right(response); + } on DioError catch (error) { + return Left(ServerFailure(error.message)); + } + } else { + return Left(ConnectionFailure()); + } + } +} diff --git a/lib/feature/domain/repository/themealdb/themealdb_repository.dart b/lib/feature/domain/repository/themealdb/themealdb_repository.dart new file mode 100644 index 0000000..40a8cc4 --- /dev/null +++ b/lib/feature/domain/repository/themealdb/themealdb_repository.dart @@ -0,0 +1,18 @@ +import 'package:dartz/dartz.dart'; +import 'package:food_recipe/core/error/failure.dart'; +import 'package:food_recipe/feature/data/model/detailmeal/detail_meal_response.dart'; +import 'package:food_recipe/feature/data/model/filterbycategory/filter_by_category_response.dart'; +import 'package:food_recipe/feature/data/model/mealcategory/meal_category_response.dart'; +import 'package:food_recipe/feature/data/model/searchmealbyname/search_meal_by_name_response.dart'; + +abstract class TheMealDbRepository { + Future> getRandomMeal(); + + Future> getCategoryMeal(); + + Future> getFilterByCategory(String category); + + Future> searchMealByName(String name); + + Future> getDetailMealById(String id); +} \ No newline at end of file diff --git a/lib/feature/domain/usecase/getcategorymeal/get_category_meal.dart b/lib/feature/domain/usecase/getcategorymeal/get_category_meal.dart new file mode 100644 index 0000000..1480aff --- /dev/null +++ b/lib/feature/domain/usecase/getcategorymeal/get_category_meal.dart @@ -0,0 +1,17 @@ +import 'package:dartz/dartz.dart'; +import 'package:meta/meta.dart'; +import 'package:food_recipe/core/error/failure.dart'; +import 'package:food_recipe/core/usecase/usecase.dart'; +import 'package:food_recipe/feature/data/model/mealcategory/meal_category_response.dart'; +import 'package:food_recipe/feature/domain/repository/themealdb/themealdb_repository.dart'; + +class GetCategoryMeal implements UseCase { + final TheMealDbRepository theMealDbRepository; + + GetCategoryMeal({@required this.theMealDbRepository}); + + @override + Future> call(NoParams params) async { + return await theMealDbRepository.getCategoryMeal(); + } +} diff --git a/lib/feature/domain/usecase/getdetailmealbyid/get_detail_meal_by_id.dart b/lib/feature/domain/usecase/getdetailmealbyid/get_detail_meal_by_id.dart new file mode 100644 index 0000000..4e6bad2 --- /dev/null +++ b/lib/feature/domain/usecase/getdetailmealbyid/get_detail_meal_by_id.dart @@ -0,0 +1,32 @@ +import 'package:dartz/dartz.dart'; +import 'package:meta/meta.dart'; +import 'package:equatable/equatable.dart'; +import 'package:food_recipe/core/error/failure.dart'; +import 'package:food_recipe/core/usecase/usecase.dart'; +import 'package:food_recipe/feature/data/model/detailmeal/detail_meal_response.dart'; +import 'package:food_recipe/feature/domain/repository/themealdb/themealdb_repository.dart'; + +class GetDetailMealById implements UseCase { + final TheMealDbRepository theMealDbRepository; + + GetDetailMealById({@required this.theMealDbRepository}); + + @override + Future> call(params) async { + return await theMealDbRepository.getDetailMealById(params.id); + } +} + +class ParamsGetDetailMealById extends Equatable { + final String id; + + ParamsGetDetailMealById(this.id); + + @override + List get props => [id]; + + @override + String toString() { + return 'ParamsGetDetailMealById{id: $id}'; + } +} \ No newline at end of file diff --git a/lib/feature/domain/usecase/getfilterbycategory/get_filter_by_category.dart b/lib/feature/domain/usecase/getfilterbycategory/get_filter_by_category.dart new file mode 100644 index 0000000..69ae0f7 --- /dev/null +++ b/lib/feature/domain/usecase/getfilterbycategory/get_filter_by_category.dart @@ -0,0 +1,32 @@ +import 'package:dartz/dartz.dart'; +import 'package:equatable/equatable.dart'; +import 'package:food_recipe/core/error/failure.dart'; +import 'package:meta/meta.dart'; +import 'package:food_recipe/core/usecase/usecase.dart'; +import 'package:food_recipe/feature/data/model/filterbycategory/filter_by_category_response.dart'; +import 'package:food_recipe/feature/domain/repository/themealdb/themealdb_repository.dart'; + +class GetFilterByCategory implements UseCase { + final TheMealDbRepository theMealDbRepository; + + GetFilterByCategory({@required this.theMealDbRepository}); + + @override + Future> call(ParamsGetFilterByCategory params) async { + return await theMealDbRepository.getFilterByCategory(params.category); + } +} + +class ParamsGetFilterByCategory extends Equatable { + final String category; + + ParamsGetFilterByCategory({@required this.category}); + + @override + List get props => [category]; + + @override + String toString() { + return 'ParamsGetFilterByCategory{category: $category}'; + } +} \ No newline at end of file diff --git a/lib/feature/domain/usecase/getrandommeal/get_random_meal.dart b/lib/feature/domain/usecase/getrandommeal/get_random_meal.dart new file mode 100644 index 0000000..0022240 --- /dev/null +++ b/lib/feature/domain/usecase/getrandommeal/get_random_meal.dart @@ -0,0 +1,16 @@ +import 'package:dartz/dartz.dart'; +import 'package:food_recipe/core/error/failure.dart'; +import 'package:food_recipe/core/usecase/usecase.dart'; +import 'package:food_recipe/feature/data/model/detailmeal/detail_meal_response.dart'; +import 'package:food_recipe/feature/domain/repository/themealdb/themealdb_repository.dart'; + +class GetRandomMeal implements UseCase { + final TheMealDbRepository theMealDbRepository; + + GetRandomMeal({this.theMealDbRepository}); + + @override + Future> call(NoParams params) async { + return await theMealDbRepository.getRandomMeal(); + } +} \ No newline at end of file diff --git a/lib/feature/domain/usecase/searchmealbyname/search_meal_by_name.dart b/lib/feature/domain/usecase/searchmealbyname/search_meal_by_name.dart new file mode 100644 index 0000000..36d8ab8 --- /dev/null +++ b/lib/feature/domain/usecase/searchmealbyname/search_meal_by_name.dart @@ -0,0 +1,32 @@ +import 'package:dartz/dartz.dart'; +import 'package:equatable/equatable.dart'; +import 'package:food_recipe/core/error/failure.dart'; +import 'package:meta/meta.dart'; +import 'package:food_recipe/core/usecase/usecase.dart'; +import 'package:food_recipe/feature/data/model/searchmealbyname/search_meal_by_name_response.dart'; +import 'package:food_recipe/feature/domain/repository/themealdb/themealdb_repository.dart'; + +class SearchMealByName implements UseCase { + final TheMealDbRepository theMealDbRepository; + + SearchMealByName({@required this.theMealDbRepository}); + + @override + Future> call(ParamsSearchMealByName params) async { + return await theMealDbRepository.searchMealByName(params.name); + } +} + +class ParamsSearchMealByName extends Equatable { + final String name; + + ParamsSearchMealByName({@required this.name}); + + @override + List get props => [name]; + + @override + String toString() { + return 'ParamsSearchMealByName{name: $name}'; + } +} diff --git a/lib/feature/presentation/bloc/categorymeal/bloc.dart b/lib/feature/presentation/bloc/categorymeal/bloc.dart new file mode 100644 index 0000000..e0c3569 --- /dev/null +++ b/lib/feature/presentation/bloc/categorymeal/bloc.dart @@ -0,0 +1,3 @@ +export 'category_meal_bloc.dart'; +export 'category_meal_event.dart'; +export 'category_meal_state.dart'; \ No newline at end of file diff --git a/lib/feature/presentation/bloc/categorymeal/category_meal_bloc.dart b/lib/feature/presentation/bloc/categorymeal/category_meal_bloc.dart new file mode 100644 index 0000000..25f9ef6 --- /dev/null +++ b/lib/feature/presentation/bloc/categorymeal/category_meal_bloc.dart @@ -0,0 +1,83 @@ +import 'dart:async'; +import 'package:bloc/bloc.dart'; +import 'package:food_recipe/core/error/failure.dart'; +import 'package:food_recipe/core/usecase/usecase.dart'; +import 'package:food_recipe/feature/data/model/filterbycategory/filter_by_category_response.dart'; +import 'package:food_recipe/feature/data/model/mealcategory/meal_category_response.dart'; +import 'package:meta/meta.dart'; +import 'package:food_recipe/feature/domain/usecase/getcategorymeal/get_category_meal.dart'; +import 'package:food_recipe/feature/domain/usecase/getfilterbycategory/get_filter_by_category.dart'; +import './bloc.dart'; + +class CategoryMealBloc extends Bloc { + final GetCategoryMeal getCategoryMeal; + final GetFilterByCategory getFilterByCategory; + + CategoryMealBloc({ + @required this.getCategoryMeal, + @required this.getFilterByCategory, + }) : assert(getCategoryMeal != null), + assert(getFilterByCategory != null); + + @override + CategoryMealState get initialState => InitialCategoryMealState(); + + @override + Stream mapEventToState( + CategoryMealEvent event, + ) async* { + if (event is LoadCategoryMealEvent) { + yield* _mapLoadCategoryMealEventToState(event); + } else if (event is LoadDetailCategoryMealEvent) { + yield* _mapLoadDetailCategoryMealEventToState(event); + } + } + + Stream _mapLoadCategoryMealEventToState(LoadCategoryMealEvent event) async* { + yield LoadingCategoryMealState(); + var resultCategoryMeal = await getCategoryMeal(NoParams()); + var resultFoldCategoryMeal = resultCategoryMeal.fold( + (failure) => failure, + (response) => response, + ); + if (resultFoldCategoryMeal is ServerFailure) { + yield FailureCategoryMealState(resultFoldCategoryMeal.errorMessage); + return; + } else if (resultFoldCategoryMeal is ConnectionFailure) { + yield FailureCategoryMealState(resultFoldCategoryMeal.errorMessage); + return; + } + var mealCategoryResponse = resultFoldCategoryMeal as MealCategoryResponse; + var category = mealCategoryResponse.categories[0].strCategory; + var resultFilterByCategory = await getFilterByCategory(ParamsGetFilterByCategory(category: category)); + var resultFoldFilterByCategory = resultFilterByCategory.fold( + (failure) => failure, + (response) => response, + ); + if (resultFoldFilterByCategory is ServerFailure) { + yield FailureCategoryMealState(resultFoldFilterByCategory.errorMessage); + return; + } else if (resultFoldFilterByCategory is ConnectionFailure) { + yield FailureCategoryMealState(resultFoldFilterByCategory.errorMessage); + return; + } + var filterByCategoryResponse = resultFoldFilterByCategory as FilterByCategoryResponse; + yield LoadedCategoryMealState(mealCategoryResponse, filterByCategoryResponse); + } + + Stream _mapLoadDetailCategoryMealEventToState(LoadDetailCategoryMealEvent event) async* { + yield LoadingDetailCategoryMealState(); + var resultFilterByCategory = await getFilterByCategory(ParamsGetFilterByCategory(category: event.category)); + yield resultFilterByCategory.fold( + // ignore: missing_return + (failure) { + if (failure is ServerFailure) { + return FailureDetailCategoryMealState(failure.errorMessage); + } else if (failure is ConnectionFailure) { + return FailureDetailCategoryMealState(failure.errorMessage); + } + }, + (response) => LoadedDetailCategoryMealState(response), + ); + } +} diff --git a/lib/feature/presentation/bloc/categorymeal/category_meal_event.dart b/lib/feature/presentation/bloc/categorymeal/category_meal_event.dart new file mode 100644 index 0000000..b55a21d --- /dev/null +++ b/lib/feature/presentation/bloc/categorymeal/category_meal_event.dart @@ -0,0 +1,24 @@ +import 'package:equatable/equatable.dart'; + +abstract class CategoryMealEvent extends Equatable { + const CategoryMealEvent(); +} + +class LoadCategoryMealEvent extends CategoryMealEvent { + @override + List get props => []; +} + +class LoadDetailCategoryMealEvent extends CategoryMealEvent { + final String category; + + LoadDetailCategoryMealEvent(this.category); + + @override + List get props => [category]; + + @override + String toString() { + return 'LoadDetailCategoryMealEvent{category: $category}'; + } +} \ No newline at end of file diff --git a/lib/feature/presentation/bloc/categorymeal/category_meal_state.dart b/lib/feature/presentation/bloc/categorymeal/category_meal_state.dart new file mode 100644 index 0000000..a970e57 --- /dev/null +++ b/lib/feature/presentation/bloc/categorymeal/category_meal_state.dart @@ -0,0 +1,74 @@ +import 'package:equatable/equatable.dart'; +import 'package:food_recipe/feature/data/model/filterbycategory/filter_by_category_response.dart'; +import 'package:food_recipe/feature/data/model/mealcategory/meal_category_response.dart'; + +abstract class CategoryMealState extends Equatable { + const CategoryMealState(); + + @override + List get props => []; +} + +class InitialCategoryMealState extends CategoryMealState {} + +class LoadingCategoryMealState extends CategoryMealState {} + +class LoadingDetailCategoryMealState extends CategoryMealState {} + +class FailureCategoryMealState extends CategoryMealState { + final String errorMessage; + + FailureCategoryMealState(this.errorMessage); + + @override + List get props => [errorMessage]; + + @override + String toString() { + return 'FailureCategoryMealState{errorMessage: $errorMessage}'; + } +} + +class FailureDetailCategoryMealState extends CategoryMealState { + final String errorMessage; + + FailureDetailCategoryMealState(this.errorMessage); + + @override + List get props => [errorMessage]; + + @override + String toString() { + return 'FailureDetailCategoryMealState{errorMessage: $errorMessage}'; + } +} + +class LoadedCategoryMealState extends CategoryMealState { + final MealCategoryResponse mealCategoryResponse; + final FilterByCategoryResponse filterByCategoryResponse; + + LoadedCategoryMealState(this.mealCategoryResponse, this.filterByCategoryResponse); + + @override + List get props => [mealCategoryResponse, filterByCategoryResponse]; + + @override + String toString() { + return 'LoadedCategoryMealState{mealCategoryResponse: $mealCategoryResponse, ' + 'filterByCategoryResponse: $filterByCategoryResponse}'; + } +} + +class LoadedDetailCategoryMealState extends CategoryMealState { + final FilterByCategoryResponse filterByCategoryResponse; + + LoadedDetailCategoryMealState(this.filterByCategoryResponse); + + @override + List get props => [filterByCategoryResponse]; + + @override + String toString() { + return 'LoadedDetailCategoryMealState{filterByCategoryResponse: $filterByCategoryResponse}'; + } +} \ No newline at end of file diff --git a/lib/feature/presentation/bloc/detailmeal/bloc.dart b/lib/feature/presentation/bloc/detailmeal/bloc.dart new file mode 100644 index 0000000..26a56c1 --- /dev/null +++ b/lib/feature/presentation/bloc/detailmeal/bloc.dart @@ -0,0 +1,3 @@ +export 'detail_meal_bloc.dart'; +export 'detail_meal_event.dart'; +export 'detail_meal_state.dart'; \ No newline at end of file diff --git a/lib/feature/presentation/bloc/detailmeal/detail_meal_bloc.dart b/lib/feature/presentation/bloc/detailmeal/detail_meal_bloc.dart new file mode 100644 index 0000000..786034f --- /dev/null +++ b/lib/feature/presentation/bloc/detailmeal/detail_meal_bloc.dart @@ -0,0 +1,40 @@ +import 'dart:async'; +import 'package:food_recipe/core/error/failure.dart'; +import 'package:meta/meta.dart'; +import 'package:bloc/bloc.dart'; +import 'package:food_recipe/feature/domain/usecase/getdetailmealbyid/get_detail_meal_by_id.dart'; +import './bloc.dart'; + +class DetailMealBloc extends Bloc { + final GetDetailMealById getDetailMealById; + + DetailMealBloc({@required this.getDetailMealById}) : assert(getDetailMealById != null); + + @override + DetailMealState get initialState => InitialDetailMealState(); + + @override + Stream mapEventToState( + DetailMealEvent event, + ) async* { + if (event is LoadDetailMealEvent) { + yield* _mapLoadDetailMealEventToState(event); + } + } + + Stream _mapLoadDetailMealEventToState(LoadDetailMealEvent event) async* { + yield LoadingDetailMealState(); + var result = await getDetailMealById(ParamsGetDetailMealById(event.idMeal)); + yield result.fold( + // ignore: missing_return + (failure) { + if (failure is ServerFailure) { + return FailureDetailMealState(); + } else if (failure is ConnectionFailure) { + return FailureDetailMealState(); + } + }, + (response) => LoadedDetailMealState(response), + ); + } +} diff --git a/lib/feature/presentation/bloc/detailmeal/detail_meal_event.dart b/lib/feature/presentation/bloc/detailmeal/detail_meal_event.dart new file mode 100644 index 0000000..98104e3 --- /dev/null +++ b/lib/feature/presentation/bloc/detailmeal/detail_meal_event.dart @@ -0,0 +1,19 @@ +import 'package:equatable/equatable.dart'; + +abstract class DetailMealEvent extends Equatable { + const DetailMealEvent(); +} + +class LoadDetailMealEvent extends DetailMealEvent { + final String idMeal; + + LoadDetailMealEvent(this.idMeal); + + @override + List get props => [idMeal]; + + @override + String toString() { + return 'LoadDetailMealEvent{idMeal: $idMeal}'; + } +} \ No newline at end of file diff --git a/lib/feature/presentation/bloc/detailmeal/detail_meal_state.dart b/lib/feature/presentation/bloc/detailmeal/detail_meal_state.dart new file mode 100644 index 0000000..04c359f --- /dev/null +++ b/lib/feature/presentation/bloc/detailmeal/detail_meal_state.dart @@ -0,0 +1,29 @@ +import 'package:equatable/equatable.dart'; +import 'package:food_recipe/feature/data/model/detailmeal/detail_meal_response.dart'; + +abstract class DetailMealState extends Equatable { + const DetailMealState(); + + @override + List get props => []; +} + +class InitialDetailMealState extends DetailMealState {} + +class LoadingDetailMealState extends DetailMealState {} + +class FailureDetailMealState extends DetailMealState {} + +class LoadedDetailMealState extends DetailMealState { + final DetailMealResponse detailMealResponse; + + LoadedDetailMealState(this.detailMealResponse); + + @override + List get props => [detailMealResponse]; + + @override + String toString() { + return 'LoadedDetailMealState{detailMealResponse: $detailMealResponse}'; + } +} \ No newline at end of file diff --git a/lib/feature/presentation/bloc/randommeal/bloc.dart b/lib/feature/presentation/bloc/randommeal/bloc.dart new file mode 100644 index 0000000..dd70df1 --- /dev/null +++ b/lib/feature/presentation/bloc/randommeal/bloc.dart @@ -0,0 +1,3 @@ +export 'random_meal_bloc.dart'; +export 'random_meal_event.dart'; +export 'random_meal_state.dart'; \ No newline at end of file diff --git a/lib/feature/presentation/bloc/randommeal/random_meal_bloc.dart b/lib/feature/presentation/bloc/randommeal/random_meal_bloc.dart new file mode 100644 index 0000000..db1c634 --- /dev/null +++ b/lib/feature/presentation/bloc/randommeal/random_meal_bloc.dart @@ -0,0 +1,41 @@ +import 'dart:async'; +import 'package:bloc/bloc.dart'; +import 'package:food_recipe/core/error/failure.dart'; +import 'package:food_recipe/core/usecase/usecase.dart'; +import 'package:meta/meta.dart'; +import 'package:food_recipe/feature/domain/usecase/getrandommeal/get_random_meal.dart'; +import './bloc.dart'; + +class RandomMealBloc extends Bloc { + final GetRandomMeal getRandomMeal; + + RandomMealBloc({@required this.getRandomMeal}) : assert(getRandomMeal != null); + + @override + RandomMealState get initialState => InitialRandomMealState(); + + @override + Stream mapEventToState( + RandomMealEvent event, + ) async* { + if (event is LoadRandomMealEvent) { + yield* _mapLoadRandomMealEventToState(); + } + } + + Stream _mapLoadRandomMealEventToState() async* { + yield LoadingRandomMealState(); + var result = await getRandomMeal(NoParams()); + yield result.fold( + // ignore: missing_return + (failure) { + if (failure is ServerFailure) { + return FailureRandomMealState(failure.errorMessage); + } else if (failure is ConnectionFailure) { + return FailureRandomMealState(failure.errorMessage); + } + }, + (response) => LoadedRandomMealState(response), + ); + } +} diff --git a/lib/feature/presentation/bloc/randommeal/random_meal_event.dart b/lib/feature/presentation/bloc/randommeal/random_meal_event.dart new file mode 100644 index 0000000..b99e9c9 --- /dev/null +++ b/lib/feature/presentation/bloc/randommeal/random_meal_event.dart @@ -0,0 +1,10 @@ +import 'package:equatable/equatable.dart'; + +abstract class RandomMealEvent extends Equatable { + const RandomMealEvent(); +} + +class LoadRandomMealEvent extends RandomMealEvent { + @override + List get props => []; +} \ No newline at end of file diff --git a/lib/feature/presentation/bloc/randommeal/random_meal_state.dart b/lib/feature/presentation/bloc/randommeal/random_meal_state.dart new file mode 100644 index 0000000..7070ab7 --- /dev/null +++ b/lib/feature/presentation/bloc/randommeal/random_meal_state.dart @@ -0,0 +1,41 @@ +import 'package:equatable/equatable.dart'; +import 'package:food_recipe/feature/data/model/detailmeal/detail_meal_response.dart'; + +abstract class RandomMealState extends Equatable { + const RandomMealState(); + + @override + List get props => []; +} + +class InitialRandomMealState extends RandomMealState {} + +class LoadingRandomMealState extends RandomMealState {} + +class FailureRandomMealState extends RandomMealState { + final String errorMessage; + + FailureRandomMealState(this.errorMessage); + + @override + List get props => [errorMessage]; + + @override + String toString() { + return 'FailureRandomMealState{errorMessage: $errorMessage}'; + } +} + +class LoadedRandomMealState extends RandomMealState { + final DetailMealResponse detailMealResponse; + + LoadedRandomMealState(this.detailMealResponse); + + @override + List get props => [detailMealResponse]; + + @override + String toString() { + return 'LoadedRandomMealState{detailMealResponse: $detailMealResponse}'; + } +} \ No newline at end of file diff --git a/lib/feature/presentation/bloc/searchmeal/bloc.dart b/lib/feature/presentation/bloc/searchmeal/bloc.dart new file mode 100644 index 0000000..009fc3e --- /dev/null +++ b/lib/feature/presentation/bloc/searchmeal/bloc.dart @@ -0,0 +1,3 @@ +export 'search_meal_bloc.dart'; +export 'search_meal_event.dart'; +export 'search_meal_state.dart'; \ No newline at end of file diff --git a/lib/feature/presentation/bloc/searchmeal/search_meal_bloc.dart b/lib/feature/presentation/bloc/searchmeal/search_meal_bloc.dart new file mode 100644 index 0000000..cac32fb --- /dev/null +++ b/lib/feature/presentation/bloc/searchmeal/search_meal_bloc.dart @@ -0,0 +1,40 @@ +import 'dart:async'; +import 'package:bloc/bloc.dart'; +import 'package:food_recipe/core/error/failure.dart'; +import 'package:meta/meta.dart'; +import 'package:food_recipe/feature/domain/usecase/searchmealbyname/search_meal_by_name.dart'; +import './bloc.dart'; + +class SearchMealBloc extends Bloc { + final SearchMealByName searchMealByName; + + SearchMealBloc({@required this.searchMealByName}) : assert(searchMealByName != null); + + @override + SearchMealState get initialState => InitialSearchMealState(); + + @override + Stream mapEventToState( + SearchMealEvent event, + ) async* { + if (event is LoadSearchMealEvent) { + yield* _mapLoadSearchMealEventToState(event); + } + } + + Stream _mapLoadSearchMealEventToState(LoadSearchMealEvent event) async* { + yield LoadingSearchMealState(); + var result = await searchMealByName(ParamsSearchMealByName(name: event.name)); + yield result.fold( + // ignore: missing_return + (failure) { + if (failure is ServerFailure) { + return FailureSearchMealState(failure.errorMessage); + } else if (failure is ConnectionFailure) { + return FailureSearchMealState(failure.errorMessage); + } + }, + (response) => LoadedSearchMealState(response), + ); + } +} diff --git a/lib/feature/presentation/bloc/searchmeal/search_meal_event.dart b/lib/feature/presentation/bloc/searchmeal/search_meal_event.dart new file mode 100644 index 0000000..dfbc0d0 --- /dev/null +++ b/lib/feature/presentation/bloc/searchmeal/search_meal_event.dart @@ -0,0 +1,19 @@ +import 'package:equatable/equatable.dart'; + +abstract class SearchMealEvent extends Equatable { + const SearchMealEvent(); +} + +class LoadSearchMealEvent extends SearchMealEvent { + final String name; + + LoadSearchMealEvent(this.name); + + @override + List get props => [name]; + + @override + String toString() { + return 'LoadSearchMealEvent{name: $name}'; + } +} \ No newline at end of file diff --git a/lib/feature/presentation/bloc/searchmeal/search_meal_state.dart b/lib/feature/presentation/bloc/searchmeal/search_meal_state.dart new file mode 100644 index 0000000..8274e65 --- /dev/null +++ b/lib/feature/presentation/bloc/searchmeal/search_meal_state.dart @@ -0,0 +1,41 @@ +import 'package:equatable/equatable.dart'; +import 'package:food_recipe/feature/data/model/searchmealbyname/search_meal_by_name_response.dart'; + +abstract class SearchMealState extends Equatable { + const SearchMealState(); + + @override + List get props => []; +} + +class InitialSearchMealState extends SearchMealState {} + +class LoadingSearchMealState extends SearchMealState {} + +class FailureSearchMealState extends SearchMealState { + final String errorMessage; + + FailureSearchMealState(this.errorMessage); + + @override + List get props => [errorMessage]; + + @override + String toString() { + return 'FailureSearchMealState{errorMessage: $errorMessage}'; + } +} + +class LoadedSearchMealState extends SearchMealState { + final SearchMealByNameResponse searchMealByNameResponse; + + LoadedSearchMealState(this.searchMealByNameResponse); + + @override + List get props => [searchMealByNameResponse]; + + @override + String toString() { + return 'LoadedSearchMealState{searchMealByNameResponse: $searchMealByNameResponse}'; + } +} \ No newline at end of file diff --git a/lib/feature/presentation/page/detail/detail_page.dart b/lib/feature/presentation/page/detail/detail_page.dart new file mode 100644 index 0000000..a1ac132 --- /dev/null +++ b/lib/feature/presentation/page/detail/detail_page.dart @@ -0,0 +1,230 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:food_recipe/feature/presentation/bloc/detailmeal/bloc.dart'; +import 'package:food_recipe/injection_container.dart'; + +class DetailPage extends StatefulWidget { + final String idMeal; + + DetailPage(this.idMeal); + + @override + _DetailPageState createState() => _DetailPageState(); +} + +class _DetailPageState extends State { + final detailMealBloc = sl(); + + var paddingTop = 0.0; + var paddingBottom = 0.0; + + @override + void initState() { + WidgetsBinding.instance.addPostFrameCallback((_) { + debugPrint('idMeal: ${widget.idMeal}'); + _doLoadData(); + }); + super.initState(); + } + + void _doLoadData() { + detailMealBloc.add(LoadDetailMealEvent(widget.idMeal)); + } + + @override + Widget build(BuildContext context) { + var mediaQueryData = MediaQuery.of(context); + paddingTop = mediaQueryData.padding.top; + paddingBottom = mediaQueryData.padding.bottom; + return Scaffold( + body: BlocProvider( + create: (context) => detailMealBloc, + child: BlocBuilder( + builder: (context, state) { + if (state is LoadingDetailMealState) { + return Center( + child: CircularProgressIndicator(), + ); + } else if (state is FailureDetailMealState) { + return Center( + child: IconButton( + icon: Icon(Icons.refresh), + onPressed: () { + _doLoadData(); + }, + ), + ); + } else if (state is LoadedDetailMealState) { + var detailMealResponse = state.detailMealResponse; + return Stack( + children: [ + CachedNetworkImage( + imageUrl: detailMealResponse.strMealThumb ?? '', + width: double.infinity, + height: 192, + fit: BoxFit.cover, + placeholder: (context, url) { + return Image.asset( + 'assets/images/img_placeholder.jpg', + fit: BoxFit.cover, + width: double.infinity, + height: 192, + ); + }, + errorWidget: (context, url, dynamic) { + return Image.asset( + 'assets/images/img_not_found.jpg', + fit: BoxFit.cover, + width: double.infinity, + height: 192, + ); + }, + ), + Positioned( + left: 8, + top: paddingTop + 8, + child: GestureDetector( + onTap: () => Navigator.pop(context), + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white, + ), + padding: EdgeInsets.all(8), + child: Center( + child: Icon( + Icons.arrow_back, + size: 20, + ), + ), + ), + ), + ), + Container( + width: double.infinity, + padding: EdgeInsets.only(top: paddingTop), + child: Column( + children: [ + SizedBox(height: 128), + Padding( + padding: EdgeInsets.symmetric(horizontal: 16), + child: Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + child: Padding( + padding: EdgeInsets.all(8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + detailMealResponse.strMeal ?? '-', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.caption.copyWith( + fontWeight: FontWeight.w500, + color: Colors.grey[800], + ), + ), + Text( + detailMealResponse.strCategory ?? '-', + style: Theme.of(context).textTheme.caption, + ), + Divider(color: Colors.grey[400]), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildWidgetInfoFloatingHotRecipe( + Icons.location_on, + Colors.orange, + detailMealResponse.strArea, + ), + _buildWidgetInfoFloatingHotRecipe( + Icons.kitchen, + Colors.lightBlue, + '${detailMealResponse.listIngredients.length} Ingredients', + ), + _buildWidgetInfoFloatingHotRecipe( + Icons.ondemand_video, + Theme.of(context).primaryColor, + detailMealResponse.strYoutube != null ? 'Youtube' : 'N/A', + ), + ], + ), + ], + ), + ), + ), + ), + Expanded( + child: ListView( + padding: EdgeInsets.only( + left: 16, + top: 16, + right: 16, + bottom: paddingBottom + 16, + ), + children: [ + Text( + 'Ingredients', + style: TextStyle(fontWeight: FontWeight.w500), + ), + SizedBox(height: 4), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: detailMealResponse.listIngredients + .map((element) => Text( + '• $element', + style: Theme.of(context).textTheme.caption, + )) + .toList(), + ), + SizedBox(height: 16), + Text( + 'Instructions', + style: TextStyle(fontWeight: FontWeight.w500), + ), + Text( + detailMealResponse.strInstructions, + style: Theme.of(context).textTheme.caption, + ), + ], + ), + ), + ], + ), + ) + ], + ); + } else { + return Container(); + } + }, + ), + ), + ); + } + + Widget _buildWidgetInfoFloatingHotRecipe( + IconData icon, + Color iconColor, + String label, + ) { + return Wrap( + children: [ + Icon( + icon, + size: 14, + color: iconColor, + ), + SizedBox(width: 4), + Text( + label ?? '-', + style: Theme.of(context).textTheme.caption.copyWith(fontSize: 11), + ), + ], + ); + } +} diff --git a/lib/feature/presentation/page/home/home_page.dart b/lib/feature/presentation/page/home/home_page.dart new file mode 100644 index 0000000..b55a969 --- /dev/null +++ b/lib/feature/presentation/page/home/home_page.dart @@ -0,0 +1,505 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:food_recipe/feature/data/model/filterbycategory/filter_by_category_response.dart'; +import 'package:food_recipe/feature/presentation/bloc/categorymeal/bloc.dart'; +import 'package:food_recipe/feature/presentation/bloc/randommeal/bloc.dart'; +import 'package:food_recipe/feature/presentation/page/detail/detail_page.dart'; +import 'package:food_recipe/feature/presentation/page/search/search_page.dart'; +import 'package:food_recipe/feature/presentation/widget/widget_custom_shimmer.dart'; +import 'package:food_recipe/injection_container.dart'; + +class HomePage extends StatefulWidget { + @override + _HomePageState createState() => _HomePageState(); +} + +class _HomePageState extends State { + final randomMealBloc = sl(); + final categoryMealBloc = sl(); + + var paddingTop = 0.0; + var categorySelected = 'Beef'; + var isCategoryLoading = false; + + @override + void initState() { + WidgetsBinding.instance.addPostFrameCallback((_) { + _doLoadData(); + }); + super.initState(); + } + + void _doLoadData() { + isCategoryLoading = true; + randomMealBloc.add(LoadRandomMealEvent()); + categoryMealBloc.add(LoadCategoryMealEvent()); + } + + @override + Widget build(BuildContext context) { + var mediaQueryData = MediaQuery.of(context); + paddingTop = mediaQueryData.padding.top; + return Scaffold( + appBar: AppBar( + title: Text('Food Recipe'), + actions: [ + IconButton( + icon: Icon( + Icons.search, + ), + onPressed: () { + Navigator.push(context, MaterialPageRoute(builder: (context) => SearchPage())); + }, + ), + ], + ), + body: MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => randomMealBloc, + ), + BlocProvider( + create: (context) => categoryMealBloc, + ), + ], + child: Container( + width: double.infinity, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + //_buildWidgetSearch(), + SizedBox(height: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Text( + 'Random Recipe', + style: Theme.of(context).textTheme.bodyText2.copyWith(fontWeight: FontWeight.w500), + ), + ), + SizedBox(height: 8), + _buildWidgetRandomRecipe(), + SizedBox(height: 8), + _buildWidgetCategoryRecipe(), + SizedBox(height: 8), + Padding( + padding: const EdgeInsets.only(left: 16.0), + child: Text( + '${categorySelected[0].toUpperCase()}${categorySelected.substring(1)} Recipe', + style: TextStyle( + fontWeight: FontWeight.w500, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + SizedBox(height: 8), + BlocBuilder( + builder: (context, state) { + if (state is LoadingCategoryMealState) { + return _buildWidgetLoadingListRecipeByCategory(); + } else if (state is LoadingDetailCategoryMealState) { + return _buildWidgetLoadingListRecipeByCategory(); + } else if (state is FailureCategoryMealState) { + return _buildWidgetFailureListRecipeByCategory(); + } else if (state is FailureDetailCategoryMealState) { + return _buildWidgetFailureListRecipeByCategory(); + } else if (state is LoadedCategoryMealState) { + var filterByCategoryResponse = state.filterByCategoryResponse; + return _buildWidgetListRecipeByCategory(filterByCategoryResponse); + } else if (state is LoadedDetailCategoryMealState) { + var filterByCategoryResponse = state.filterByCategoryResponse; + return _buildWidgetListRecipeByCategory(filterByCategoryResponse); + } else { + return Container(); + } + }, + ), + ], + ), + ), + ), + ); + } + + Widget _buildWidgetListRecipeByCategory(FilterByCategoryResponse data) { + var listData = data.meals; + return Expanded( + child: ListView.separated( + padding: EdgeInsets.only( + left: 16, + right: 16, + bottom: 16, + ), + itemBuilder: (context, index) { + var itemData = listData[index]; + return GestureDetector( + onTap: () { + Navigator.push(context, MaterialPageRoute(builder: (context) => DetailPage(itemData.idMeal))); + }, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(4), + child: CachedNetworkImage( + width: 48, + height: 48, + imageUrl: itemData.strMealThumb, + fit: BoxFit.cover, + placeholder: (context, url) { + return Image.asset( + 'assets/images/img_placeholder.jpg', + fit: BoxFit.cover, + width: 48, + height: 48, + ); + }, + errorWidget: (context, url, dynamic) { + return Image.asset( + 'assets/images/img_not_found.jpg', + fit: BoxFit.cover, + width: 48, + height: 48, + ); + }, + ), + ), + SizedBox(width: 8), + Expanded( + child: Text( + itemData.strMeal, + style: Theme.of(context).textTheme.bodyText2, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ); + }, + separatorBuilder: (context, index) => Divider(), + itemCount: listData.length, + ), + ); + } + + Widget _buildWidgetFailureListRecipeByCategory() { + return Center( + child: IconButton( + icon: Icon(Icons.refresh), + onPressed: () { + categoryMealBloc.add(LoadDetailCategoryMealEvent(categorySelected)); + }, + ), + ); + } + + Widget _buildWidgetLoadingListRecipeByCategory() { + return Padding( + padding: const EdgeInsets.only(left: 16.0), + child: WidgetCustomShimmer( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(4), + ), + ), + SizedBox(width: 8), + Container( + width: 64, + height: 14, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(4), + ), + ), + ], + ), + ), + ); + } + + Widget _buildWidgetCategoryRecipe() { + return BlocBuilder( + condition: (previousState, currentState) { + return isCategoryLoading; + }, + builder: (context, state) { + if (state is LoadingCategoryMealState) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: SizedBox( + height: 32, + child: WidgetCustomShimmer( + child: Row( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(4), + ), + padding: EdgeInsets.symmetric( + horizontal: 24, + vertical: 16, + ), + child: Text(''), + ), + ), + SizedBox(width: 8), + Expanded( + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(4), + ), + padding: EdgeInsets.symmetric( + horizontal: 24, + vertical: 16, + ), + child: Text(''), + ), + ), + SizedBox(width: 8), + Expanded( + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(4), + ), + padding: EdgeInsets.symmetric( + horizontal: 24, + vertical: 16, + ), + child: Text(''), + ), + ), + ], + ), + ), + ), + ); + } else if (state is LoadedCategoryMealState) { + isCategoryLoading = false; + var categories = state.mealCategoryResponse.categories; + if (categorySelected.isEmpty) { + categorySelected = categories[0].strCategory.toLowerCase(); + } + return SizedBox( + height: 32, + child: ListView.separated( + scrollDirection: Axis.horizontal, + padding: EdgeInsets.symmetric(horizontal: 16), + itemBuilder: (context, index) { + var itemCategory = categories[index]; + var thumbnail = 'assets/images/img_' + itemCategory.strCategory.toLowerCase() + '.jpg'; + return GestureDetector( + onTap: () { + setState(() { + categorySelected = itemCategory.strCategory.toLowerCase(); + categoryMealBloc.add(LoadDetailCategoryMealEvent(categorySelected)); + }); + }, + child: Container( + height: 32, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(thumbnail), + fit: BoxFit.cover, + ), + borderRadius: BorderRadius.circular(4), + ), + child: Container( + height: 32, + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.5), + borderRadius: BorderRadius.circular(4), + border: categorySelected == itemCategory.strCategory.toLowerCase() + ? Border.all( + color: Theme.of(context).primaryColor, + width: 2, + ) + : null, + ), + padding: EdgeInsets.symmetric(horizontal: 24), + alignment: Alignment.center, + child: Text( + itemCategory.strCategory, + style: TextStyle( + color: Colors.white, + ), + ), + ), + ), + ); + }, + separatorBuilder: (context, index) => SizedBox(width: 8), + itemCount: categories.length, + ), + ); + } else if (state is FailureCategoryMealState) { + return IconButton( + icon: Icon(Icons.refresh), + onPressed: () { + _doLoadData(); + }, + ); + } else { + return Container(); + } + }, + ); + } + + Widget _buildWidgetRandomRecipe() { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: BlocBuilder( + builder: (context, state) { + if (state is LoadingRandomMealState) { + return WidgetCustomShimmer( + child: Container( + width: double.infinity, + height: 160, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + ), + ), + ); + } else if (state is FailureRandomMealState) { + return Text('failure'); + } else if (state is LoadedRandomMealState) { + var detailMealResponse = state.detailMealResponse; + return GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DetailPage(detailMealResponse.idMeal), + ), + ); + }, + child: Container( + width: double.infinity, + height: 160, + child: Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(16), + child: CachedNetworkImage( + width: double.infinity, + height: 128, + imageUrl: detailMealResponse.strMealThumb, + fit: BoxFit.cover, + placeholder: (context, url) { + return Image.asset( + 'assets/images/img_placeholder.jpg', + fit: BoxFit.cover, + width: double.infinity, + height: 128, + ); + }, + errorWidget: (context, url, dynamic) { + return Image.asset( + 'assets/images/img_not_found.jpg', + fit: BoxFit.cover, + width: double.infinity, + height: 128, + ); + }, + ), + ), + Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + detailMealResponse.strMeal ?? '-', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.caption.copyWith( + fontWeight: FontWeight.w500, + color: Colors.grey[800], + ), + ), + Text( + detailMealResponse.strCategory ?? '-', + style: Theme.of(context).textTheme.caption, + ), + Divider(color: Colors.grey[400]), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildWidgetInfoFloatingHotRecipe( + Icons.location_on, + Colors.orange, + detailMealResponse.strArea, + ), + _buildWidgetInfoFloatingHotRecipe( + Icons.kitchen, + Colors.lightBlue, + '${detailMealResponse.listIngredients.length} Ingredients', + ), + _buildWidgetInfoFloatingHotRecipe( + Icons.ondemand_video, + Theme.of(context).primaryColor, + detailMealResponse.strYoutube != null ? 'Youtube' : 'N/A', + ), + ], + ), + ], + ), + ), + ), + ), + ) + ], + ), + ), + ); + } else { + return Container(); + } + }, + ), + ); + } + + Widget _buildWidgetInfoFloatingHotRecipe( + IconData icon, + Color iconColor, + String label, + ) { + return Wrap( + children: [ + Icon( + icon, + size: 14, + color: iconColor, + ), + SizedBox(width: 4), + Text( + label ?? '-', + style: Theme.of(context).textTheme.caption.copyWith(fontSize: 11), + ), + ], + ); + } +} diff --git a/lib/feature/presentation/page/search/search_page.dart b/lib/feature/presentation/page/search/search_page.dart new file mode 100644 index 0000000..c797745 --- /dev/null +++ b/lib/feature/presentation/page/search/search_page.dart @@ -0,0 +1,144 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:food_recipe/feature/presentation/bloc/searchmeal/bloc.dart'; +import 'package:food_recipe/feature/presentation/page/detail/detail_page.dart'; +import 'package:food_recipe/injection_container.dart'; + +class SearchPage extends StatelessWidget { + final searchMealBloc = sl(); + final listResult = []; + final controller = TextEditingController(); + final scaffoldState = GlobalKey(); + final focusNode = FocusNode(); + + @override + Widget build(BuildContext context) { + return Scaffold( + key: scaffoldState, + appBar: AppBar( + title: Text('Search'), + ), + body: BlocProvider( + create: (context) => searchMealBloc, + child: BlocListener( + listener: (context, state) { + if (state is FailureSearchMealState) { + scaffoldState.currentState.showSnackBar(SnackBar(content: Text('Failed search data'))); + } + }, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: TextField( + controller: controller, + decoration: InputDecoration( + hintText: 'Search something...', + isDense: true, + ), + ), + ), + SizedBox(width: 8), + RaisedButton( + child: Text('Search'), + focusNode: focusNode, + onPressed: () { + var keyword = controller.text; + if (keyword.isEmpty) { + scaffoldState.currentState.showSnackBar(SnackBar(content: Text('Invalid keyword'))); + return; + } + focusNode.requestFocus(); + searchMealBloc.add(LoadSearchMealEvent(keyword)); + }, + ), + ], + ), + Expanded( + child: BlocBuilder( + builder: (context, state) { + if (state is LoadingSearchMealState) { + return Center( + child: CircularProgressIndicator(), + ); + } else if (state is FailureSearchMealState) { + return Center( + child: Text('Failed search data'), + ); + } else if (state is LoadedSearchMealState) { + var listData = state.searchMealByNameResponse.meals; + return listData == null || listData.isEmpty + ? Center( + child: Text('Data not found'), + ) + : ListView.separated( + itemBuilder: (context, index) { + var itemData = listData[index]; + return GestureDetector( + onTap: () { + Navigator.push(context, + MaterialPageRoute(builder: (context) => DetailPage(itemData.idMeal))); + }, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(4), + child: CachedNetworkImage( + width: 48, + height: 48, + imageUrl: itemData.strMealThumb, + fit: BoxFit.cover, + placeholder: (context, url) { + return Image.asset( + 'assets/images/img_placeholder.jpg', + fit: BoxFit.cover, + width: 48, + height: 48, + ); + }, + errorWidget: (context, url, dynamic) { + return Image.asset( + 'assets/images/img_not_found.jpg', + fit: BoxFit.cover, + width: 48, + height: 48, + ); + }, + ), + ), + SizedBox(width: 8), + Expanded( + child: Text( + itemData.strMeal, + style: Theme.of(context).textTheme.bodyText2, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ); + }, + separatorBuilder: (context, index) => Divider(), + itemCount: listData.length, + ); + } else { + return Container(); + } + }, + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/feature/presentation/widget/widget_custom_shimmer.dart b/lib/feature/presentation/widget/widget_custom_shimmer.dart new file mode 100644 index 0000000..c9d1a6b --- /dev/null +++ b/lib/feature/presentation/widget/widget_custom_shimmer.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; + +class WidgetCustomShimmer extends StatelessWidget { + final Color baseColor; + final Color highlightColor; + final Widget child; + + WidgetCustomShimmer({ + this.baseColor = const Color(0xFFEEEEEE), + this.highlightColor = const Color(0xFFF5F5F5), + @required this.child, + }); + + @override + Widget build(BuildContext context) { + return Shimmer.fromColors( + baseColor: baseColor, + highlightColor: highlightColor, + child: child, + ); + } +} diff --git a/lib/injection_container.dart b/lib/injection_container.dart new file mode 100644 index 0000000..70127be --- /dev/null +++ b/lib/injection_container.dart @@ -0,0 +1,64 @@ +import 'package:data_connection_checker/data_connection_checker.dart'; +import 'package:dio/dio.dart'; +import 'package:get_it/get_it.dart'; + +import 'config/base_url_config.dart'; +import 'core/network/network_info.dart'; +import 'feature/data/datasource/themealdb/themealdb_remote_data_source.dart'; +import 'feature/data/repository/themealdb/themealdb_repository_impl.dart'; +import 'feature/domain/repository/themealdb/themealdb_repository.dart'; +import 'feature/domain/usecase/getcategorymeal/get_category_meal.dart'; +import 'feature/domain/usecase/getdetailmealbyid/get_detail_meal_by_id.dart'; +import 'feature/domain/usecase/getfilterbycategory/get_filter_by_category.dart'; +import 'feature/domain/usecase/getrandommeal/get_random_meal.dart'; +import 'feature/domain/usecase/searchmealbyname/search_meal_by_name.dart'; +import 'feature/presentation/bloc/categorymeal/bloc.dart'; +import 'feature/presentation/bloc/detailmeal/bloc.dart'; +import 'feature/presentation/bloc/randommeal/bloc.dart'; +import 'feature/presentation/bloc/searchmeal/bloc.dart'; + +final sl = GetIt.instance; + +Future init() async { + /** + * ! Feature + */ + // Bloc + sl.registerFactory(() => CategoryMealBloc(getCategoryMeal: sl(), getFilterByCategory: sl())); + sl.registerFactory(() => RandomMealBloc(getRandomMeal: sl())); + sl.registerFactory(() => SearchMealBloc(searchMealByName: sl())); + sl.registerFactory(() => DetailMealBloc(getDetailMealById: sl())); + + // Use case + sl.registerLazySingleton(() => GetCategoryMeal(theMealDbRepository: sl())); + sl.registerLazySingleton(() => GetFilterByCategory(theMealDbRepository: sl())); + sl.registerLazySingleton(() => GetRandomMeal(theMealDbRepository: sl())); + sl.registerLazySingleton(() => SearchMealByName(theMealDbRepository: sl())); + sl.registerLazySingleton(() => GetDetailMealById(theMealDbRepository: sl())); + + // Repository + sl.registerLazySingleton( + () => TheMealDbRepositoryImpl( + theMealDbRemoteDataSource: sl(), + networkInfo: sl(), + ), + ); + + // Data source + sl.registerLazySingleton(() => TheMealDbRemoteDataSourceImpl(dio: sl())); + + /** + * ! Core + */ + sl.registerLazySingleton(() => NetworkInfoImpl(sl())); + + /** + * ! External + */ + sl.registerLazySingleton(() { + final dio = Dio(); + dio.options.baseUrl = BaseUrlConfig().baseUrlMealDb; + return dio; + }); + sl.registerLazySingleton(() => DataConnectionChecker()); +} diff --git a/lib/main.dart b/lib/main.dart index dbb767e..6b999f5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,10 @@ import 'package:flutter/material.dart'; -import 'src/app.dart'; +import 'app.dart'; +import 'injection_container.dart' as di; -void main() => runApp(App()); +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await di.init(); + runApp(App()); +} \ No newline at end of file diff --git a/lib/src/app.dart b/lib/src/app.dart deleted file mode 100644 index 536d56c..0000000 --- a/lib/src/app.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:food_recipe/values/color_assets.dart'; - -import 'ui/favorite/favorite_screen.dart'; -import 'ui/home/home_screen.dart'; -import 'ui/infoapp/info_app_screen.dart'; -import 'ui/listmeals/list_meals_screen.dart'; -import 'ui/searchmeals/search_meals_screen.dart'; -import 'utils/utils.dart'; - -class App extends StatefulWidget { - @override - _AppState createState() => _AppState(); -} - -class _AppState extends State { - int _indexTabSelected = 0; - - @override - Widget build(BuildContext context) { - return MaterialApp( - debugShowCheckedModeBanner: false, - routes: { - navigatorListMeals: (context) { - return ListMealsScreen(); - }, - navigatorSearchMeals: (context) { - return SearchMealsScreen(); - }, - navigatorInfoApp: (context) { - return InfoAppScreen(); - }, - }, - theme: ThemeData( - primaryColor: ColorAssets.primarySwatchColor, - accentColor: ColorAssets.accentColor, - ), - home: Scaffold( - body: _buildBodyWidget(), - bottomNavigationBar: BottomNavigationBar( - currentIndex: _indexTabSelected, - items: [ - BottomNavigationBarItem( - title: Text("Home"), - icon: Icon(Icons.home), - ), - BottomNavigationBarItem( - title: Text("Favorite"), - icon: Icon(Icons.star), - ), - ], - onTap: (indexTab) { - setState(() => _indexTabSelected = indexTab); - }, - ), - ), - ); - } - - Widget _buildBodyWidget() { - if (_indexTabSelected == 0) { - return HomeScreen(); - } else { - return FavoriteScreen(); - } - } -} diff --git a/lib/src/blocs/detailmeals/detail_meals_bloc.dart b/lib/src/blocs/detailmeals/detail_meals_bloc.dart deleted file mode 100644 index 0e26f84..0000000 --- a/lib/src/blocs/detailmeals/detail_meals_bloc.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:food_recipe/src/models/lookupmealsbyid/lookup_meals_by_id.dart'; -import 'package:food_recipe/src/resources/food_api_repository.dart'; - -class DetailsMealsBloc { - final _foodApiRepository = FoodApiRepository(); - - dispose() { - // TODO: do something in here - } - - Future getDetailsMealsById(String idMeal) async { - return await _foodApiRepository.getLookupMealsById(idMeal); - } - -} - -final detailsMealsBloc = DetailsMealsBloc(); \ No newline at end of file diff --git a/lib/src/blocs/favorite/favorite_bloc.dart b/lib/src/blocs/favorite/favorite_bloc.dart deleted file mode 100644 index 95e9c81..0000000 --- a/lib/src/blocs/favorite/favorite_bloc.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:food_recipe/src/database/entity/favorite_meal.dart'; -import 'package:food_recipe/src/database/repository/favorite_meal_repository.dart'; -import 'package:rxdart/rxdart.dart'; - -import 'favorite_meals_bloc_model.dart'; - -class FavoriteBloc { - final _favoriteMealRepository = FavoriteMealRepository(); - var _publishSubjectListFavoriteMeal = - PublishSubject(); - - dispose() { - _publishSubjectListFavoriteMeal.close(); - } - - Observable get listFavoriteMeal => - _publishSubjectListFavoriteMeal.stream; - - getAllFavoriteMeals() async { - _publishSubjectListFavoriteMeal.sink - .add(FavoriteMealsBlocModel(isLoading: true)); - List listFavoriteMeals = - await _favoriteMealRepository.getAllFavoriteMeals(); - FavoriteMealsBlocModel favoriteMealsBlocModel = - FavoriteMealsBlocModel(listFavoriteMeals: listFavoriteMeals); - _publishSubjectListFavoriteMeal.sink.add(favoriteMealsBlocModel); - } - - removeFavoriteMealById(String id) async { - _publishSubjectListFavoriteMeal.sink.add(FavoriteMealsBlocModel(isLoading: true)); - await _favoriteMealRepository.deleteFavoriteMealById(id); - List listFavoriteMeals = - await _favoriteMealRepository.getAllFavoriteMeals(); - FavoriteMealsBlocModel favoriteMealsBlocModel = - FavoriteMealsBlocModel(listFavoriteMeals: listFavoriteMeals); - _publishSubjectListFavoriteMeal.sink.add(favoriteMealsBlocModel); - } - -} diff --git a/lib/src/blocs/favorite/favorite_meals_bloc_model.dart b/lib/src/blocs/favorite/favorite_meals_bloc_model.dart deleted file mode 100644 index 87c518c..0000000 --- a/lib/src/blocs/favorite/favorite_meals_bloc_model.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:food_recipe/src/database/entity/favorite_meal.dart'; - -class FavoriteMealsBlocModel { - List listFavoriteMeals; - bool isLoading; - - FavoriteMealsBlocModel({this.listFavoriteMeals, this.isLoading = false,}); - - @override - String toString() { - return 'FavoriteMealsBlocModel{listFavoriteMeals: $listFavoriteMeals, isLoading: $isLoading}'; - } - -} \ No newline at end of file diff --git a/lib/src/blocs/home/home_bloc.dart b/lib/src/blocs/home/home_bloc.dart deleted file mode 100644 index 1a637d3..0000000 --- a/lib/src/blocs/home/home_bloc.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:food_recipe/src/models/categories/categories.dart'; -import 'package:food_recipe/src/models/latest/latest_meals.dart'; -import 'package:food_recipe/src/resources/food_api_repository.dart'; - -class HomeBloc { - final _repository = FoodApiRepository(); - - dispose() { - // TODO: do something in here - } - - Future getLatestMeals() async { - return await _repository.getLatestMeals(); - } - - Future getCategories() async { - return await _repository.getCategories(); - } - -} - -final homeBloc = HomeBloc(); \ No newline at end of file diff --git a/lib/src/blocs/listmeals/list_meals_bloc.dart b/lib/src/blocs/listmeals/list_meals_bloc.dart deleted file mode 100644 index 25f1d6c..0000000 --- a/lib/src/blocs/listmeals/list_meals_bloc.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:food_recipe/src/database/entity/favorite_meal.dart'; -import 'package:food_recipe/src/database/repository/favorite_meal_repository.dart'; -import 'package:food_recipe/src/models/filtercategories/filter_categories.dart'; -import 'package:food_recipe/src/models/lookupmealsbyid/lookup_meals_by_id.dart'; -import 'package:food_recipe/src/resources/food_api_repository.dart'; - -class ListMealsBloc { - final _foodApiRepository = FoodApiRepository(); - final _favoriteMealRepository = FavoriteMealRepository(); - - dispose() { - // TODO: do something in here - } - - Future getFilterCategories(String category) async { - FilterCategories filterCategories = - await _foodApiRepository.getFilterByCategories(category); - List listFavoriteMeals = - await _favoriteMealRepository.getAllFavoriteMeals(); - List listFilterCategoryItems = - filterCategories.filterCategoryItems.where((item) { - bool isFavorite = false; - for (FavoriteMeal favoriteMeal in listFavoriteMeals) { - if (item.idMeal == favoriteMeal.idMeal) { - isFavorite = true; - break; - } - } - item.isFavorite = isFavorite; - return true; - }).toList(); - filterCategories.filterCategoryItems = listFilterCategoryItems; - return filterCategories; - } - - Future getDetailMealById(String id) async { - LookupMealsById lookupMealsById = await _foodApiRepository.getLookupMealsById(id); - return lookupMealsById; - } - - Future addFavoriteMeal(FavoriteMeal favoriteMeal) async { - return await _favoriteMealRepository.insertFavoriteMeal(favoriteMeal); - } - - Future deleteFavoriteMealById(String id) async { - return await _favoriteMealRepository.deleteFavoriteMealById(id); - } -} - -final listMealsBloc = ListMealsBloc(); diff --git a/lib/src/blocs/searchmeals/search_meals_bloc.dart b/lib/src/blocs/searchmeals/search_meals_bloc.dart deleted file mode 100644 index d2bba76..0000000 --- a/lib/src/blocs/searchmeals/search_meals_bloc.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'package:food_recipe/src/database/entity/favorite_meal.dart'; -import 'package:food_recipe/src/database/repository/favorite_meal_repository.dart'; -import 'package:food_recipe/src/models/lookupmealsbyid/lookup_meals_by_id.dart'; -import 'package:food_recipe/src/models/searchmeals/search_meals.dart'; -import 'package:food_recipe/src/resources/food_api_repository.dart'; -import 'package:rxdart/rxdart.dart'; - -class SearchMealsBloc { - final _publishSubjectSearchMealsByKeyword = PublishSubject(); - final _foodApiRepository = FoodApiRepository(); - final _favoriteMealRepository = FavoriteMealRepository(); - - dispose() { - _publishSubjectSearchMealsByKeyword.close(); - } - - Observable get resultSearchMealsByKeyword => - _publishSubjectSearchMealsByKeyword.stream; - - searchMealsByKeyword(String keyword) async { - _publishSubjectSearchMealsByKeyword.sink.add(SearchMeals(isLoading: true)); - if (keyword.trim().isEmpty) { - _publishSubjectSearchMealsByKeyword.sink - .add(SearchMeals(searchMealsItems: [])); - } else { - SearchMeals searchMeals = - await _foodApiRepository.getSearchMealsByKeyword(keyword); - List listFavoriteMeals = - await _favoriteMealRepository.getAllFavoriteMeals(); - if (searchMeals.searchMealsItems == null) { - _publishSubjectSearchMealsByKeyword.sink.add(SearchMeals(searchMealsItems: [])); - return; - } - List listSearchMealsItem = - searchMeals.searchMealsItems.where((searchMealsItem) { - for (FavoriteMeal favoriteMeal in listFavoriteMeals) { - if (favoriteMeal.idMeal == searchMealsItem.idMeal) { - searchMealsItem.isFavorite = true; - break; - } - } - return true; - }).toList(); - searchMeals.searchMealsItems = listSearchMealsItem; - _publishSubjectSearchMealsByKeyword.sink.add(searchMeals); - } - } - - Future getDetailMealById(String id) async { - LookupMealsById lookupMealsById = - await _foodApiRepository.getLookupMealsById(id); - return lookupMealsById; - } - - Future addFavoriteMeal(FavoriteMeal favoriteMeal) async { - return await _favoriteMealRepository.insertFavoriteMeal(favoriteMeal); - } - - Future deleteFavoriteMealById(String id) async { - return await _favoriteMealRepository.deleteFavoriteMealById(id); - } -} diff --git a/lib/src/database/dao/favorite_meal_dao.dart b/lib/src/database/dao/favorite_meal_dao.dart deleted file mode 100644 index 7b3134e..0000000 --- a/lib/src/database/dao/favorite_meal_dao.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:food_recipe/src/database/entity/favorite_meal.dart'; - -import '../database.dart'; - -class FavoriteMealDao { - final dbProvider = DatabaseProvider.dbProvider; - - Future createFavoriteMeal(FavoriteMeal favoriteMeal) async { - final db = await dbProvider.database; - var result = db.insert(favoriteTable, favoriteMeal.toJson()); - return result; - } - - Future> getAllFavoriteMeals() async { - final db = await dbProvider.database; - List> result; - result = await db.query(favoriteTable); - List listFavoriteMeals = result.isNotEmpty - ? result.map((resultMap) { - return FavoriteMeal.fromJson(resultMap); - }).toList() - : []; - return listFavoriteMeals; - } - - Future> getFavoriteMealById(String id) async { - final db = await dbProvider.database; - List> result; - result = await db.query(favoriteTable, where: "idMeal = ?", whereArgs: [id]); - List listFavoriteMeals = result.isNotEmpty - ? result.map((resultMap) { - return FavoriteMeal.fromJson(resultMap); - }).toList() - : []; - return listFavoriteMeals; - } - - Future deleteFavoriteMeal(String id) async { - final db = await dbProvider.database; - var result = - await db.delete(favoriteTable, where: "idMeal = ?", whereArgs: [id]); - return result; - } -} diff --git a/lib/src/database/database.dart b/lib/src/database/database.dart deleted file mode 100644 index df49370..0000000 --- a/lib/src/database/database.dart +++ /dev/null @@ -1,88 +0,0 @@ -import 'dart:io'; - -import 'package:path/path.dart'; -import 'package:sqflite/sqflite.dart'; -import 'package:path_provider/path_provider.dart'; - -final favoriteTable = "favorite"; - -class DatabaseProvider { - static final DatabaseProvider dbProvider = DatabaseProvider(); - - Database _database; - - Future get database async { - if (_database != null) return _database; - _database = await createDatabase(); - return _database; - } - - createDatabase() async { - Directory documentsDirectory = await getApplicationDocumentsDirectory(); - String path = join(documentsDirectory.path, "FoodRecipe.db"); - - var database = await openDatabase( - path, version: 1, onCreate: initDb, onUpgrade: onUpgrade); - return database; - } - - void onUpgrade(Database database, int oldVersion, int newVersion) { - if (newVersion > oldVersion) { - // TODO: do something in here if needed to upgrade database version - } - } - - void initDb(Database database, int version) async { - String queryCreateTableFavorite = "CREATE TABLE $favoriteTable (" - "idMeal TEXT PRIMARY KEY, " - "strMeal TEXT, " - "strMealThumb TEXT, " - "strCategory TEXT, " - "strTags TEXT, " - "strYoutube TEXT, " - "strArea TEXT, " - "strInstructions TEXT, " - "strIngredient1 TEXT, " - "strIngredient2 TEXT, " - "strIngredient3 TEXT, " - "strIngredient4 TEXT, " - "strIngredient5 TEXT, " - "strIngredient6 TEXT, " - "strIngredient7 TEXT, " - "strIngredient8 TEXT, " - "strIngredient9 TEXT, " - "strIngredient10 TEXT, " - "strIngredient11 TEXT, " - "strIngredient12 TEXT, " - "strIngredient13 TEXT, " - "strIngredient14 TEXT, " - "strIngredient15 TEXT, " - "strIngredient16 TEXT, " - "strIngredient17 TEXT, " - "strIngredient18 TEXT, " - "strIngredient19 TEXT, " - "strIngredient20 TEXT, " - "strMeasure1 TEXT, " - "strMeasure2 TEXT, " - "strMeasure3 TEXT, " - "strMeasure4 TEXT, " - "strMeasure5 TEXT, " - "strMeasure6 TEXT, " - "strMeasure7 TEXT, " - "strMeasure8 TEXT, " - "strMeasure9 TEXT, " - "strMeasure10 TEXT, " - "strMeasure11 TEXT, " - "strMeasure12 TEXT, " - "strMeasure13 TEXT, " - "strMeasure14 TEXT, " - "strMeasure15 TEXT, " - "strMeasure16 TEXT, " - "strMeasure17 TEXT, " - "strMeasure18 TEXT, " - "strMeasure19 TEXT, " - "strMeasure20 TEXT " - ")"; - await database.execute(queryCreateTableFavorite); - } -} diff --git a/lib/src/database/entity/favorite_meal.dart b/lib/src/database/entity/favorite_meal.dart deleted file mode 100644 index 3f2d999..0000000 --- a/lib/src/database/entity/favorite_meal.dart +++ /dev/null @@ -1,115 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; - -part 'favorite_meal.g.dart'; - -@JsonSerializable() -class FavoriteMeal { - String idMeal; - String strMeal; - String strMealThumb; - String strCategory; - String strTags; - String strYoutube; - String strArea; - String strInstructions; - String strIngredient1; - String strIngredient2; - String strIngredient3; - String strIngredient4; - String strIngredient5; - String strIngredient6; - String strIngredient7; - String strIngredient8; - String strIngredient9; - String strIngredient10; - String strIngredient11; - String strIngredient12; - String strIngredient13; - String strIngredient14; - String strIngredient15; - String strIngredient16; - String strIngredient17; - String strIngredient18; - String strIngredient19; - String strIngredient20; - String strMeasure1; - String strMeasure2; - String strMeasure3; - String strMeasure4; - String strMeasure5; - String strMeasure6; - String strMeasure7; - String strMeasure8; - String strMeasure9; - String strMeasure10; - String strMeasure11; - String strMeasure12; - String strMeasure13; - String strMeasure14; - String strMeasure15; - String strMeasure16; - String strMeasure17; - String strMeasure18; - String strMeasure19; - String strMeasure20; - - FavoriteMeal( - {this.idMeal, - this.strMeal, - this.strMealThumb, - this.strCategory, - this.strTags, - this.strYoutube, - this.strArea, - this.strInstructions, - this.strIngredient1, - this.strIngredient2, - this.strIngredient3, - this.strIngredient4, - this.strIngredient5, - this.strIngredient6, - this.strIngredient7, - this.strIngredient8, - this.strIngredient9, - this.strIngredient10, - this.strIngredient11, - this.strIngredient12, - this.strIngredient13, - this.strIngredient14, - this.strIngredient15, - this.strIngredient16, - this.strIngredient17, - this.strIngredient18, - this.strIngredient19, - this.strIngredient20, - this.strMeasure1, - this.strMeasure2, - this.strMeasure3, - this.strMeasure4, - this.strMeasure5, - this.strMeasure6, - this.strMeasure7, - this.strMeasure8, - this.strMeasure9, - this.strMeasure10, - this.strMeasure11, - this.strMeasure12, - this.strMeasure13, - this.strMeasure14, - this.strMeasure15, - this.strMeasure16, - this.strMeasure17, - this.strMeasure18, - this.strMeasure19, - this.strMeasure20}); - - @override - String toString() { - return 'FavoriteMeal{idMeal: $idMeal, strMeal: $strMeal, strMealThumb: $strMealThumb, strCategory: $strCategory, strTags: $strTags, strYoutube: $strYoutube, strArea: $strArea, strInstructions: $strInstructions, strIngredient1: $strIngredient1, strIngredient2: $strIngredient2, strIngredient3: $strIngredient3, strIngredient4: $strIngredient4, strIngredient5: $strIngredient5, strIngredient6: $strIngredient6, strIngredient7: $strIngredient7, strIngredient8: $strIngredient8, strIngredient9: $strIngredient9, strIngredient10: $strIngredient10, strIngredient11: $strIngredient11, strIngredient12: $strIngredient12, strIngredient13: $strIngredient13, strIngredient14: $strIngredient14, strIngredient15: $strIngredient15, strIngredient16: $strIngredient16, strIngredient17: $strIngredient17, strIngredient18: $strIngredient18, strIngredient19: $strIngredient19, strIngredient20: $strIngredient20, strMeasure1: $strMeasure1, strMeasure2: $strMeasure2, strMeasure3: $strMeasure3, strMeasure4: $strMeasure4, strMeasure5: $strMeasure5, strMeasure6: $strMeasure6, strMeasure7: $strMeasure7, strMeasure8: $strMeasure8, strMeasure9: $strMeasure9, strMeasure10: $strMeasure10, strMeasure11: $strMeasure11, strMeasure12: $strMeasure12, strMeasure13: $strMeasure13, strMeasure14: $strMeasure14, strMeasure15: $strMeasure15, strMeasure16: $strMeasure16, strMeasure17: $strMeasure17, strMeasure18: $strMeasure18, strMeasure19: $strMeasure19, strMeasure20: $strMeasure20}'; - } - - factory FavoriteMeal.fromJson(Map json) => - _$FavoriteMealFromJson(json); - - Map toJson() => _$FavoriteMealToJson(this); -} diff --git a/lib/src/database/entity/favorite_meal.g.dart b/lib/src/database/entity/favorite_meal.g.dart deleted file mode 100644 index 655f4d7..0000000 --- a/lib/src/database/entity/favorite_meal.g.dart +++ /dev/null @@ -1,111 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'favorite_meal.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -FavoriteMeal _$FavoriteMealFromJson(Map json) { - return FavoriteMeal( - idMeal: json['idMeal'] as String, - strMeal: json['strMeal'] as String, - strMealThumb: json['strMealThumb'] as String, - strCategory: json['strCategory'] as String, - strTags: json['strTags'] as String, - strYoutube: json['strYoutube'] as String, - strArea: json['strArea'] as String, - strInstructions: json['strInstructions'] as String, - strIngredient1: json['strIngredient1'] as String, - strIngredient2: json['strIngredient2'] as String, - strIngredient3: json['strIngredient3'] as String, - strIngredient4: json['strIngredient4'] as String, - strIngredient5: json['strIngredient5'] as String, - strIngredient6: json['strIngredient6'] as String, - strIngredient7: json['strIngredient7'] as String, - strIngredient8: json['strIngredient8'] as String, - strIngredient9: json['strIngredient9'] as String, - strIngredient10: json['strIngredient10'] as String, - strIngredient11: json['strIngredient11'] as String, - strIngredient12: json['strIngredient12'] as String, - strIngredient13: json['strIngredient13'] as String, - strIngredient14: json['strIngredient14'] as String, - strIngredient15: json['strIngredient15'] as String, - strIngredient16: json['strIngredient16'] as String, - strIngredient17: json['strIngredient17'] as String, - strIngredient18: json['strIngredient18'] as String, - strIngredient19: json['strIngredient19'] as String, - strIngredient20: json['strIngredient20'] as String, - strMeasure1: json['strMeasure1'] as String, - strMeasure2: json['strMeasure2'] as String, - strMeasure3: json['strMeasure3'] as String, - strMeasure4: json['strMeasure4'] as String, - strMeasure5: json['strMeasure5'] as String, - strMeasure6: json['strMeasure6'] as String, - strMeasure7: json['strMeasure7'] as String, - strMeasure8: json['strMeasure8'] as String, - strMeasure9: json['strMeasure9'] as String, - strMeasure10: json['strMeasure10'] as String, - strMeasure11: json['strMeasure11'] as String, - strMeasure12: json['strMeasure12'] as String, - strMeasure13: json['strMeasure13'] as String, - strMeasure14: json['strMeasure14'] as String, - strMeasure15: json['strMeasure15'] as String, - strMeasure16: json['strMeasure16'] as String, - strMeasure17: json['strMeasure17'] as String, - strMeasure18: json['strMeasure18'] as String, - strMeasure19: json['strMeasure19'] as String, - strMeasure20: json['strMeasure20'] as String); -} - -Map _$FavoriteMealToJson(FavoriteMeal instance) => - { - 'idMeal': instance.idMeal, - 'strMeal': instance.strMeal, - 'strMealThumb': instance.strMealThumb, - 'strCategory': instance.strCategory, - 'strTags': instance.strTags, - 'strYoutube': instance.strYoutube, - 'strArea': instance.strArea, - 'strInstructions': instance.strInstructions, - 'strIngredient1': instance.strIngredient1, - 'strIngredient2': instance.strIngredient2, - 'strIngredient3': instance.strIngredient3, - 'strIngredient4': instance.strIngredient4, - 'strIngredient5': instance.strIngredient5, - 'strIngredient6': instance.strIngredient6, - 'strIngredient7': instance.strIngredient7, - 'strIngredient8': instance.strIngredient8, - 'strIngredient9': instance.strIngredient9, - 'strIngredient10': instance.strIngredient10, - 'strIngredient11': instance.strIngredient11, - 'strIngredient12': instance.strIngredient12, - 'strIngredient13': instance.strIngredient13, - 'strIngredient14': instance.strIngredient14, - 'strIngredient15': instance.strIngredient15, - 'strIngredient16': instance.strIngredient16, - 'strIngredient17': instance.strIngredient17, - 'strIngredient18': instance.strIngredient18, - 'strIngredient19': instance.strIngredient19, - 'strIngredient20': instance.strIngredient20, - 'strMeasure1': instance.strMeasure1, - 'strMeasure2': instance.strMeasure2, - 'strMeasure3': instance.strMeasure3, - 'strMeasure4': instance.strMeasure4, - 'strMeasure5': instance.strMeasure5, - 'strMeasure6': instance.strMeasure6, - 'strMeasure7': instance.strMeasure7, - 'strMeasure8': instance.strMeasure8, - 'strMeasure9': instance.strMeasure9, - 'strMeasure10': instance.strMeasure10, - 'strMeasure11': instance.strMeasure11, - 'strMeasure12': instance.strMeasure12, - 'strMeasure13': instance.strMeasure13, - 'strMeasure14': instance.strMeasure14, - 'strMeasure15': instance.strMeasure15, - 'strMeasure16': instance.strMeasure16, - 'strMeasure17': instance.strMeasure17, - 'strMeasure18': instance.strMeasure18, - 'strMeasure19': instance.strMeasure19, - 'strMeasure20': instance.strMeasure20 - }; diff --git a/lib/src/database/repository/favorite_meal_repository.dart b/lib/src/database/repository/favorite_meal_repository.dart deleted file mode 100644 index 09a620e..0000000 --- a/lib/src/database/repository/favorite_meal_repository.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:food_recipe/src/database/dao/favorite_meal_dao.dart'; -import 'package:food_recipe/src/database/entity/favorite_meal.dart'; - -class FavoriteMealRepository { - final _favoriteMealDao = FavoriteMealDao(); - - Future insertFavoriteMeal(FavoriteMeal favoriteMeal) => - _favoriteMealDao.createFavoriteMeal(favoriteMeal); - - Future> getAllFavoriteMeals() => _favoriteMealDao.getAllFavoriteMeals(); - - Future> getFavoriteMealsById(String id) => _favoriteMealDao.getFavoriteMealById(id); - - Future deleteFavoriteMealById(String id) => _favoriteMealDao.deleteFavoriteMeal(id); - -} diff --git a/lib/src/models/area/area_meals.dart b/lib/src/models/area/area_meals.dart deleted file mode 100644 index d86b13b..0000000 --- a/lib/src/models/area/area_meals.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -part 'area_meals.g.dart'; - -@JsonSerializable() -class AreaMeals { - @JsonKey(name: "meals") - List areaMealsItems; - - AreaMeals({this.areaMealsItems}); - - @override - String toString() { - return 'AreaMeals{areaMealsItems: $areaMealsItems}'; - } - - factory AreaMeals.fromJson(Map json) => _$AreaMealsFromJson(json); - - Map toJson() => _$AreaMealsToJson(this); - -} - -@JsonSerializable() -class AreaMealsItem { - String strArea; - - AreaMealsItem({this.strArea}); - - @override - String toString() { - return 'AreaMealsItem{strArea: $strArea}'; - } - - factory AreaMealsItem.fromJson(Map json) => _$AreaMealsItemFromJson(json); - - Map toJson() => _$AreaMealsItemToJson(this); - -} \ No newline at end of file diff --git a/lib/src/models/area/area_meals.g.dart b/lib/src/models/area/area_meals.g.dart deleted file mode 100644 index bcd690c..0000000 --- a/lib/src/models/area/area_meals.g.dart +++ /dev/null @@ -1,26 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'area_meals.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -AreaMeals _$AreaMealsFromJson(Map json) { - return AreaMeals( - areaMealsItems: (json['meals'] as List) - ?.map((e) => e == null - ? null - : AreaMealsItem.fromJson(e as Map)) - ?.toList()); -} - -Map _$AreaMealsToJson(AreaMeals instance) => - {'meals': instance.areaMealsItems}; - -AreaMealsItem _$AreaMealsItemFromJson(Map json) { - return AreaMealsItem(strArea: json['strArea'] as String); -} - -Map _$AreaMealsItemToJson(AreaMealsItem instance) => - {'strArea': instance.strArea}; diff --git a/lib/src/models/categories/categories.dart b/lib/src/models/categories/categories.dart deleted file mode 100644 index 582d4e1..0000000 --- a/lib/src/models/categories/categories.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -part 'categories.g.dart'; - -@JsonSerializable() -class Categories { - @JsonKey(name: "categories") - List categoryItems; - - Categories({this.categoryItems}); - - @override - String toString() { - return 'Categories{categoryItems: $categoryItems}'; - } - - factory Categories.fromJson(Map json) => _$CategoriesFromJson(json); - - Map toJson() => _$CategoriesToJson(this); - -} - -@JsonSerializable() -class CategoryItem { - String idCategory; - String strCategory; - String strCategoryThumb; - String strCategoryDescription; - - CategoryItem({this.idCategory, this.strCategory, this.strCategoryThumb, this.strCategoryDescription}); - - @override - String toString() { - return 'CategoryItem{idCategory: $idCategory, strCategory: $strCategory, strCategoryThumb: $strCategoryThumb, strCategoryDescription: $strCategoryDescription}'; - } - - factory CategoryItem.fromJson(Map json) => _$CategoryItemFromJson(json); - - Map toJson() => _$CategoryItemToJson(this); -} \ No newline at end of file diff --git a/lib/src/models/categories/categories.g.dart b/lib/src/models/categories/categories.g.dart deleted file mode 100644 index aef9ceb..0000000 --- a/lib/src/models/categories/categories.g.dart +++ /dev/null @@ -1,35 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'categories.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -Categories _$CategoriesFromJson(Map json) { - return Categories( - categoryItems: (json['categories'] as List) - ?.map((e) => e == null - ? null - : CategoryItem.fromJson(e as Map)) - ?.toList()); -} - -Map _$CategoriesToJson(Categories instance) => - {'categories': instance.categoryItems}; - -CategoryItem _$CategoryItemFromJson(Map json) { - return CategoryItem( - idCategory: json['idCategory'] as String, - strCategory: json['strCategory'] as String, - strCategoryThumb: json['strCategoryThumb'] as String, - strCategoryDescription: json['strCategoryDescription'] as String); -} - -Map _$CategoryItemToJson(CategoryItem instance) => - { - 'idCategory': instance.idCategory, - 'strCategory': instance.strCategory, - 'strCategoryThumb': instance.strCategoryThumb, - 'strCategoryDescription': instance.strCategoryDescription - }; diff --git a/lib/src/models/filterarea/filter_area_meals.dart b/lib/src/models/filterarea/filter_area_meals.dart deleted file mode 100644 index 23edae3..0000000 --- a/lib/src/models/filterarea/filter_area_meals.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -part 'filter_area_meals.g.dart'; - -@JsonSerializable() -class FilterAreaMeals { - @JsonKey(name: "meals") - List filterAreaMealsItems; - - FilterAreaMeals({this.filterAreaMealsItems}); - - @override - String toString() { - return 'FilterAreaMeals{filterAreaMealsItems: $filterAreaMealsItems}'; - } - - factory FilterAreaMeals.fromJson(Map json) => _$FilterAreaMealsFromJson(json); - - Map toJson() => _$FilterAreaMealsToJson(this); - -} - -@JsonSerializable() -class FilterAreaMealsItem { - String strMeal; - String strMealThumb; - String idMeal; - - FilterAreaMealsItem({this.strMeal, this.strMealThumb, this.idMeal}); - - @override - String toString() { - return 'FilterAreaMealsItem{strMeal: $strMeal, strMealThumb: $strMealThumb, idMeal: $idMeal}'; - } - - factory FilterAreaMealsItem.fromJson(Map json) => _$FilterAreaMealsItemFromJson(json); - - Map toJson() => _$FilterAreaMealsItemToJson(this); - - -} \ No newline at end of file diff --git a/lib/src/models/filterarea/filter_area_meals.g.dart b/lib/src/models/filterarea/filter_area_meals.g.dart deleted file mode 100644 index 3fff3d7..0000000 --- a/lib/src/models/filterarea/filter_area_meals.g.dart +++ /dev/null @@ -1,34 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'filter_area_meals.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -FilterAreaMeals _$FilterAreaMealsFromJson(Map json) { - return FilterAreaMeals( - filterAreaMealsItems: (json['meals'] as List) - ?.map((e) => e == null - ? null - : FilterAreaMealsItem.fromJson(e as Map)) - ?.toList()); -} - -Map _$FilterAreaMealsToJson(FilterAreaMeals instance) => - {'meals': instance.filterAreaMealsItems}; - -FilterAreaMealsItem _$FilterAreaMealsItemFromJson(Map json) { - return FilterAreaMealsItem( - strMeal: json['strMeal'] as String, - strMealThumb: json['strMealThumb'] as String, - idMeal: json['idMeal'] as String); -} - -Map _$FilterAreaMealsItemToJson( - FilterAreaMealsItem instance) => - { - 'strMeal': instance.strMeal, - 'strMealThumb': instance.strMealThumb, - 'idMeal': instance.idMeal - }; diff --git a/lib/src/models/filtercategories/filter_categories.dart b/lib/src/models/filtercategories/filter_categories.dart deleted file mode 100644 index c8e11b8..0000000 --- a/lib/src/models/filtercategories/filter_categories.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -part 'filter_categories.g.dart'; - -@JsonSerializable() -class FilterCategories { - @JsonKey(name: "meals") - List filterCategoryItems; - - FilterCategories({this.filterCategoryItems}); - - @override - String toString() { - return 'FilterCategories{filterCategoryItems: $filterCategoryItems}'; - } - - factory FilterCategories.fromJson(Map json) => _$FilterCategoriesFromJson(json); - - Map toJson() => _$FilterCategoriesToJson(this); - -} - -@JsonSerializable() -class FilterCategoryItem { - String strMeal; - String strMealThumb; - String idMeal; - @JsonKey(ignore: true) - bool isFavorite; - - FilterCategoryItem({this.strMeal, this.strMealThumb, this.idMeal, this.isFavorite = false}); - - @override - String toString() { - return 'FilterCategoryItem{strMeal: $strMeal, strMealThumb: $strMealThumb, idMeal: $idMeal}'; - } - - factory FilterCategoryItem.fromJson(Map json) => _$FilterCategoryItemFromJson(json); - - Map toJson() => _$FilterCategoryItemToJson(this); - -} \ No newline at end of file diff --git a/lib/src/models/filtercategories/filter_categories.g.dart b/lib/src/models/filtercategories/filter_categories.g.dart deleted file mode 100644 index 44da1f1..0000000 --- a/lib/src/models/filtercategories/filter_categories.g.dart +++ /dev/null @@ -1,33 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'filter_categories.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -FilterCategories _$FilterCategoriesFromJson(Map json) { - return FilterCategories( - filterCategoryItems: (json['meals'] as List) - ?.map((e) => e == null - ? null - : FilterCategoryItem.fromJson(e as Map)) - ?.toList()); -} - -Map _$FilterCategoriesToJson(FilterCategories instance) => - {'meals': instance.filterCategoryItems}; - -FilterCategoryItem _$FilterCategoryItemFromJson(Map json) { - return FilterCategoryItem( - strMeal: json['strMeal'] as String, - strMealThumb: json['strMealThumb'] as String, - idMeal: json['idMeal'] as String); -} - -Map _$FilterCategoryItemToJson(FilterCategoryItem instance) => - { - 'strMeal': instance.strMeal, - 'strMealThumb': instance.strMealThumb, - 'idMeal': instance.idMeal - }; diff --git a/lib/src/models/latest/latest_meals.dart b/lib/src/models/latest/latest_meals.dart deleted file mode 100644 index 1568448..0000000 --- a/lib/src/models/latest/latest_meals.dart +++ /dev/null @@ -1,139 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -part 'latest_meals.g.dart'; - -@JsonSerializable() -class LatestMeals { - @JsonKey(name: "meals") - List latestMealsItems; - - LatestMeals({this.latestMealsItems}); - - @override - String toString() { - return 'LatestMeals{latestMealsItems: $latestMealsItems}'; - } - - factory LatestMeals.fromJson(Map json) => - _$LatestMealsFromJson(json); - - Map toJson() => _$LatestMealsToJson(this); -} - -@JsonSerializable() -class LatestMealsItem { - String idMeal; - String strMeal; - String strDrinkAlternate; - String strCategory; - String strArea; - String strInstructions; - String strMealThumb; - String strTags; - String strYoutube; - String strIngredient1; - String strIngredient2; - String strIngredient3; - String strIngredient4; - String strIngredient5; - String strIngredient6; - String strIngredient7; - String strIngredient8; - String strIngredient9; - String strIngredient10; - String strIngredient11; - String strIngredient12; - String strIngredient13; - String strIngredient14; - String strIngredient15; - String strIngredient16; - String strIngredient17; - String strIngredient18; - String strIngredient19; - String strIngredient20; - String strMeasure1; - String strMeasure2; - String strMeasure3; - String strMeasure4; - String strMeasure5; - String strMeasure6; - String strMeasure7; - String strMeasure8; - String strMeasure9; - String strMeasure10; - String strMeasure11; - String strMeasure12; - String strMeasure13; - String strMeasure14; - String strMeasure15; - String strMeasure16; - String strMeasure17; - String strMeasure18; - String strMeasure19; - String strMeasure20; - String strSource; - String dateModified; - - LatestMealsItem( - {this.idMeal, - this.strMeal, - this.strDrinkAlternate, - this.strCategory, - this.strArea, - this.strInstructions, - this.strMealThumb, - this.strTags, - this.strYoutube, - this.strIngredient1, - this.strIngredient2, - this.strIngredient3, - this.strIngredient4, - this.strIngredient5, - this.strIngredient6, - this.strIngredient7, - this.strIngredient8, - this.strIngredient9, - this.strIngredient10, - this.strIngredient11, - this.strIngredient12, - this.strIngredient13, - this.strIngredient14, - this.strIngredient15, - this.strIngredient16, - this.strIngredient17, - this.strIngredient18, - this.strIngredient19, - this.strIngredient20, - this.strMeasure1, - this.strMeasure2, - this.strMeasure3, - this.strMeasure4, - this.strMeasure5, - this.strMeasure6, - this.strMeasure7, - this.strMeasure8, - this.strMeasure9, - this.strMeasure10, - this.strMeasure11, - this.strMeasure12, - this.strMeasure13, - this.strMeasure14, - this.strMeasure15, - this.strMeasure16, - this.strMeasure17, - this.strMeasure18, - this.strMeasure19, - this.strMeasure20, - this.strSource, - this.dateModified}); - - - @override - String toString() { - return 'LatestMealsItem{idMeal: $idMeal, strMeal: $strMeal, strDrinkAlternate: $strDrinkAlternate, strCategory: $strCategory, strArea: $strArea, strInstructions: $strInstructions, strMealThumb: $strMealThumb, strTags: $strTags, strYoutube: $strYoutube, strIngredient1: $strIngredient1, strIngredient2: $strIngredient2, strIngredient3: $strIngredient3, strIngredient4: $strIngredient4, strIngredient5: $strIngredient5, strIngredient6: $strIngredient6, strIngredient7: $strIngredient7, strIngredient8: $strIngredient8, strIngredient9: $strIngredient9, strIngredient10: $strIngredient10, strIngredient11: $strIngredient11, strIngredient12: $strIngredient12, strIngredient13: $strIngredient13, strIngredient14: $strIngredient14, strIngredient15: $strIngredient15, strIngredient16: $strIngredient16, strIngredient17: $strIngredient17, strIngredient18: $strIngredient18, strIngredient19: $strIngredient19, strIngredient20: $strIngredient20, strMeasure1: $strMeasure1, strMeasure2: $strMeasure2, strMeasure3: $strMeasure3, strMeasure4: $strMeasure4, strMeasure5: $strMeasure5, strMeasure6: $strMeasure6, strMeasure7: $strMeasure7, strMeasure8: $strMeasure8, strMeasure9: $strMeasure9, strMeasure10: $strMeasure10, strMeasure11: $strMeasure11, strMeasure12: $strMeasure12, strMeasure13: $strMeasure13, strMeasure14: $strMeasure14, strMeasure15: $strMeasure15, strMeasure16: $strMeasure16, strMeasure17: $strMeasure17, strMeasure18: $strMeasure18, strMeasure19: $strMeasure19, strMeasure20: $strMeasure20, strSource: $strSource, dateModified: $dateModified}'; - } - - factory LatestMealsItem.fromJson(Map json) => _$LatestMealsItemFromJson(json); - - Map toJson() => _$LatestMealsItemToJson(this); - -} diff --git a/lib/src/models/latest/latest_meals.g.dart b/lib/src/models/latest/latest_meals.g.dart deleted file mode 100644 index 3070365..0000000 --- a/lib/src/models/latest/latest_meals.g.dart +++ /dev/null @@ -1,129 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'latest_meals.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -LatestMeals _$LatestMealsFromJson(Map json) { - return LatestMeals( - latestMealsItems: (json['meals'] as List) - ?.map((e) => e == null - ? null - : LatestMealsItem.fromJson(e as Map)) - ?.toList()); -} - -Map _$LatestMealsToJson(LatestMeals instance) => - {'meals': instance.latestMealsItems}; - -LatestMealsItem _$LatestMealsItemFromJson(Map json) { - return LatestMealsItem( - idMeal: json['idMeal'] as String, - strMeal: json['strMeal'] as String, - strDrinkAlternate: json['strDrinkAlternate'] as String, - strCategory: json['strCategory'] as String, - strArea: json['strArea'] as String, - strInstructions: json['strInstructions'] as String, - strMealThumb: json['strMealThumb'] as String, - strTags: json['strTags'] as String, - strYoutube: json['strYoutube'] as String, - strIngredient1: json['strIngredient1'] as String, - strIngredient2: json['strIngredient2'] as String, - strIngredient3: json['strIngredient3'] as String, - strIngredient4: json['strIngredient4'] as String, - strIngredient5: json['strIngredient5'] as String, - strIngredient6: json['strIngredient6'] as String, - strIngredient7: json['strIngredient7'] as String, - strIngredient8: json['strIngredient8'] as String, - strIngredient9: json['strIngredient9'] as String, - strIngredient10: json['strIngredient10'] as String, - strIngredient11: json['strIngredient11'] as String, - strIngredient12: json['strIngredient12'] as String, - strIngredient13: json['strIngredient13'] as String, - strIngredient14: json['strIngredient14'] as String, - strIngredient15: json['strIngredient15'] as String, - strIngredient16: json['strIngredient16'] as String, - strIngredient17: json['strIngredient17'] as String, - strIngredient18: json['strIngredient18'] as String, - strIngredient19: json['strIngredient19'] as String, - strIngredient20: json['strIngredient20'] as String, - strMeasure1: json['strMeasure1'] as String, - strMeasure2: json['strMeasure2'] as String, - strMeasure3: json['strMeasure3'] as String, - strMeasure4: json['strMeasure4'] as String, - strMeasure5: json['strMeasure5'] as String, - strMeasure6: json['strMeasure6'] as String, - strMeasure7: json['strMeasure7'] as String, - strMeasure8: json['strMeasure8'] as String, - strMeasure9: json['strMeasure9'] as String, - strMeasure10: json['strMeasure10'] as String, - strMeasure11: json['strMeasure11'] as String, - strMeasure12: json['strMeasure12'] as String, - strMeasure13: json['strMeasure13'] as String, - strMeasure14: json['strMeasure14'] as String, - strMeasure15: json['strMeasure15'] as String, - strMeasure16: json['strMeasure16'] as String, - strMeasure17: json['strMeasure17'] as String, - strMeasure18: json['strMeasure18'] as String, - strMeasure19: json['strMeasure19'] as String, - strMeasure20: json['strMeasure20'] as String, - strSource: json['strSource'] as String, - dateModified: json['dateModified'] as String); -} - -Map _$LatestMealsItemToJson(LatestMealsItem instance) => - { - 'idMeal': instance.idMeal, - 'strMeal': instance.strMeal, - 'strDrinkAlternate': instance.strDrinkAlternate, - 'strCategory': instance.strCategory, - 'strArea': instance.strArea, - 'strInstructions': instance.strInstructions, - 'strMealThumb': instance.strMealThumb, - 'strTags': instance.strTags, - 'strYoutube': instance.strYoutube, - 'strIngredient1': instance.strIngredient1, - 'strIngredient2': instance.strIngredient2, - 'strIngredient3': instance.strIngredient3, - 'strIngredient4': instance.strIngredient4, - 'strIngredient5': instance.strIngredient5, - 'strIngredient6': instance.strIngredient6, - 'strIngredient7': instance.strIngredient7, - 'strIngredient8': instance.strIngredient8, - 'strIngredient9': instance.strIngredient9, - 'strIngredient10': instance.strIngredient10, - 'strIngredient11': instance.strIngredient11, - 'strIngredient12': instance.strIngredient12, - 'strIngredient13': instance.strIngredient13, - 'strIngredient14': instance.strIngredient14, - 'strIngredient15': instance.strIngredient15, - 'strIngredient16': instance.strIngredient16, - 'strIngredient17': instance.strIngredient17, - 'strIngredient18': instance.strIngredient18, - 'strIngredient19': instance.strIngredient19, - 'strIngredient20': instance.strIngredient20, - 'strMeasure1': instance.strMeasure1, - 'strMeasure2': instance.strMeasure2, - 'strMeasure3': instance.strMeasure3, - 'strMeasure4': instance.strMeasure4, - 'strMeasure5': instance.strMeasure5, - 'strMeasure6': instance.strMeasure6, - 'strMeasure7': instance.strMeasure7, - 'strMeasure8': instance.strMeasure8, - 'strMeasure9': instance.strMeasure9, - 'strMeasure10': instance.strMeasure10, - 'strMeasure11': instance.strMeasure11, - 'strMeasure12': instance.strMeasure12, - 'strMeasure13': instance.strMeasure13, - 'strMeasure14': instance.strMeasure14, - 'strMeasure15': instance.strMeasure15, - 'strMeasure16': instance.strMeasure16, - 'strMeasure17': instance.strMeasure17, - 'strMeasure18': instance.strMeasure18, - 'strMeasure19': instance.strMeasure19, - 'strMeasure20': instance.strMeasure20, - 'strSource': instance.strSource, - 'dateModified': instance.dateModified - }; diff --git a/lib/src/models/lookupmealsbyid/lookup_meals_by_id.dart b/lib/src/models/lookupmealsbyid/lookup_meals_by_id.dart deleted file mode 100644 index 6ee8572..0000000 --- a/lib/src/models/lookupmealsbyid/lookup_meals_by_id.dart +++ /dev/null @@ -1,138 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -part 'lookup_meals_by_id.g.dart'; - -@JsonSerializable() -class LookupMealsById { - @JsonKey(name: "meals") - List lookupMealsbyIdItems; - - LookupMealsById({this.lookupMealsbyIdItems}); - - @override - String toString() { - return 'LookupMealsById{lookupMealsbyIdItems: $lookupMealsbyIdItems}'; - } - - factory LookupMealsById.fromJson(Map json) => - _$LookupMealsByIdFromJson(json); - - Map toJson() => _$LookupMealsByIdToJson(this); -} - -@JsonSerializable() -class LookupMealsByIdItem { - String idMeal; - String strMeal; - String strDrinkAlternate; - String strCategory; - String strArea; - String strInstructions; - String strMealThumb; - String strTags; - String strYoutube; - String strIngredient1; - String strIngredient2; - String strIngredient3; - String strIngredient4; - String strIngredient5; - String strIngredient6; - String strIngredient7; - String strIngredient8; - String strIngredient9; - String strIngredient10; - String strIngredient11; - String strIngredient12; - String strIngredient13; - String strIngredient14; - String strIngredient15; - String strIngredient16; - String strIngredient17; - String strIngredient18; - String strIngredient19; - String strIngredient20; - String strMeasure1; - String strMeasure2; - String strMeasure3; - String strMeasure4; - String strMeasure5; - String strMeasure6; - String strMeasure7; - String strMeasure8; - String strMeasure9; - String strMeasure10; - String strMeasure11; - String strMeasure12; - String strMeasure13; - String strMeasure14; - String strMeasure15; - String strMeasure16; - String strMeasure17; - String strMeasure18; - String strMeasure19; - String strMeasure20; - String strSource; - String dateModified; - - LookupMealsByIdItem( - {this.idMeal, - this.strMeal, - this.strDrinkAlternate, - this.strCategory, - this.strArea, - this.strInstructions, - this.strMealThumb, - this.strTags, - this.strYoutube, - this.strIngredient1, - this.strIngredient2, - this.strIngredient3, - this.strIngredient4, - this.strIngredient5, - this.strIngredient6, - this.strIngredient7, - this.strIngredient8, - this.strIngredient9, - this.strIngredient10, - this.strIngredient11, - this.strIngredient12, - this.strIngredient13, - this.strIngredient14, - this.strIngredient15, - this.strIngredient16, - this.strIngredient17, - this.strIngredient18, - this.strIngredient19, - this.strIngredient20, - this.strMeasure1, - this.strMeasure2, - this.strMeasure3, - this.strMeasure4, - this.strMeasure5, - this.strMeasure6, - this.strMeasure7, - this.strMeasure8, - this.strMeasure9, - this.strMeasure10, - this.strMeasure11, - this.strMeasure12, - this.strMeasure13, - this.strMeasure14, - this.strMeasure15, - this.strMeasure16, - this.strMeasure17, - this.strMeasure18, - this.strMeasure19, - this.strMeasure20, - this.strSource, - this.dateModified}); - - @override - String toString() { - return 'LookupMealsByIdItem{idMeal: $idMeal, strMeal: $strMeal, strDrinkAlternate: $strDrinkAlternate, strCategory: $strCategory, strArea: $strArea, strInstructions: $strInstructions, strMealThumb: $strMealThumb, strTags: $strTags, strYoutube: $strYoutube, strIngredient1: $strIngredient1, strIngredient2: $strIngredient2, strIngredient3: $strIngredient3, strIngredient4: $strIngredient4, strIngredient5: $strIngredient5, strIngredient6: $strIngredient6, strIngredient7: $strIngredient7, strIngredient8: $strIngredient8, strIngredient9: $strIngredient9, strIngredient10: $strIngredient10, strIngredient11: $strIngredient11, strIngredient12: $strIngredient12, strIngredient13: $strIngredient13, strIngredient14: $strIngredient14, strIngredient15: $strIngredient15, strIngredient16: $strIngredient16, strIngredient17: $strIngredient17, strIngredient18: $strIngredient18, strIngredient19: $strIngredient19, strIngredient20: $strIngredient20, strMeasure1: $strMeasure1, strMeasure2: $strMeasure2, strMeasure3: $strMeasure3, strMeasure4: $strMeasure4, strMeasure5: $strMeasure5, strMeasure6: $strMeasure6, strMeasure7: $strMeasure7, strMeasure8: $strMeasure8, strMeasure9: $strMeasure9, strMeasure10: $strMeasure10, strMeasure11: $strMeasure11, strMeasure12: $strMeasure12, strMeasure13: $strMeasure13, strMeasure14: $strMeasure14, strMeasure15: $strMeasure15, strMeasure16: $strMeasure16, strMeasure17: $strMeasure17, strMeasure18: $strMeasure18, strMeasure19: $strMeasure19, strMeasure20: $strMeasure20, strSource: $strSource, dateModified: $dateModified}'; - } - - factory LookupMealsByIdItem.fromJson(Map json) => _$LookupMealsByIdItemFromJson(json); - - Map toJson() => _$LookupMealsByIdItemToJson(this); - -} diff --git a/lib/src/models/lookupmealsbyid/lookup_meals_by_id.g.dart b/lib/src/models/lookupmealsbyid/lookup_meals_by_id.g.dart deleted file mode 100644 index 675c251..0000000 --- a/lib/src/models/lookupmealsbyid/lookup_meals_by_id.g.dart +++ /dev/null @@ -1,130 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'lookup_meals_by_id.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -LookupMealsById _$LookupMealsByIdFromJson(Map json) { - return LookupMealsById( - lookupMealsbyIdItems: (json['meals'] as List) - ?.map((e) => e == null - ? null - : LookupMealsByIdItem.fromJson(e as Map)) - ?.toList()); -} - -Map _$LookupMealsByIdToJson(LookupMealsById instance) => - {'meals': instance.lookupMealsbyIdItems}; - -LookupMealsByIdItem _$LookupMealsByIdItemFromJson(Map json) { - return LookupMealsByIdItem( - idMeal: json['idMeal'] as String, - strMeal: json['strMeal'] as String, - strDrinkAlternate: json['strDrinkAlternate'] as String, - strCategory: json['strCategory'] as String, - strArea: json['strArea'] as String, - strInstructions: json['strInstructions'] as String, - strMealThumb: json['strMealThumb'] as String, - strTags: json['strTags'] as String, - strYoutube: json['strYoutube'] as String, - strIngredient1: json['strIngredient1'] as String, - strIngredient2: json['strIngredient2'] as String, - strIngredient3: json['strIngredient3'] as String, - strIngredient4: json['strIngredient4'] as String, - strIngredient5: json['strIngredient5'] as String, - strIngredient6: json['strIngredient6'] as String, - strIngredient7: json['strIngredient7'] as String, - strIngredient8: json['strIngredient8'] as String, - strIngredient9: json['strIngredient9'] as String, - strIngredient10: json['strIngredient10'] as String, - strIngredient11: json['strIngredient11'] as String, - strIngredient12: json['strIngredient12'] as String, - strIngredient13: json['strIngredient13'] as String, - strIngredient14: json['strIngredient14'] as String, - strIngredient15: json['strIngredient15'] as String, - strIngredient16: json['strIngredient16'] as String, - strIngredient17: json['strIngredient17'] as String, - strIngredient18: json['strIngredient18'] as String, - strIngredient19: json['strIngredient19'] as String, - strIngredient20: json['strIngredient20'] as String, - strMeasure1: json['strMeasure1'] as String, - strMeasure2: json['strMeasure2'] as String, - strMeasure3: json['strMeasure3'] as String, - strMeasure4: json['strMeasure4'] as String, - strMeasure5: json['strMeasure5'] as String, - strMeasure6: json['strMeasure6'] as String, - strMeasure7: json['strMeasure7'] as String, - strMeasure8: json['strMeasure8'] as String, - strMeasure9: json['strMeasure9'] as String, - strMeasure10: json['strMeasure10'] as String, - strMeasure11: json['strMeasure11'] as String, - strMeasure12: json['strMeasure12'] as String, - strMeasure13: json['strMeasure13'] as String, - strMeasure14: json['strMeasure14'] as String, - strMeasure15: json['strMeasure15'] as String, - strMeasure16: json['strMeasure16'] as String, - strMeasure17: json['strMeasure17'] as String, - strMeasure18: json['strMeasure18'] as String, - strMeasure19: json['strMeasure19'] as String, - strMeasure20: json['strMeasure20'] as String, - strSource: json['strSource'] as String, - dateModified: json['dateModified'] as String); -} - -Map _$LookupMealsByIdItemToJson( - LookupMealsByIdItem instance) => - { - 'idMeal': instance.idMeal, - 'strMeal': instance.strMeal, - 'strDrinkAlternate': instance.strDrinkAlternate, - 'strCategory': instance.strCategory, - 'strArea': instance.strArea, - 'strInstructions': instance.strInstructions, - 'strMealThumb': instance.strMealThumb, - 'strTags': instance.strTags, - 'strYoutube': instance.strYoutube, - 'strIngredient1': instance.strIngredient1, - 'strIngredient2': instance.strIngredient2, - 'strIngredient3': instance.strIngredient3, - 'strIngredient4': instance.strIngredient4, - 'strIngredient5': instance.strIngredient5, - 'strIngredient6': instance.strIngredient6, - 'strIngredient7': instance.strIngredient7, - 'strIngredient8': instance.strIngredient8, - 'strIngredient9': instance.strIngredient9, - 'strIngredient10': instance.strIngredient10, - 'strIngredient11': instance.strIngredient11, - 'strIngredient12': instance.strIngredient12, - 'strIngredient13': instance.strIngredient13, - 'strIngredient14': instance.strIngredient14, - 'strIngredient15': instance.strIngredient15, - 'strIngredient16': instance.strIngredient16, - 'strIngredient17': instance.strIngredient17, - 'strIngredient18': instance.strIngredient18, - 'strIngredient19': instance.strIngredient19, - 'strIngredient20': instance.strIngredient20, - 'strMeasure1': instance.strMeasure1, - 'strMeasure2': instance.strMeasure2, - 'strMeasure3': instance.strMeasure3, - 'strMeasure4': instance.strMeasure4, - 'strMeasure5': instance.strMeasure5, - 'strMeasure6': instance.strMeasure6, - 'strMeasure7': instance.strMeasure7, - 'strMeasure8': instance.strMeasure8, - 'strMeasure9': instance.strMeasure9, - 'strMeasure10': instance.strMeasure10, - 'strMeasure11': instance.strMeasure11, - 'strMeasure12': instance.strMeasure12, - 'strMeasure13': instance.strMeasure13, - 'strMeasure14': instance.strMeasure14, - 'strMeasure15': instance.strMeasure15, - 'strMeasure16': instance.strMeasure16, - 'strMeasure17': instance.strMeasure17, - 'strMeasure18': instance.strMeasure18, - 'strMeasure19': instance.strMeasure19, - 'strMeasure20': instance.strMeasure20, - 'strSource': instance.strSource, - 'dateModified': instance.dateModified - }; diff --git a/lib/src/models/randommeals/random_meals.dart b/lib/src/models/randommeals/random_meals.dart deleted file mode 100644 index e8d3dd0..0000000 --- a/lib/src/models/randommeals/random_meals.dart +++ /dev/null @@ -1,138 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -part 'package:food_recipe/src/models/randommeals/random_meals.g.dart'; - -@JsonSerializable() -class RandomMeals { - @JsonKey(name: "meals") - List randomMealsItem; - - RandomMeals({this.randomMealsItem}); - - @override - String toString() { - return 'RandomMeals{randomMealsItem: $randomMealsItem}'; - } - - factory RandomMeals.fromJson(Map json) => - _$RandomMealsFromJson(json); - - Map toJson() => _$RandomMealsToJson(this); -} - -@JsonSerializable() -class RandomMealsItem { - String idMeal; - String strMeal; - String strDrinkAlternate; - String strCategory; - String strArea; - String strInstructions; - String strMealThumb; - String strTags; - String strYoutube; - String strIngredient1; - String strIngredient2; - String strIngredient3; - String strIngredient4; - String strIngredient5; - String strIngredient6; - String strIngredient7; - String strIngredient8; - String strIngredient9; - String strIngredient10; - String strIngredient11; - String strIngredient12; - String strIngredient13; - String strIngredient14; - String strIngredient15; - String strIngredient16; - String strIngredient17; - String strIngredient18; - String strIngredient19; - String strIngredient20; - String strMeasure1; - String strMeasure2; - String strMeasure3; - String strMeasure4; - String strMeasure5; - String strMeasure6; - String strMeasure7; - String strMeasure8; - String strMeasure9; - String strMeasure10; - String strMeasure11; - String strMeasure12; - String strMeasure13; - String strMeasure14; - String strMeasure15; - String strMeasure16; - String strMeasure17; - String strMeasure18; - String strMeasure19; - String strMeasure20; - String strSource; - String dateModified; - - RandomMealsItem( - {this.idMeal, - this.strMeal, - this.strDrinkAlternate, - this.strCategory, - this.strArea, - this.strInstructions, - this.strMealThumb, - this.strTags, - this.strYoutube, - this.strIngredient1, - this.strIngredient2, - this.strIngredient3, - this.strIngredient4, - this.strIngredient5, - this.strIngredient6, - this.strIngredient7, - this.strIngredient8, - this.strIngredient9, - this.strIngredient10, - this.strIngredient11, - this.strIngredient12, - this.strIngredient13, - this.strIngredient14, - this.strIngredient15, - this.strIngredient16, - this.strIngredient17, - this.strIngredient18, - this.strIngredient19, - this.strIngredient20, - this.strMeasure1, - this.strMeasure2, - this.strMeasure3, - this.strMeasure4, - this.strMeasure5, - this.strMeasure6, - this.strMeasure7, - this.strMeasure8, - this.strMeasure9, - this.strMeasure10, - this.strMeasure11, - this.strMeasure12, - this.strMeasure13, - this.strMeasure14, - this.strMeasure15, - this.strMeasure16, - this.strMeasure17, - this.strMeasure18, - this.strMeasure19, - this.strMeasure20, - this.strSource, - this.dateModified}); - - @override - String toString() { - return 'RandomMealsItem{idMeal: $idMeal, strMeal: $strMeal, strDrinkAlternate: $strDrinkAlternate, strCategory: $strCategory, strArea: $strArea, strInstructions: $strInstructions, strMealThumb: $strMealThumb, strTags: $strTags, strYoutube: $strYoutube, strIngredient1: $strIngredient1, strIngredient2: $strIngredient2, strIngredient3: $strIngredient3, strIngredient4: $strIngredient4, strIngredient5: $strIngredient5, strIngredient6: $strIngredient6, strIngredient7: $strIngredient7, strIngredient8: $strIngredient8, strIngredient9: $strIngredient9, strIngredient10: $strIngredient10, strIngredient11: $strIngredient11, strIngredient12: $strIngredient12, strIngredient13: $strIngredient13, strIngredient14: $strIngredient14, strIngredient15: $strIngredient15, strIngredient16: $strIngredient16, strIngredient17: $strIngredient17, strIngredient18: $strIngredient18, strIngredient19: $strIngredient19, strIngredient20: $strIngredient20, strMeasure1: $strMeasure1, strMeasure2: $strMeasure2, strMeasure3: $strMeasure3, strMeasure4: $strMeasure4, strMeasure5: $strMeasure5, strMeasure6: $strMeasure6, strMeasure7: $strMeasure7, strMeasure8: $strMeasure8, strMeasure9: $strMeasure9, strMeasure10: $strMeasure10, strMeasure11: $strMeasure11, strMeasure12: $strMeasure12, strMeasure13: $strMeasure13, strMeasure14: $strMeasure14, strMeasure15: $strMeasure15, strMeasure16: $strMeasure16, strMeasure17: $strMeasure17, strMeasure18: $strMeasure18, strMeasure19: $strMeasure19, strMeasure20: $strMeasure20, strSource: $strSource, dateModified: $dateModified}'; - } - - factory RandomMealsItem.fromJson(Map json) => - _$RandomMealsItemFromJson(json); - - Map toJson() => _$RandomMealsItemToJson(this); -} diff --git a/lib/src/models/randommeals/random_meals.g.dart b/lib/src/models/randommeals/random_meals.g.dart deleted file mode 100644 index 55f0051..0000000 --- a/lib/src/models/randommeals/random_meals.g.dart +++ /dev/null @@ -1,129 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'random_meals.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -RandomMeals _$RandomMealsFromJson(Map json) { - return RandomMeals( - randomMealsItem: (json['meals'] as List) - ?.map((e) => e == null - ? null - : RandomMealsItem.fromJson(e as Map)) - ?.toList()); -} - -Map _$RandomMealsToJson(RandomMeals instance) => - {'meals': instance.randomMealsItem}; - -RandomMealsItem _$RandomMealsItemFromJson(Map json) { - return RandomMealsItem( - idMeal: json['idMeal'] as String, - strMeal: json['strMeal'] as String, - strDrinkAlternate: json['strDrinkAlternate'] as String, - strCategory: json['strCategory'] as String, - strArea: json['strArea'] as String, - strInstructions: json['strInstructions'] as String, - strMealThumb: json['strMealThumb'] as String, - strTags: json['strTags'] as String, - strYoutube: json['strYoutube'] as String, - strIngredient1: json['strIngredient1'] as String, - strIngredient2: json['strIngredient2'] as String, - strIngredient3: json['strIngredient3'] as String, - strIngredient4: json['strIngredient4'] as String, - strIngredient5: json['strIngredient5'] as String, - strIngredient6: json['strIngredient6'] as String, - strIngredient7: json['strIngredient7'] as String, - strIngredient8: json['strIngredient8'] as String, - strIngredient9: json['strIngredient9'] as String, - strIngredient10: json['strIngredient10'] as String, - strIngredient11: json['strIngredient11'] as String, - strIngredient12: json['strIngredient12'] as String, - strIngredient13: json['strIngredient13'] as String, - strIngredient14: json['strIngredient14'] as String, - strIngredient15: json['strIngredient15'] as String, - strIngredient16: json['strIngredient16'] as String, - strIngredient17: json['strIngredient17'] as String, - strIngredient18: json['strIngredient18'] as String, - strIngredient19: json['strIngredient19'] as String, - strIngredient20: json['strIngredient20'] as String, - strMeasure1: json['strMeasure1'] as String, - strMeasure2: json['strMeasure2'] as String, - strMeasure3: json['strMeasure3'] as String, - strMeasure4: json['strMeasure4'] as String, - strMeasure5: json['strMeasure5'] as String, - strMeasure6: json['strMeasure6'] as String, - strMeasure7: json['strMeasure7'] as String, - strMeasure8: json['strMeasure8'] as String, - strMeasure9: json['strMeasure9'] as String, - strMeasure10: json['strMeasure10'] as String, - strMeasure11: json['strMeasure11'] as String, - strMeasure12: json['strMeasure12'] as String, - strMeasure13: json['strMeasure13'] as String, - strMeasure14: json['strMeasure14'] as String, - strMeasure15: json['strMeasure15'] as String, - strMeasure16: json['strMeasure16'] as String, - strMeasure17: json['strMeasure17'] as String, - strMeasure18: json['strMeasure18'] as String, - strMeasure19: json['strMeasure19'] as String, - strMeasure20: json['strMeasure20'] as String, - strSource: json['strSource'] as String, - dateModified: json['dateModified'] as String); -} - -Map _$RandomMealsItemToJson(RandomMealsItem instance) => - { - 'idMeal': instance.idMeal, - 'strMeal': instance.strMeal, - 'strDrinkAlternate': instance.strDrinkAlternate, - 'strCategory': instance.strCategory, - 'strArea': instance.strArea, - 'strInstructions': instance.strInstructions, - 'strMealThumb': instance.strMealThumb, - 'strTags': instance.strTags, - 'strYoutube': instance.strYoutube, - 'strIngredient1': instance.strIngredient1, - 'strIngredient2': instance.strIngredient2, - 'strIngredient3': instance.strIngredient3, - 'strIngredient4': instance.strIngredient4, - 'strIngredient5': instance.strIngredient5, - 'strIngredient6': instance.strIngredient6, - 'strIngredient7': instance.strIngredient7, - 'strIngredient8': instance.strIngredient8, - 'strIngredient9': instance.strIngredient9, - 'strIngredient10': instance.strIngredient10, - 'strIngredient11': instance.strIngredient11, - 'strIngredient12': instance.strIngredient12, - 'strIngredient13': instance.strIngredient13, - 'strIngredient14': instance.strIngredient14, - 'strIngredient15': instance.strIngredient15, - 'strIngredient16': instance.strIngredient16, - 'strIngredient17': instance.strIngredient17, - 'strIngredient18': instance.strIngredient18, - 'strIngredient19': instance.strIngredient19, - 'strIngredient20': instance.strIngredient20, - 'strMeasure1': instance.strMeasure1, - 'strMeasure2': instance.strMeasure2, - 'strMeasure3': instance.strMeasure3, - 'strMeasure4': instance.strMeasure4, - 'strMeasure5': instance.strMeasure5, - 'strMeasure6': instance.strMeasure6, - 'strMeasure7': instance.strMeasure7, - 'strMeasure8': instance.strMeasure8, - 'strMeasure9': instance.strMeasure9, - 'strMeasure10': instance.strMeasure10, - 'strMeasure11': instance.strMeasure11, - 'strMeasure12': instance.strMeasure12, - 'strMeasure13': instance.strMeasure13, - 'strMeasure14': instance.strMeasure14, - 'strMeasure15': instance.strMeasure15, - 'strMeasure16': instance.strMeasure16, - 'strMeasure17': instance.strMeasure17, - 'strMeasure18': instance.strMeasure18, - 'strMeasure19': instance.strMeasure19, - 'strMeasure20': instance.strMeasure20, - 'strSource': instance.strSource, - 'dateModified': instance.dateModified - }; diff --git a/lib/src/models/searchmeals/search_meals.dart b/lib/src/models/searchmeals/search_meals.dart deleted file mode 100644 index 30e8d8b..0000000 --- a/lib/src/models/searchmeals/search_meals.dart +++ /dev/null @@ -1,106 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -part 'search_meals.g.dart'; - -@JsonSerializable() -class SearchMeals { - @JsonKey(name: "meals") - List searchMealsItems; - @JsonKey(ignore: true) - bool isLoading; - - SearchMeals({this.searchMealsItems, this.isLoading = false}); - - @override - String toString() { - return 'SearchMeals{searchMealsItems: $searchMealsItems}'; - } - - factory SearchMeals.fromJson(Map json) => _$SearchMealsFromJson(json); - - Map toJson() => _$SearchMealsToJson(this); - -} - -@JsonSerializable() -class SearchMealsItem { - String idMeal; - String strMeal; - String strDrinkAlternate; - String strCategory; - String strArea; - String strInstructions; - String strMealThumb; - String strTags; - String strYoutube; - String strIngredient1; - String strIngredient2; - String strIngredient3; - String strIngredient4; - String strIngredient5; - String strIngredient6; - String strIngredient7; - String strIngredient8; - String strIngredient9; - String strIngredient10; - String strIngredient11; - String strIngredient12; - String strIngredient13; - String strIngredient14; - String strIngredient15; - String strIngredient16; - String strIngredient17; - String strIngredient18; - String strIngredient19; - String strIngredient20; - String strMeasure1; - String strMeasure2; - String strMeasure3; - String strMeasure4; - String strMeasure5; - String strMeasure6; - String strMeasure7; - String strMeasure8; - String strMeasure9; - String strMeasure10; - String strMeasure11; - String strMeasure12; - String strMeasure13; - String strMeasure14; - String strMeasure15; - String strMeasure16; - String strMeasure17; - String strMeasure18; - String strMeasure19; - String strMeasure20; - String strSource; - String dateModified; - @JsonKey(ignore: true) - bool isFavorite; - - SearchMealsItem({this.idMeal, this.strMeal, this.strDrinkAlternate, - this.strCategory, this.strArea, this.strInstructions, this.strMealThumb, - this.strTags, this.strYoutube, this.strIngredient1, this.strIngredient2, - this.strIngredient3, this.strIngredient4, this.strIngredient5, - this.strIngredient6, this.strIngredient7, this.strIngredient8, - this.strIngredient9, this.strIngredient10, this.strIngredient11, - this.strIngredient12, this.strIngredient13, this.strIngredient14, - this.strIngredient15, this.strIngredient16, this.strIngredient17, - this.strIngredient18, this.strIngredient19, this.strIngredient20, - this.strMeasure1, this.strMeasure2, this.strMeasure3, this.strMeasure4, - this.strMeasure5, this.strMeasure6, this.strMeasure7, this.strMeasure8, - this.strMeasure9, this.strMeasure10, this.strMeasure11, this.strMeasure12, - this.strMeasure13, this.strMeasure14, this.strMeasure15, - this.strMeasure16, this.strMeasure17, this.strMeasure18, - this.strMeasure19, this.strMeasure20, this.strSource, this.dateModified, this.isFavorite = false}); - - @override - String toString() { - return 'SearchMealsItem{idMeal: $idMeal, strMeal: $strMeal, strDrinkAlternate: $strDrinkAlternate, strCategory: $strCategory, strArea: $strArea, strInstructions: $strInstructions, strMealThumb: $strMealThumb, strTags: $strTags, strYoutube: $strYoutube, strIngredient1: $strIngredient1, strIngredient2: $strIngredient2, strIngredient3: $strIngredient3, strIngredient4: $strIngredient4, strIngredient5: $strIngredient5, strIngredient6: $strIngredient6, strIngredient7: $strIngredient7, strIngredient8: $strIngredient8, strIngredient9: $strIngredient9, strIngredient10: $strIngredient10, strIngredient11: $strIngredient11, strIngredient12: $strIngredient12, strIngredient13: $strIngredient13, strIngredient14: $strIngredient14, strIngredient15: $strIngredient15, strIngredient16: $strIngredient16, strIngredient17: $strIngredient17, strIngredient18: $strIngredient18, strIngredient19: $strIngredient19, strIngredient20: $strIngredient20, strMeasure1: $strMeasure1, strMeasure2: $strMeasure2, strMeasure3: $strMeasure3, strMeasure4: $strMeasure4, strMeasure5: $strMeasure5, strMeasure6: $strMeasure6, strMeasure7: $strMeasure7, strMeasure8: $strMeasure8, strMeasure9: $strMeasure9, strMeasure10: $strMeasure10, strMeasure11: $strMeasure11, strMeasure12: $strMeasure12, strMeasure13: $strMeasure13, strMeasure14: $strMeasure14, strMeasure15: $strMeasure15, strMeasure16: $strMeasure16, strMeasure17: $strMeasure17, strMeasure18: $strMeasure18, strMeasure19: $strMeasure19, strMeasure20: $strMeasure20, strSource: $strSource, dateModified: $dateModified}'; - } - - factory SearchMealsItem.fromJson(Map json) => _$SearchMealsItemFromJson(json); - - Map toJson() => _$SearchMealsItemToJson(this); - - -} \ No newline at end of file diff --git a/lib/src/models/searchmeals/search_meals.g.dart b/lib/src/models/searchmeals/search_meals.g.dart deleted file mode 100644 index e91df32..0000000 --- a/lib/src/models/searchmeals/search_meals.g.dart +++ /dev/null @@ -1,129 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'search_meals.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -SearchMeals _$SearchMealsFromJson(Map json) { - return SearchMeals( - searchMealsItems: (json['meals'] as List) - ?.map((e) => e == null - ? null - : SearchMealsItem.fromJson(e as Map)) - ?.toList()); -} - -Map _$SearchMealsToJson(SearchMeals instance) => - {'meals': instance.searchMealsItems}; - -SearchMealsItem _$SearchMealsItemFromJson(Map json) { - return SearchMealsItem( - idMeal: json['idMeal'] as String, - strMeal: json['strMeal'] as String, - strDrinkAlternate: json['strDrinkAlternate'] as String, - strCategory: json['strCategory'] as String, - strArea: json['strArea'] as String, - strInstructions: json['strInstructions'] as String, - strMealThumb: json['strMealThumb'] as String, - strTags: json['strTags'] as String, - strYoutube: json['strYoutube'] as String, - strIngredient1: json['strIngredient1'] as String, - strIngredient2: json['strIngredient2'] as String, - strIngredient3: json['strIngredient3'] as String, - strIngredient4: json['strIngredient4'] as String, - strIngredient5: json['strIngredient5'] as String, - strIngredient6: json['strIngredient6'] as String, - strIngredient7: json['strIngredient7'] as String, - strIngredient8: json['strIngredient8'] as String, - strIngredient9: json['strIngredient9'] as String, - strIngredient10: json['strIngredient10'] as String, - strIngredient11: json['strIngredient11'] as String, - strIngredient12: json['strIngredient12'] as String, - strIngredient13: json['strIngredient13'] as String, - strIngredient14: json['strIngredient14'] as String, - strIngredient15: json['strIngredient15'] as String, - strIngredient16: json['strIngredient16'] as String, - strIngredient17: json['strIngredient17'] as String, - strIngredient18: json['strIngredient18'] as String, - strIngredient19: json['strIngredient19'] as String, - strIngredient20: json['strIngredient20'] as String, - strMeasure1: json['strMeasure1'] as String, - strMeasure2: json['strMeasure2'] as String, - strMeasure3: json['strMeasure3'] as String, - strMeasure4: json['strMeasure4'] as String, - strMeasure5: json['strMeasure5'] as String, - strMeasure6: json['strMeasure6'] as String, - strMeasure7: json['strMeasure7'] as String, - strMeasure8: json['strMeasure8'] as String, - strMeasure9: json['strMeasure9'] as String, - strMeasure10: json['strMeasure10'] as String, - strMeasure11: json['strMeasure11'] as String, - strMeasure12: json['strMeasure12'] as String, - strMeasure13: json['strMeasure13'] as String, - strMeasure14: json['strMeasure14'] as String, - strMeasure15: json['strMeasure15'] as String, - strMeasure16: json['strMeasure16'] as String, - strMeasure17: json['strMeasure17'] as String, - strMeasure18: json['strMeasure18'] as String, - strMeasure19: json['strMeasure19'] as String, - strMeasure20: json['strMeasure20'] as String, - strSource: json['strSource'] as String, - dateModified: json['dateModified'] as String); -} - -Map _$SearchMealsItemToJson(SearchMealsItem instance) => - { - 'idMeal': instance.idMeal, - 'strMeal': instance.strMeal, - 'strDrinkAlternate': instance.strDrinkAlternate, - 'strCategory': instance.strCategory, - 'strArea': instance.strArea, - 'strInstructions': instance.strInstructions, - 'strMealThumb': instance.strMealThumb, - 'strTags': instance.strTags, - 'strYoutube': instance.strYoutube, - 'strIngredient1': instance.strIngredient1, - 'strIngredient2': instance.strIngredient2, - 'strIngredient3': instance.strIngredient3, - 'strIngredient4': instance.strIngredient4, - 'strIngredient5': instance.strIngredient5, - 'strIngredient6': instance.strIngredient6, - 'strIngredient7': instance.strIngredient7, - 'strIngredient8': instance.strIngredient8, - 'strIngredient9': instance.strIngredient9, - 'strIngredient10': instance.strIngredient10, - 'strIngredient11': instance.strIngredient11, - 'strIngredient12': instance.strIngredient12, - 'strIngredient13': instance.strIngredient13, - 'strIngredient14': instance.strIngredient14, - 'strIngredient15': instance.strIngredient15, - 'strIngredient16': instance.strIngredient16, - 'strIngredient17': instance.strIngredient17, - 'strIngredient18': instance.strIngredient18, - 'strIngredient19': instance.strIngredient19, - 'strIngredient20': instance.strIngredient20, - 'strMeasure1': instance.strMeasure1, - 'strMeasure2': instance.strMeasure2, - 'strMeasure3': instance.strMeasure3, - 'strMeasure4': instance.strMeasure4, - 'strMeasure5': instance.strMeasure5, - 'strMeasure6': instance.strMeasure6, - 'strMeasure7': instance.strMeasure7, - 'strMeasure8': instance.strMeasure8, - 'strMeasure9': instance.strMeasure9, - 'strMeasure10': instance.strMeasure10, - 'strMeasure11': instance.strMeasure11, - 'strMeasure12': instance.strMeasure12, - 'strMeasure13': instance.strMeasure13, - 'strMeasure14': instance.strMeasure14, - 'strMeasure15': instance.strMeasure15, - 'strMeasure16': instance.strMeasure16, - 'strMeasure17': instance.strMeasure17, - 'strMeasure18': instance.strMeasure18, - 'strMeasure19': instance.strMeasure19, - 'strMeasure20': instance.strMeasure20, - 'strSource': instance.strSource, - 'dateModified': instance.dateModified - }; diff --git a/lib/src/resources/food_api_provider.dart b/lib/src/resources/food_api_provider.dart deleted file mode 100644 index 900e571..0000000 --- a/lib/src/resources/food_api_provider.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'package:dio/dio.dart'; -import 'package:food_recipe/src/models/area/area_meals.dart'; -import 'package:food_recipe/src/models/categories/categories.dart'; -import 'package:food_recipe/src/models/filterarea/filter_area_meals.dart'; -import 'package:food_recipe/src/models/filtercategories/filter_categories.dart'; -import 'package:food_recipe/src/models/latest/latest_meals.dart'; -import 'package:food_recipe/src/models/lookupmealsbyid/lookup_meals_by_id.dart'; -import 'package:food_recipe/src/models/randommeals/random_meals.dart'; -import 'package:food_recipe/src/models/searchmeals/search_meals.dart'; - -class FoodApiProvider { - Dio dio = Dio(); - final _baseUrl = "https://www.themealdb.com"; - - Future getRandomMeals() async { - final response = await dio.get("$_baseUrl/api/json/v1/1/random.php"); - if (response.statusCode == 200) { - return RandomMeals.fromJson(response.data); - } else { - throw Exception("Failed to get random meals"); - } - } - - Future getCategories() async { - final response = await dio.get("$_baseUrl/api/json/v1/1/categories.php"); - if (response.statusCode == 200) { - return Categories.fromJson(response.data); - } else { - throw Exception("Failed to get categories"); - } - } - - Future getLatestMeals() async { - final response = await dio.get("$_baseUrl/api/json/v1/1/latest.php"); - if (response.statusCode == 200) { - return LatestMeals.fromJson(response.data); - } else { - throw Exception("Failed to get latest meals"); - } - } - - Future getAreaMeals() async { - final response = await dio.get("$_baseUrl/api/json/v1/1/list.php?a=list"); - if (response.statusCode == 200) { - return AreaMeals.fromJson(response.data); - } else { - throw Exception("Failed to get area meals"); - } - } - - Future getFilterByCategories(String category) async { - final response = await dio.get("$_baseUrl/api/json/v1/1/filter.php?c=$category"); - if (response.statusCode == 200) { - return FilterCategories.fromJson(response.data); - } else { - throw Exception("Failed to get filter by categories"); - } - } - - Future getFilterByArea(String area) async { - final response = await dio.get("$_baseUrl/api/json/v1/1/filter.php?a=$area"); - if (response.statusCode == 200) { - return FilterAreaMeals.fromJson(response.data); - } else { - throw Exception("Failed to get filter by area"); - } - } - - Future getLookupMealsById(String id) async { - final response = await dio.get("$_baseUrl/api/json/v1/1/lookup.php?i=$id"); - if (response.statusCode == 200) { - return LookupMealsById.fromJson(response.data); - } else { - throw Exception("Failed to get lookup meals by id"); - } - } - - Future getSearchMealsByKeyword(String keyword) async { - final response = await dio.get("$_baseUrl/api/json/v1/1/search.php?s=$keyword"); - if (response.statusCode == 200) { - return SearchMeals.fromJson(response.data); - } else { - throw Exception("Failed to get search meals by id keyword"); - } - } - -} diff --git a/lib/src/resources/food_api_repository.dart b/lib/src/resources/food_api_repository.dart deleted file mode 100644 index 49d2325..0000000 --- a/lib/src/resources/food_api_repository.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'dart:async'; - -import 'package:food_recipe/src/models/area/area_meals.dart'; -import 'package:food_recipe/src/models/categories/categories.dart'; -import 'package:food_recipe/src/models/filterarea/filter_area_meals.dart'; -import 'package:food_recipe/src/models/filtercategories/filter_categories.dart'; -import 'package:food_recipe/src/models/latest/latest_meals.dart'; -import 'package:food_recipe/src/models/lookupmealsbyid/lookup_meals_by_id.dart'; -import 'package:food_recipe/src/models/randommeals/random_meals.dart'; -import 'package:food_recipe/src/models/searchmeals/search_meals.dart'; - -import 'food_api_provider.dart'; - -class FoodApiRepository { - final foodApiProvider = FoodApiProvider(); - - Future getRandomMeals() => foodApiProvider.getRandomMeals(); - - Future getCategories() => foodApiProvider.getCategories(); - - Future getLatestMeals() => foodApiProvider.getLatestMeals(); - - Future getAreaMeals() => foodApiProvider.getAreaMeals(); - - Future getFilterByCategories(String category) => - foodApiProvider.getFilterByCategories(category); - - Future getFilterByArea(String area) => - foodApiProvider.getFilterByArea(area); - - Future getLookupMealsById(String id) => - foodApiProvider.getLookupMealsById(id); - - Future getSearchMealsByKeyword(String keyword) => - foodApiProvider.getSearchMealsByKeyword(keyword); -} diff --git a/lib/src/ui/detailmeals/detail_meals_screen.dart b/lib/src/ui/detailmeals/detail_meals_screen.dart deleted file mode 100644 index d104b79..0000000 --- a/lib/src/ui/detailmeals/detail_meals_screen.dart +++ /dev/null @@ -1,458 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:food_recipe/src/blocs/detailmeals/detail_meals_bloc.dart'; -import 'package:food_recipe/src/database/entity/favorite_meal.dart'; -import 'package:food_recipe/src/models/lookupmealsbyid/lookup_meals_by_id.dart'; -import 'package:food_recipe/src/utils/utils.dart'; -import 'package:url_launcher/url_launcher.dart'; - -class DetailMealsScreen extends StatefulWidget { - final String idMeal; - final String strMeal; - final String strMealThumb; - final FavoriteMeal favoriteMeal; - - DetailMealsScreen( - {this.idMeal, this.strMeal, this.strMealThumb, this.favoriteMeal}); - - @override - _DetailMealsScreenState createState() => _DetailMealsScreenState(); -} - -class _DetailMealsScreenState extends State { - @override - Widget build(BuildContext context) { - var mediaQuery = MediaQuery.of(context); - return Scaffold( - body: Container( - child: Stack( - children: [ - _buildWidgetImageHeader(mediaQuery), - SafeArea( - child: Padding( - padding: const EdgeInsets.only(left: 16.0, top: 16.0), - child: GestureDetector( - onTap: () { - Navigator.pop(context); - }, - child: CircleAvatar( - child: Icon(Icons.arrow_back, color: Colors.black87), - backgroundColor: Colors.white, - ), - ), - ), - ), - _buildWidgetDetailMeal(mediaQuery, context), - ], - ), - ), - ); - } - - Widget _buildWidgetDetailMeal( - MediaQueryData mediaQuery, BuildContext context) { - return Padding( - padding: EdgeInsets.only(top: mediaQuery.size.height / 2.5), - child: ClipRRect( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(28.0), - topRight: Radius.circular(28.0), - ), - child: Container( - width: double.infinity, - color: Colors.white, - child: SafeArea( - left: false, - top: false, - right: false, - child: ListView( - padding: EdgeInsets.only( - left: 16.0, - top: 16.0, - right: 16.0, - ), - children: [ - _buildTitleMeal(context), - _buildWidgetDataDetailMeal(), - ], - ), - ), - ), - ), - ); - } - - Widget _buildWidgetDataDetailMeal() { - if (widget.favoriteMeal == null) { - return FutureBuilder( - future: detailsMealsBloc.getDetailsMealsById(widget.idMeal), - builder: - (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasData) { - LookupMealsById lookupMealsById = snapshot.data; - var detailMeals = lookupMealsById.lookupMealsbyIdItems[0]; - return _buildWidgetBottomSheet( - detailMeals: detailMeals, context: context); - } else if (snapshot.hasError) { - return Text(snapshot.error.toString()); - } - return Padding( - padding: const EdgeInsets.only(top: 16.0), - child: Center(child: buildCircularProgressIndicator()), - ); - }, - ); - } else { - return _buildWidgetBottomSheet(context: context); - } - } - - Widget _buildWidgetBottomSheet( - {LookupMealsByIdItem detailMeals, BuildContext context}) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildTagMeal(detailMeals?.strTags ?? widget.favoriteMeal?.strTags ?? null), - Padding(padding: EdgeInsets.only(top: 24.0)), - _buildWidgetPanelInfoGeneralMeal(detailMeals), - Padding(padding: EdgeInsets.only(top: 24.0)), - Text( - "Ingredients", - style: Theme.of(context).textTheme.title, - ), - _buildWidgetInfoIngredients(detailMeals), - Padding(padding: EdgeInsets.only(top: 24.0)), - Text( - "Instructions", - style: Theme.of(context).textTheme.title, - ), - _buildWidgetInfoInstructions( - detailMeals?.strInstructions ?? widget.favoriteMeal?.strInstructions ?? "", - ), - ], - ); - } - - Widget _buildWidgetInfoInstructions(String strInstructions) { - return Padding( - padding: EdgeInsets.only(left: 8.0, top: 8.0, right: 8.0, bottom: 16.0), - child: Text(strInstructions.isEmpty ? "N/A" : strInstructions), - ); - } - - Widget _buildWidgetInfoIngredients(LookupMealsByIdItem detailMeals) { - List ingredientsTemp = []; - if (detailMeals == null) { - ingredientsTemp - ..add((widget.favoriteMeal.strMeasure1 ?? "") + - " " + - (widget.favoriteMeal.strIngredient1 ?? "")) - ..add((widget.favoriteMeal.strMeasure2 ?? "") + - " " + - (widget.favoriteMeal.strIngredient2 ?? "")) - ..add((widget.favoriteMeal.strMeasure3 ?? "") + - " " + - (widget.favoriteMeal.strIngredient3 ?? "")) - ..add((widget.favoriteMeal.strMeasure4 ?? "") + - " " + - (widget.favoriteMeal.strIngredient4 ?? "")) - ..add((widget.favoriteMeal.strMeasure5 ?? "") + - " " + - (widget.favoriteMeal.strIngredient5 ?? "")) - ..add((widget.favoriteMeal.strMeasure6 ?? "") + - " " + - (widget.favoriteMeal.strIngredient6 ?? "")) - ..add((widget.favoriteMeal.strMeasure7 ?? "") + - " " + - (widget.favoriteMeal.strIngredient7 ?? "")) - ..add((widget.favoriteMeal.strMeasure8 ?? "") + - " " + - (widget.favoriteMeal.strIngredient8 ?? "")) - ..add((widget.favoriteMeal.strMeasure9 ?? "") + - " " + - (widget.favoriteMeal.strIngredient9 ?? "")) - ..add((widget.favoriteMeal.strMeasure10 ?? "") + - " " + - (widget.favoriteMeal.strIngredient10 ?? "")) - ..add((widget.favoriteMeal.strMeasure11 ?? "") + - " " + - (widget.favoriteMeal.strIngredient11 ?? "")) - ..add((widget.favoriteMeal.strMeasure12 ?? "") + - " " + - (widget.favoriteMeal.strIngredient12 ?? "")) - ..add((widget.favoriteMeal.strMeasure13 ?? "") + - " " + - (widget.favoriteMeal.strIngredient13 ?? "")) - ..add((widget.favoriteMeal.strMeasure14 ?? "") + - " " + - (widget.favoriteMeal.strIngredient14 ?? "")) - ..add((widget.favoriteMeal.strMeasure15 ?? "") + - " " + - (widget.favoriteMeal.strIngredient15 ?? "")) - ..add((widget.favoriteMeal.strMeasure16 ?? "") + - " " + - (widget.favoriteMeal.strIngredient16 ?? "")) - ..add((widget.favoriteMeal.strMeasure17 ?? "") + - " " + - (widget.favoriteMeal.strIngredient17 ?? "")) - ..add((widget.favoriteMeal.strMeasure18 ?? "") + - " " + - (widget.favoriteMeal.strIngredient18 ?? "")) - ..add((widget.favoriteMeal.strMeasure19 ?? "") + - " " + - (widget.favoriteMeal.strIngredient19 ?? "")) - ..add((widget.favoriteMeal.strMeasure20 ?? "") + - " " + - (widget.favoriteMeal.strIngredient20 ?? "")); - } else { - ingredientsTemp - ..add((detailMeals.strMeasure1 ?? "") + - " " + - (detailMeals.strIngredient1 ?? "")) - ..add((detailMeals.strMeasure2 ?? "") + - " " + - (detailMeals.strIngredient2 ?? "")) - ..add((detailMeals.strMeasure3 ?? "") + - " " + - (detailMeals.strIngredient3 ?? "")) - ..add((detailMeals.strMeasure4 ?? "") + - " " + - (detailMeals.strIngredient4 ?? "")) - ..add((detailMeals.strMeasure5 ?? "") + - " " + - (detailMeals.strIngredient5 ?? "")) - ..add((detailMeals.strMeasure6 ?? "") + - " " + - (detailMeals.strIngredient6 ?? "")) - ..add((detailMeals.strMeasure7 ?? "") + - " " + - (detailMeals.strIngredient7 ?? "")) - ..add((detailMeals.strMeasure8 ?? "") + - " " + - (detailMeals.strIngredient8 ?? "")) - ..add((detailMeals.strMeasure9 ?? "") + - " " + - (detailMeals.strIngredient9 ?? "")) - ..add((detailMeals.strMeasure10 ?? "") + - " " + - (detailMeals.strIngredient10 ?? "")) - ..add((detailMeals.strMeasure11 ?? "") + - " " + - (detailMeals.strIngredient11 ?? "")) - ..add((detailMeals.strMeasure12 ?? "") + - " " + - (detailMeals.strIngredient12 ?? "")) - ..add((detailMeals.strMeasure13 ?? "") + - " " + - (detailMeals.strIngredient13 ?? "")) - ..add((detailMeals.strMeasure14 ?? "") + - " " + - (detailMeals.strIngredient14 ?? "")) - ..add((detailMeals.strMeasure15 ?? "") + - " " + - (detailMeals.strIngredient15 ?? "")) - ..add((detailMeals.strMeasure16 ?? "") + - " " + - (detailMeals.strIngredient16 ?? "")) - ..add((detailMeals.strMeasure17 ?? "") + - " " + - (detailMeals.strIngredient17 ?? "")) - ..add((detailMeals.strMeasure18 ?? "") + - " " + - (detailMeals.strIngredient18 ?? "")) - ..add((detailMeals.strMeasure19 ?? "") + - " " + - (detailMeals.strIngredient19 ?? "")) - ..add((detailMeals.strMeasure20 ?? "") + - " " + - (detailMeals.strIngredient20 ?? "")); - } - List ingredients = []; - for (String ingredientItem in ingredientsTemp) { - if (ingredientItem.trim().isEmpty) { - break; - } - ingredients.add(ingredientItem); - } - return Padding( - padding: EdgeInsets.only(left: 8.0, top: 8.0, right: 8.0), - child: ListView.builder( - padding: EdgeInsets.all(0.0), - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: ingredients.length, - itemBuilder: (context, index) { - return Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(top: 5.0), - child: Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.black54, - ), - width: 6.0, - height: 6.0, - ), - ), - Padding(padding: EdgeInsets.only(right: 16.0)), - Expanded( - child: Text(ingredients[index]), - ), - ], - ); - }, - ), - ); - } - - Widget _buildWidgetPanelInfoGeneralMeal(LookupMealsByIdItem detailMeals) { - return Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - _buildWidgetInfoPlayVideo( - detailMeals?.strYoutube ?? widget.favoriteMeal?.strYoutube ?? ""), - Padding(padding: EdgeInsets.only(left: 8.0)), - _buildVerticalDivider(), - Padding(padding: EdgeInsets.only(left: 8.0)), - _buildWidgetInfoCategoryMeal( - detailMeals?.strCategory ?? widget.favoriteMeal?.strCategory ?? ""), - Padding(padding: EdgeInsets.only(left: 8.0)), - _buildVerticalDivider(), - Padding(padding: EdgeInsets.only(left: 8.0)), - _buildWidgetInfoCountryMeal( - detailMeals?.strArea ?? widget.favoriteMeal?.strArea ?? ""), - Padding(padding: EdgeInsets.only(left: 8.0)), - ], - ); - } - - Widget _buildWidgetInfoCountryMeal(String strArea) { - return Column( - children: [ - Text("Country"), - Text(strArea.isEmpty ? "N/A" : strArea, style: TextStyle(fontWeight: FontWeight.bold)), - ], - ); - } - - Widget _buildWidgetInfoCategoryMeal(String strCategory) { - return Column( - children: [ - Text("Category"), - Text(strCategory.isEmpty ? "N/A" : strCategory, style: TextStyle(fontWeight: FontWeight.bold)), - ], - ); - } - - Widget _buildVerticalDivider() { - return Container( - color: Colors.grey, - width: 1.0, - height: 42.0, - ); - } - - Widget _buildWidgetInfoPlayVideo(String strYoutube) { - return GestureDetector( - onTap: () async { - if (strYoutube.isNotEmpty) { - if (Platform.isIOS) { - if (await canLaunch(strYoutube)) { - await launch(strYoutube, forceSafariVC: false); - } else { - if (await canLaunch(strYoutube)) { - await launch(strYoutube); - } else { - Scaffold.of(context).showSnackBar( - SnackBar(content: Text("Could not play video"))); - throw "Could not play video"; - } - } - } else { - if (await canLaunch(strYoutube)) { - await launch(strYoutube); - } else { - Scaffold.of(context).showSnackBar( - SnackBar(content: Text("Could not play video"))); - throw "Could not play video"; - } - } - } - }, - child: Row( - children: [ - Icon( - Icons.ondemand_video, - size: 28.0, - ), - Padding(padding: EdgeInsets.only(left: 8.0)), - Column( - children: [ - Text(strYoutube.isEmpty ? "Video" : "Play"), - Text( - strYoutube.isEmpty ? "N/A" : "Video", - style: TextStyle(fontWeight: FontWeight.bold), - ), - ], - ), - ], - ), - ); - } - - Widget _buildTagMeal(String strTags) { - return Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Icon( - Icons.label, - color: Colors.black26, - size: 24.0, - ), - Padding(padding: EdgeInsets.only(left: 4.0)), - Expanded( - child: Padding( - padding: const EdgeInsets.only(top: 3.5), - child: Text( - strTags != null - ? strTags.substring(0, strTags.length - 1) - : "N/A", - style: TextStyle(color: Colors.grey), - maxLines: 2, - ), - ), - ), - ], - ); - } - - Widget _buildTitleMeal(BuildContext context) { - return Text( - widget.strMeal ?? widget.favoriteMeal.strMeal, - style: Theme.of(context) - .textTheme - .title - .merge(TextStyle(fontWeight: FontWeight.bold)), - maxLines: 2, - ); - } - - Widget _buildWidgetImageHeader(MediaQueryData mediaQuery) { - return Hero( - tag: "image_detail_meals_${widget.idMeal ?? widget.favoriteMeal.idMeal}", - child: FadeInImage( - image: NetworkImage( - widget.strMealThumb ?? widget.favoriteMeal.strMealThumb), - placeholder: AssetImage("assets/images/img_placeholder.jpg"), - fit: BoxFit.cover, - width: double.infinity, - height: mediaQuery.size.height / 2.2, - ), - ); - } -} diff --git a/lib/src/ui/favorite/favorite_screen.dart b/lib/src/ui/favorite/favorite_screen.dart deleted file mode 100644 index bf24ae8..0000000 --- a/lib/src/ui/favorite/favorite_screen.dart +++ /dev/null @@ -1,180 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:food_recipe/src/blocs/favorite/favorite_bloc.dart'; -import 'package:food_recipe/src/blocs/favorite/favorite_meals_bloc_model.dart'; -import 'package:food_recipe/src/database/entity/favorite_meal.dart'; -import 'package:food_recipe/src/ui/detailmeals/detail_meals_screen.dart'; -import 'package:food_recipe/src/utils/utils.dart'; - -class FavoriteScreen extends StatefulWidget { - @override - _FavoriteScreenState createState() => _FavoriteScreenState(); -} - -class _FavoriteScreenState extends State { - FavoriteBloc _favoriteBloc; - - @override - void initState() { - _favoriteBloc = FavoriteBloc(); - _favoriteBloc.getAllFavoriteMeals(); - super.initState(); - } - - @override - void dispose() { - _favoriteBloc.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - var mediaQuery = MediaQuery.of(context); - return Scaffold( - body: Container( - width: double.infinity, - height: double.infinity, - child: Stack( - children: [ - buildWidgetBackgroundCircle(mediaQuery), - _buildWidgetContent(mediaQuery), - ], - ), - ), - ); - } - - Widget _buildWidgetContent(MediaQueryData mediaQuery) { - return SafeArea( - minimum: EdgeInsets.symmetric(horizontal: 16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding(padding: EdgeInsets.only(top: 16.0)), - Text( - "Favorite\nRecipe", - style: Theme.of(context) - .textTheme - .display2 - .merge(TextStyle(fontWeight: FontWeight.bold)), - ), - Padding(padding: EdgeInsets.only(top: 16)), - Expanded( - child: StreamBuilder( - stream: _favoriteBloc.listFavoriteMeal, - builder: (BuildContext context, - AsyncSnapshot snapshot) { - if (snapshot.hasData) { - FavoriteMealsBlocModel favoriteMealsBlocModel = snapshot.data; - if (favoriteMealsBlocModel.isLoading) { - return Center(child: buildCircularProgressIndicator()); - } else { - List listFavoriteMeals = - favoriteMealsBlocModel.listFavoriteMeals; - return ListView.builder( - shrinkWrap: true, - itemCount: listFavoriteMeals.length, - itemBuilder: (context, index) { - FavoriteMeal favoriteMeal = listFavoriteMeals[index]; - return Padding( - padding: const EdgeInsets.only(bottom: 16.0), - child: GestureDetector( - onTap: () { - Navigator.push(context, MaterialPageRoute( - builder: (context) { - return DetailMealsScreen( - favoriteMeal: favoriteMeal, - ); - }, - )); - }, - child: Card( - elevation: 8.0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16.0), - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(16.0), - child: Stack( - children: [ - Hero( - tag: - "image_detail_meals_${favoriteMeal.idMeal}", - child: FadeInImage( - image: NetworkImage( - favoriteMeal.strMealThumb), - placeholder: AssetImage( - "assets/images/img_placeholder.jpg"), - fit: BoxFit.cover, - width: double.infinity, - height: mediaQuery.size.width / 1.5, - ), - ), - Container( - width: double.infinity, - height: mediaQuery.size.width / 1.5, - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - stops: [0.1, 0.9], - colors: [ - Color(0xFFFFFFFF), - Color(0x00FFFFFF) - ], - ), - ), - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: Row( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Expanded( - child: Text( - favoriteMeal.strMeal, - style: Theme.of(context) - .textTheme - .title, - maxLines: 2, - ), - ), - GestureDetector( - onTap: () { - _favoriteBloc - .removeFavoriteMealById( - favoriteMeal.idMeal); - }, - child: CircleAvatar( - backgroundColor: - Color(0xAFE8364B), - child: Icon( - Icons.favorite, - color: Colors.white, - ), - ), - ) - ], - ), - ), - ], - ), - ), - ), - ), - ); - }, - ); - } - } else if (snapshot.hasError) { - return Text(snapshot.error.toString()); - } - return Container(); - }, - ), - ), - ], - ), - ); - } -} diff --git a/lib/src/ui/home/home_screen.dart b/lib/src/ui/home/home_screen.dart deleted file mode 100644 index 2e74095..0000000 --- a/lib/src/ui/home/home_screen.dart +++ /dev/null @@ -1,328 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:food_recipe/src/blocs/home/home_bloc.dart'; -import 'package:food_recipe/src/models/categories/categories.dart'; -import 'package:food_recipe/src/models/latest/latest_meals.dart'; -import 'package:food_recipe/src/ui/detailmeals/detail_meals_screen.dart'; -import 'package:food_recipe/src/utils/utils.dart'; -import 'package:food_recipe/values/color_assets.dart'; - -class HomeScreen extends StatefulWidget { - @override - _HomeScreenState createState() => _HomeScreenState(); -} - -class _HomeScreenState extends State { - @override - void dispose() { - homeBloc.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Stack( - children: [ - _buildWidgetBackground(), - SafeArea( - child: ListView( - children: [ - Align( - alignment: Alignment.topRight, - child: GestureDetector( - onTap: () { - Navigator.pushNamed(context, navigatorInfoApp); - }, - child: Padding( - padding: const EdgeInsets.only(top: 16.0, right: 16.0), - child: Icon( - Icons.info_outline, - color: Colors.white, - ), - ), - ), - ), - _buildWidgetContent(), - ], - ), - ), - ], - ); - } - - Widget _buildWidgetBackground() { - return Column( - children: [ - Expanded( - flex: 2, - child: Container( - color: ColorAssets.primarySwatchColor, - ), - ), - Expanded( - child: Container( - color: Colors.white, - ), - ) - ], - ); - } - - Widget _buildWidgetContent() { - var mediaQuery = MediaQuery.of(context); - return SafeArea( - minimum: EdgeInsets.symmetric(horizontal: 16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding(padding: EdgeInsets.only(top: 16.0)), - Text( - "What are you\nCooking today ?", - style: Theme.of(context).textTheme.display1.merge( - TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontFamily: "Roboto", - ), - ), - ), - Padding(padding: EdgeInsets.only(top: 16.0)), - _buildTextFieldSearchMeals(), - Padding(padding: EdgeInsets.only(top: 24.0)), - Text("Latest Recipe", - style: Theme.of(context) - .textTheme - .title - .merge(TextStyle(color: Colors.white))), - Padding(padding: EdgeInsets.only(top: 8.0)), - _buildListViewLatestRecipe(mediaQuery), - Padding(padding: EdgeInsets.only(top: 24.0)), - Text("Category", - style: Theme.of(context) - .textTheme - .title - .merge(TextStyle(color: ColorAssets.primaryTextColor))), - Padding(padding: EdgeInsets.only(top: 8.0)), - _buildListViewCategory(), - ], - ), - ); - } - - Widget _buildListViewCategory() { - return FutureBuilder( - future: homeBloc.getCategories(), - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasData) { - int numberItem = 0; - Categories categories = snapshot.data; - return Container( - height: 136.0, - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: categories.categoryItems.length, - itemBuilder: (context, index) { - numberItem += 1; - if (numberItem > 3) { - numberItem = 1; - } - var category = categories.categoryItems[index]; - return Container( - width: 136.0, - child: Padding( - padding: const EdgeInsets.only(right: 4.0), - child: GestureDetector( - onTap: () { - Navigator.pushNamed(context, navigatorListMeals, - arguments: category); - }, - child: Card( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16.0)), - color: _setColorItemCategory(numberItem), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, vertical: 12.0), - child: Column( - children: [ - Hero( - tag: - "label_item_category_${category.idCategory}", - child: Text( - category.strCategory, - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - ), - ), - ), - Padding(padding: const EdgeInsets.only(top: 8.0)), - Container( - width: 136.0, - height: 72.0, - child: FadeInImage( - fit: BoxFit.cover, - image: NetworkImage( - category.strCategoryThumb, - ), - placeholder: AssetImage( - "assets/images/img_placeholder.jpg"), - ), - ), - ], - ), - ), - ), - ), - ), - ); - }, - ), - ); - } else if (snapshot.hasError) { - return Text(snapshot.error.toString()); - } - return Center(child: buildCircularProgressIndicator()); - }, - ); - } - - Widget _buildListViewLatestRecipe(MediaQueryData mediaQuery) { - return FutureBuilder( - future: homeBloc.getLatestMeals(), - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasData) { - var latestMeals = snapshot.data; - return Container( - height: mediaQuery.size.width / 2 + 64.0, // 256.0 - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: latestMeals.latestMealsItems.length, - itemBuilder: (context, index) { - var latestMealsItem = latestMeals.latestMealsItems[index]; - return Padding( - padding: const EdgeInsets.only(right: 8.0), - child: GestureDetector( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return DetailMealsScreen( - idMeal: latestMealsItem.idMeal, - strMeal: latestMealsItem.strMeal, - strMealThumb: latestMealsItem.strMealThumb, - ); - }, - ), - ); - }, - child: Card( - elevation: 4.0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16.0)), - child: ClipRRect( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16.0), - topRight: Radius.circular(16.0), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - child: Hero( - tag: - "image_detail_meals_${latestMealsItem.idMeal}", - child: FadeInImage( - image: NetworkImage( - latestMealsItem.strMealThumb, - ), - placeholder: AssetImage( - "assets/images/img_placeholder.jpg", - ), - fit: BoxFit.cover, - ), - ), - width: mediaQuery.size.width - 96.0, - height: mediaQuery.size.width / 2, // 192.0 - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text(latestMealsItem.strMeal, - maxLines: 2, - style: Theme.of(context).textTheme.subhead), - ), - ], - ), - ), - ), - ), - ); - }, - ), - ); - } else if (snapshot.hasError) { - return Text(snapshot.error.toString()); - } - return Container( - height: 128.0, - child: Center( - child: buildCircularProgressIndicator(), - ), - ); - }, - ); - } - - Widget _buildTextFieldSearchMeals() { - return GestureDetector( - onTap: () { - Navigator.pushNamed(context, navigatorSearchMeals); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(36.0), - color: Color(0x2FFFFFFF), - ), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12.0, - vertical: 8.0, - ), - child: Row( - children: [ - Icon( - Icons.search, - color: Colors.white, - ), - Padding(padding: EdgeInsets.only(right: 8.0)), - Expanded( - child: TextField( - decoration: InputDecoration.collapsed( - hintText: "Search...", - hintStyle: TextStyle( - color: Colors.white70, - ), - ), - style: TextStyle(color: Colors.white), - maxLines: 1, - enabled: false, - ), - ), - ], - ), - ), - ), - ); - } - - Color _setColorItemCategory(int number) { - if (number == 1) { - return ColorAssets.accentColor; - } else if (number == 2) { - return ColorAssets.secondColorCategoryItem; - } else { - return ColorAssets.thirdColorCategoryItem; - } - } -} diff --git a/lib/src/ui/infoapp/info_app_screen.dart b/lib/src/ui/infoapp/info_app_screen.dart deleted file mode 100644 index b199ea3..0000000 --- a/lib/src/ui/infoapp/info_app_screen.dart +++ /dev/null @@ -1,151 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get_version/get_version.dart'; -import 'package:flutter/services.dart'; - -class InfoAppScreen extends StatefulWidget { - @override - _InfoAppScreenState createState() => _InfoAppScreenState(); -} - -class _InfoAppScreenState extends State { - String versionName = "1.0.1"; - - @override - void initState() { - getVersionApp(); - super.initState(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: SafeArea( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: ListView( - children: [ - Image.asset( - "assets/images/img_logo_512.png", - width: 72.0, - height: 72.0, - ), - Padding( - padding: EdgeInsets.only( - top: 4.0, - ), - ), - SizedBox( - width: double.infinity, - child: Text( - "Food Recipe", - textAlign: TextAlign.center, - style: Theme - .of(context) - .textTheme - .title, - ), - ), - Center( - child: Text( - versionName, - style: Theme - .of(context) - .textTheme - .subtitle, - ), - ), - SizedBox( - width: double.infinity, - child: Text( - "Aplikasi yang menyajikan informasi resep makanan dari seluruh dunia", - textAlign: TextAlign.center, - style: Theme - .of(context) - .textTheme - .subtitle, - ), - ), - Padding(padding: EdgeInsets.only(top: 16.0)), - Container( - width: double.infinity, - height: 1.0, - color: Colors.grey, - ), - Padding(padding: EdgeInsets.only(top: 16.0)), - Text( - "Developer", - style: Theme - .of(context) - .textTheme - .headline, - textAlign: TextAlign.center, - ), - Padding(padding: EdgeInsets.only(top: 8.0)), - Center( - child: ClipRRect( - borderRadius: BorderRadius.circular(900.0), - child: Container( - width: 128.0, - height: 128.0, - child: Image.asset("assets/images/img_yudi_setiawan.jpeg"), - ), - ), - ), - Padding(padding: EdgeInsets.only(top: 8.0)), - Center( - child: Text( - "Yudi Setiawan", - style: Theme - .of(context) - .textTheme - .headline - .merge( - TextStyle(fontSize: 20.0), - ), - ), - ), - Center( - child: Text( - "Software Developer\n specialize in Android development", - textAlign: TextAlign.center, - ), - ), - Padding( - padding: EdgeInsets.only(top: 20.0), - ), - Text( - "API Service", - style: Theme - .of(context) - .textTheme - .headline, - textAlign: TextAlign.center, - ), - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Center( - child: Image.asset("assets/images/img_logo_themealdb.png"), - ), - ), - Center( - child: Text( - "An open source database of Recipes from around the world", - textAlign: TextAlign.center, - ), - ), - ], - ), - ), - ), - ); - } - - getVersionApp() async { - try { - versionName = await GetVersion.projectVersion; - setState(() {}); - } on PlatformException { - versionName = "1.0.1"; - } - } -} diff --git a/lib/src/ui/listmeals/list_meals_screen.dart b/lib/src/ui/listmeals/list_meals_screen.dart deleted file mode 100644 index 2d3052e..0000000 --- a/lib/src/ui/listmeals/list_meals_screen.dart +++ /dev/null @@ -1,225 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:food_recipe/src/blocs/listmeals/list_meals_bloc.dart'; -import 'package:food_recipe/src/database/entity/favorite_meal.dart'; -import 'package:food_recipe/src/models/categories/categories.dart'; -import 'package:food_recipe/src/models/filtercategories/filter_categories.dart'; -import 'package:food_recipe/src/models/lookupmealsbyid/lookup_meals_by_id.dart'; -import 'package:food_recipe/src/ui/detailmeals/detail_meals_screen.dart'; -import 'package:food_recipe/src/utils/utils.dart'; - -class ListMealsScreen extends StatefulWidget { - @override - _ListMealsScreenState createState() => _ListMealsScreenState(); -} - -class _ListMealsScreenState extends State { - CategoryItem categoryItem; - - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - var mediaQuery = MediaQuery.of(context); - categoryItem = ModalRoute.of(context).settings.arguments; - - return Scaffold( - body: Container( - width: double.infinity, - height: double.infinity, - child: Stack( - children: [ - buildWidgetBackgroundCircle(mediaQuery), - _buildWidgetContent(mediaQuery), - ], - ), - ), - ); - } - - Widget _buildWidgetContent(MediaQueryData mediaQuery) { - return SafeArea( - minimum: EdgeInsets.symmetric(horizontal: 16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding(padding: EdgeInsets.only(top: 16.0)), - Hero( - tag: "label_item_category_${categoryItem.idCategory}", - child: Text( - categoryItem.strCategory, - style: Theme.of(context) - .textTheme - .display2 - .merge(TextStyle(fontWeight: FontWeight.bold)), - maxLines: 1, - ), - ), - Padding(padding: EdgeInsets.only(top: 16.0)), - Expanded( - child: FutureBuilder( - future: - listMealsBloc.getFilterCategories(categoryItem.strCategory), - builder: (BuildContext context, - AsyncSnapshot snapshot) { - if (snapshot.hasData) { - FilterCategories filterCategories = snapshot.data; - return ListView.builder( - shrinkWrap: true, - itemCount: filterCategories.filterCategoryItems.length, - itemBuilder: (context, index) { - FilterCategoryItem filterCategoryItem = - filterCategories.filterCategoryItems[index]; - return CardMeal(filterCategoryItem); - }, - ); - } else if (snapshot.hasError) { - return Text(snapshot.error.toString()); - } - return Center(child: buildCircularProgressIndicator()); - }, - ), - ), - ], - ), - ); - } -} - -class CardMeal extends StatefulWidget { - final FilterCategoryItem filterCategoryItem; - - CardMeal(this.filterCategoryItem); - - @override - _CardMealState createState() => _CardMealState(); -} - -class _CardMealState extends State { - @override - Widget build(BuildContext context) { - var mediaQuery = MediaQuery.of(context); - return Padding( - padding: const EdgeInsets.only(bottom: 16.0), - child: GestureDetector( - onTap: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) { - return DetailMealsScreen( - idMeal: widget.filterCategoryItem.idMeal, - strMeal: widget.filterCategoryItem.strMeal, - strMealThumb: widget.filterCategoryItem.strMealThumb, - ); - }), - ); - }, - child: Card( - elevation: 8.0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16.0), - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(16.0), - child: Stack( - children: [ - Hero( - tag: "image_detail_meals_${widget.filterCategoryItem.idMeal}", - child: FadeInImage( - image: NetworkImage(widget.filterCategoryItem.strMealThumb), - placeholder: - AssetImage("assets/images/img_placeholder.jpg"), - fit: BoxFit.cover, - width: double.infinity, - height: mediaQuery.size.width / 1.5, - ), - ), - Container( - width: double.infinity, - height: mediaQuery.size.width / 1.5, - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - stops: [0.1, 0.9], - colors: [ - Color(0xFFFFFFFF), - Color(0x00FFFFFF), - ], - ), - ), - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Text( - widget.filterCategoryItem.strMeal, - style: Theme.of(context).textTheme.title, - maxLines: 2, - ), - ), - GestureDetector( - onTap: () { - var isFavorite = widget.filterCategoryItem.isFavorite; - if (isFavorite) { - listMealsBloc - .deleteFavoriteMealById( - widget.filterCategoryItem.idMeal) - .then((status) { - setState(() { - widget.filterCategoryItem.isFavorite = - !isFavorite; - }); - }); - } else { - Future lookupMealsById = - listMealsBloc.getDetailMealById( - widget.filterCategoryItem.idMeal); - lookupMealsById.then((value) { - if (value != null) { - var item = value.lookupMealsbyIdItems[0]; - FavoriteMeal favoriteMeal = - FavoriteMeal.fromJson(item.toJson()); - listMealsBloc - .addFavoriteMeal(favoriteMeal) - .then((status) { - setState(() { - widget.filterCategoryItem.isFavorite = - !isFavorite; - }); - }); - } else { - Scaffold.of(context).showSnackBar(SnackBar( - content: - Text("Failed added to favorite meal"))); - } - }); - } - }, - child: CircleAvatar( - backgroundColor: Color(0xAFE8364B), - child: Icon( - widget.filterCategoryItem.isFavorite - ? Icons.favorite - : Icons.favorite_border, - color: Colors.white, - ), - ), - ), - ], - ), - ), - ], - ), - ), - ), - ), - ); - } -} diff --git a/lib/src/ui/searchmeals/search_meals_screen.dart b/lib/src/ui/searchmeals/search_meals_screen.dart deleted file mode 100644 index 16a32c7..0000000 --- a/lib/src/ui/searchmeals/search_meals_screen.dart +++ /dev/null @@ -1,338 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:food_recipe/src/blocs/searchmeals/search_meals_bloc.dart'; -import 'package:food_recipe/src/database/entity/favorite_meal.dart'; -import 'package:food_recipe/src/models/lookupmealsbyid/lookup_meals_by_id.dart'; -import 'package:food_recipe/src/models/searchmeals/search_meals.dart'; -import 'package:food_recipe/src/ui/detailmeals/detail_meals_screen.dart'; -import 'package:food_recipe/src/utils/utils.dart'; -import 'package:food_recipe/values/color_assets.dart'; - -class SearchMealsScreen extends StatefulWidget { - @override - _SearchMealsScreenState createState() => _SearchMealsScreenState(); -} - -class _SearchMealsScreenState extends State { - SearchMealsBloc _searchMealsBloc; - - @override - void initState() { - _searchMealsBloc = SearchMealsBloc(); - super.initState(); - } - - @override - void dispose() { - _searchMealsBloc.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - var mediaQuery = MediaQuery.of(context); - - return Scaffold( - resizeToAvoidBottomInset: false, - body: Container( - child: Stack( - children: [ - _buildWidgetBackground(), - _buildWidgetContent(mediaQuery, context), - ], - ), - ), - ); - } - - Widget _buildWidgetContent(MediaQueryData mediaQuery, BuildContext context) { - return SafeArea( - minimum: EdgeInsets.symmetric(horizontal: 16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding(padding: EdgeInsets.only(top: 16.0 + mediaQuery.padding.top)), - Text( - "Search\nSomething ?", - style: Theme.of(context).textTheme.display1.merge( - TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontFamily: "Roboto", - ), - ), - ), - Padding(padding: EdgeInsets.only(top: 16.0)), - _buildTextFieldSearchMeals(), - Padding(padding: EdgeInsets.only(top: 16.0)), - Expanded(child: _buildResultSearchMeals(mediaQuery)), - ], - ), - ); - } - - Widget _buildResultSearchMeals(MediaQueryData mediaQuery) { - return StreamBuilder( - stream: _searchMealsBloc.resultSearchMealsByKeyword, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasData) { - SearchMeals searchMeals = snapshot.data; - if (searchMeals.isLoading) { - return Center(child: buildCircularProgressIndicator()); - } else if (searchMeals.searchMealsItems == null || - searchMeals.searchMealsItems.isEmpty) { - return Container(); - } - return ListView.builder( - shrinkWrap: true, - itemCount: searchMeals.searchMealsItems.length, - itemBuilder: (context, index) { - var searchMealsItem = searchMeals.searchMealsItems[index]; - return CardMeal(searchMealsItem, _searchMealsBloc); - }, - ); - } else if (snapshot.hasError) { - return Text(snapshot.error.toString()); - } - return Container(); - }, - ); - } - - Widget _buildTextFieldSearchMeals() { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(36.0), - color: Color(0x2FFFFFFF), - ), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12.0, - vertical: 8.0, - ), - child: TextFieldSearch(_searchMealsBloc), - ), - ); - } - - Widget _buildWidgetBackground() { - return Column( - children: [ - Expanded( - child: Container( - color: ColorAssets.primarySwatchColor, - ), - ), - Expanded( - child: Container( - color: Colors.white, - ), - ) - ], - ); - } -} - -class CardMeal extends StatefulWidget { - final SearchMealsItem searchMealsItem; - final SearchMealsBloc searchMealsBloc; - - CardMeal(this.searchMealsItem, this.searchMealsBloc); - - @override - _CardMealState createState() => _CardMealState(); -} - -class _CardMealState extends State { - @override - Widget build(BuildContext context) { - var mediaQuery = MediaQuery.of(context); - return Padding( - padding: EdgeInsets.only(bottom: 16.0), - child: GestureDetector( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return DetailMealsScreen( - idMeal: widget.searchMealsItem.idMeal, - strMeal: widget.searchMealsItem.strMeal, - strMealThumb: widget.searchMealsItem.strMealThumb, - ); - }, - ), - ); - }, - child: Card( - elevation: 8.0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16.0), - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(16.0), - child: Stack( - children: [ - Hero( - tag: "image_detail_meals_${widget.searchMealsItem.idMeal}", - child: FadeInImage( - image: NetworkImage(widget.searchMealsItem.strMealThumb), - placeholder: - AssetImage("assets/images/img_placeholder.jpg"), - fit: BoxFit.cover, - width: double.infinity, - height: mediaQuery.size.width / 1.5, - ), - ), - Container( - width: double.infinity, - height: mediaQuery.size.width / 1.5, - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - stops: [ - 0.1, - 0.9 - ], - colors: [ - Color(0xFFFFFFFF), - Color(0x00FFFFFF), - ]), - ), - ), - Padding( - padding: EdgeInsets.all(16.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Text( - widget.searchMealsItem.strMeal, - style: Theme.of(context).textTheme.title, - maxLines: 2, - ), - ), - GestureDetector( - onTap: () { - var isFavorite = widget.searchMealsItem.isFavorite; - if (isFavorite) { - widget.searchMealsBloc - .deleteFavoriteMealById( - widget.searchMealsItem.idMeal) - .then((status) { - setState(() { - widget.searchMealsItem.isFavorite = !isFavorite; - }); - }); - } else { - Future lookupMealsById = - widget.searchMealsBloc.getDetailMealById( - widget.searchMealsItem.idMeal); - lookupMealsById.then((value) { - if (value != null) { - var item = value.lookupMealsbyIdItems[0]; - FavoriteMeal favoriteMeal = - FavoriteMeal.fromJson(item.toJson()); - widget.searchMealsBloc - .addFavoriteMeal(favoriteMeal) - .then((status) { - setState(() { - widget.searchMealsItem.isFavorite = - !isFavorite; - }); - }); - } else { - Scaffold.of(context).showSnackBar(SnackBar( - content: - Text("Failed added to favorite meal"))); - } - }); - } - }, - child: CircleAvatar( - backgroundColor: Color(0xAFE8364B), - child: widget.searchMealsItem.isFavorite - ? Icon( - Icons.favorite, - color: Colors.white, - ) - : Icon( - Icons.favorite_border, - color: Colors.white, - ), - ), - ), - ], - ), - ), - ], - ), - ), - ), - ), - ); - } -} - -class TextFieldSearch extends StatefulWidget { - final SearchMealsBloc searchMealsBloc; - - TextFieldSearch(this.searchMealsBloc); - - @override - _TextFieldSearchState createState() => _TextFieldSearchState(); -} - -class _TextFieldSearchState extends State { - var _textEditingControllerKeyword = TextEditingController(); - - @override - Widget build(BuildContext context) { - return Row( - children: [ - Icon( - Icons.search, - color: Colors.white, - ), - Padding(padding: EdgeInsets.only(right: 8.0)), - Expanded( - child: TextField( - autofocus: true, - controller: _textEditingControllerKeyword, - decoration: InputDecoration.collapsed( - hintText: "Search...", - hintStyle: TextStyle( - color: Colors.white70, - ), - ), - style: TextStyle(color: Colors.white), - maxLines: 1, - textInputAction: TextInputAction.search, - onChanged: (value) { - setState(() {}); - if (value.isEmpty) { - widget.searchMealsBloc.searchMealsByKeyword(value); - } - }, - onSubmitted: (value) { - setState(() {}); - widget.searchMealsBloc.searchMealsByKeyword(value); - }, - ), - ), - _textEditingControllerKeyword.text.isEmpty - ? Container() - : GestureDetector( - onTap: () { - setState(() => _textEditingControllerKeyword.clear()); - widget.searchMealsBloc.searchMealsByKeyword(""); - }, - child: Icon( - Icons.clear, - color: Colors.white, - ), - ), - ], - ); - } -} diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart deleted file mode 100644 index adf1e61..0000000 --- a/lib/src/utils/utils.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; - -const navigatorListMeals = "/list_meals"; -const navigatorSearchMeals = "/search_meals"; -const navigatorInfoApp = "/info_app"; - -Widget buildCircularProgressIndicator() { - if (Platform.isIOS) { - return CupertinoActivityIndicator(); - } else { - return CircularProgressIndicator(); - } -} - -Widget buildWidgetBackgroundCircle(MediaQueryData mediaQuery) { - return Positioned( - top: -100.0, - left: -100.0, - child: Container( - width: mediaQuery.size.height / 2.2, - height: mediaQuery.size.height / 2.2, - decoration: BoxDecoration( - color: Color(0x3FE8364B), - shape: BoxShape.circle, - ), - ), - ); -} diff --git a/lib/values/color_assets.dart b/lib/values/color_assets.dart deleted file mode 100644 index b06c337..0000000 --- a/lib/values/color_assets.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:flutter/material.dart'; - -class ColorAssets { - static final Color primarySwatchColor = Color(0xFFE8364B); - static final Color accentColor = Color(0xFFEE8F3B); - static final Color primaryTextColor = Colors.grey[900]; - static final Color secondColorCategoryItem = Color(0xFF3FD231); - static final Color thirdColorCategoryItem = Color(0xFFF03F7D); -} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 8f17bf4..3ff6591 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,111 +1,160 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.0" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "0.36.3" + version: "0.39.14" args: dependency: transitive description: name: args url: "https://pub.dartlang.org" source: hosted - version: "1.5.2" + version: "1.6.0" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "2.4.1" + bloc: + dependency: transitive + description: + name: bloc + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" + bloc_test: + dependency: "direct dev" + description: + name: bloc_test + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.0" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.0" build: dependency: transitive description: name: build url: "https://pub.dartlang.org" source: hosted - version: "1.1.4" + version: "1.3.0" build_config: dependency: transitive description: name: build_config url: "https://pub.dartlang.org" source: hosted - version: "0.4.0" + version: "0.4.2" build_daemon: dependency: transitive description: name: build_daemon url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "2.1.4" build_resolvers: dependency: transitive description: name: build_resolvers url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.3.11" build_runner: - dependency: "direct main" + dependency: "direct dev" description: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "1.5.1" + version: "1.10.1" build_runner_core: dependency: transitive description: name: build_runner_core url: "https://pub.dartlang.org" source: hosted - version: "3.0.6" + version: "6.0.1" built_collection: dependency: transitive description: name: built_collection url: "https://pub.dartlang.org" source: hosted - version: "4.2.2" + version: "4.3.2" built_value: dependency: transitive description: name: built_value url: "https://pub.dartlang.org" source: hosted - version: "6.6.0" + version: "7.1.0" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0+1" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "1.1.3" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + cli_util: + dependency: transitive + description: + name: cli_util + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.4" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" code_builder: dependency: transitive description: name: code_builder url: "https://pub.dartlang.org" source: hosted - version: "3.2.0" + version: "3.4.1" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.11" + version: "1.14.12" convert: dependency: transitive description: @@ -113,86 +162,147 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.1" - cookie_jar: + coverage: dependency: transitive description: - name: cookie_jar + name: coverage url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "0.14.0" crypto: dependency: transitive description: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.1.5" csslib: dependency: transitive description: name: csslib url: "https://pub.dartlang.org" source: hosted - version: "0.16.0" + version: "0.16.2" cupertino_icons: dependency: "direct main" description: name: cupertino_icons url: "https://pub.dartlang.org" source: hosted - version: "0.1.2" + version: "0.1.3" dart_style: dependency: transitive description: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "1.2.8" + version: "1.3.6" + dartz: + dependency: "direct main" + description: + name: dartz + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.9" + data_connection_checker: + dependency: "direct main" + description: + name: data_connection_checker + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.4" + device_info: + dependency: "direct main" + description: + name: device_info + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.2+7" + device_info_platform_interface: + dependency: transitive + description: + name: device_info_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" dio: dependency: "direct main" description: name: dio url: "https://pub.dartlang.org" source: hosted - version: "2.1.7" + version: "3.0.10" + equatable: + dependency: "direct main" + description: + name: equatable + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.4" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "5.2.1" fixnum: dependency: transitive description: name: fixnum url: "https://pub.dartlang.org" source: hosted - version: "0.10.9" + version: "0.10.11" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.1" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.1" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" - front_end: - dependency: transitive - description: - name: front_end - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.18" - get_version: + get_it: dependency: "direct main" description: - name: get_version + name: get_it url: "https://pub.dartlang.org" source: hosted - version: "0.1.0" + version: "4.0.4" glob: dependency: transitive description: name: glob url: "https://pub.dartlang.org" source: hosted - version: "1.1.7" + version: "1.2.0" graphs: dependency: transitive description: @@ -206,133 +316,210 @@ packages: name: html url: "https://pub.dartlang.org" source: hosted - version: "0.14.0+2" + version: "0.14.0+3" http: dependency: transitive description: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.12.0+2" + version: "0.12.2" http_multi_server: dependency: transitive description: name: http_multi_server url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.2.0" http_parser: dependency: transitive description: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "3.1.3" + version: "3.1.4" + intl: + dependency: transitive + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.1" + intl_translation: + dependency: "direct dev" + description: + name: intl_translation + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.10+1" io: dependency: transitive description: name: io url: "https://pub.dartlang.org" source: hosted - version: "0.3.3" + version: "0.3.4" js: dependency: transitive description: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.1+1" + version: "0.6.2" json_annotation: dependency: transitive description: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "2.4.0" + version: "3.0.1" json_serializable: - dependency: "direct main" + dependency: "direct dev" description: name: json_serializable url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" - kernel: - dependency: transitive - description: - name: kernel - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.18" + version: "3.4.1" logging: dependency: transitive description: name: logging url: "https://pub.dartlang.org" source: hosted - version: "0.11.3+2" + version: "0.11.4" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.5" + version: "0.12.6" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.1.8" mime: dependency: transitive description: name: mime url: "https://pub.dartlang.org" source: hosted - version: "0.9.6+3" - package_config: + version: "0.9.7" + mockito: + dependency: "direct dev" + description: + name: mockito + url: "https://pub.dartlang.org" + source: hosted + version: "4.1.1" + multi_server_socket: dependency: transitive description: - name: package_config + name: multi_server_socket url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" - package_info: + version: "1.0.2" + nested: dependency: transitive description: - name: package_info + name: nested url: "https://pub.dartlang.org" source: hosted - version: "0.4.0+4" - package_resolver: + version: "0.0.4" + node_interop: dependency: transitive description: - name: package_resolver + name: node_interop url: "https://pub.dartlang.org" source: hosted - version: "1.0.10" + version: "1.1.1" + node_io: + dependency: transitive + description: + name: node_io + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + node_preamble: + dependency: transitive + description: + name: node_preamble + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.12" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.3" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.6.2" + version: "1.7.0" path_provider: - dependency: "direct main" + dependency: transitive description: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" - pedantic: + version: "1.6.14" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1+2" + path_provider_macos: dependency: transitive + description: + name: path_provider_macos + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.4+3" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + pedantic: + dependency: "direct dev" description: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.9.0" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.4" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" pool: dependency: transitive description: @@ -340,41 +527,69 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.4.0" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.13" + provider: + dependency: transitive + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "4.3.2+1" pub_semver: dependency: transitive description: name: pub_semver url: "https://pub.dartlang.org" source: hosted - version: "1.4.2" + version: "1.4.4" pubspec_parse: dependency: transitive description: name: pubspec_parse url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.1.5" quiver: dependency: transitive description: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" + version: "2.1.3" rxdart: - dependency: "direct main" + dependency: transitive description: name: rxdart url: "https://pub.dartlang.org" source: hosted - version: "0.22.0" + version: "0.24.1" shelf: dependency: transitive description: name: shelf url: "https://pub.dartlang.org" source: hosted - version: "0.7.5" + version: "0.7.9" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + shelf_static: + dependency: transitive + description: + name: shelf_static + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.8" shelf_web_socket: dependency: transitive description: @@ -382,6 +597,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.2.3" + shimmer: + dependency: "direct main" + description: + name: shimmer + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" sky_engine: dependency: transitive description: flutter @@ -393,21 +615,42 @@ packages: name: source_gen url: "https://pub.dartlang.org" source: hosted - version: "0.9.4+2" + version: "0.9.6" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + source_maps: + dependency: transitive + description: + name: source_maps + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.9" source_span: dependency: transitive description: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.5.5" + version: "1.7.0" sqflite: - dependency: "direct main" + dependency: transitive description: name: sqflite url: "https://pub.dartlang.org" source: hosted - version: "1.1.5" + version: "1.3.1+1" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2+1" stack_trace: dependency: transitive description: @@ -428,21 +671,21 @@ packages: name: stream_transform url: "https://pub.dartlang.org" source: hosted - version: "0.0.19" + version: "1.2.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "1.0.5" synchronized: dependency: transitive description: name: synchronized url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.2.0+2" term_glyph: dependency: transitive description: @@ -450,20 +693,34 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + test: + dependency: transitive + description: + name: test + url: "https://pub.dartlang.org" + source: hosted + version: "1.14.7" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.5" + version: "0.2.16" + test_core: + dependency: transitive + description: + name: test_core + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.7" timing: dependency: transitive description: name: timing url: "https://pub.dartlang.org" source: hosted - version: "0.1.1+1" + version: "0.1.1+2" typed_data: dependency: transitive description: @@ -471,13 +728,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.6" - url_launcher: - dependency: "direct main" + uuid: + dependency: transitive description: - name: url_launcher + name: uuid url: "https://pub.dartlang.org" source: hosted - version: "5.0.3" + version: "2.2.2" vector_math: dependency: transitive description: @@ -485,27 +742,48 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.8" + vm_service: + dependency: transitive + description: + name: vm_service + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.0" watcher: dependency: transitive description: name: watcher url: "https://pub.dartlang.org" source: hosted - version: "0.9.7+10" + version: "0.9.7+15" web_socket_channel: dependency: transitive description: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "1.0.13" + version: "1.1.0" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.3" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0" yaml: dependency: transitive description: name: yaml url: "https://pub.dartlang.org" source: hosted - version: "2.1.15" + version: "2.2.1" sdks: - dart: ">=2.3.0 <3.0.0" - flutter: ">=1.5.0 <2.0.0" + dart: ">=2.8.0 <3.0.0" + flutter: ">=1.16.0 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 6e1267a..e4cafec 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,47 +1,78 @@ name: food_recipe -description: Food Recipe - Challenge Dicoding (Create Beautiful Apps Using Flutter) +description: Food Recipe version: 1.0.3+4 environment: - sdk: ">=2.1.0 <3.0.0" + sdk: ">=2.7.0 <3.0.0" dependencies: flutter: sdk: flutter + flutter_localizations: + sdk: flutter # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^0.1.2 + cupertino_icons: ^0.1.3 - # Reactive functional programming library for Google Dart, based on ReactiveX. - rxdart: ^0.22.0 + # A powerful http client for Dart, which supports interceptors, FormData, Request Cancellation, + # File Downloading, Timeout etc. + dio: ^3.0.9 - # A powerful Http client for Dart. - dio: ^2.1.7 + # Flutter widgets that make it easy to implement the BLoC design pattern. + flutter_bloc: ^4.0.0 - # Automatically generate code for converting to and from JSON - json_serializable: ^3.0.0 + # Flutter library to load and cache network image. Can also be used with placeholder and + # error widgets. + cached_network_image: ^2.2.0+1 - # Tools to write binaries that run builders. - build_runner: ^1.5.1 + # Simple direct Service Locator that allows to decouple the interface from a concrete + # implementation and to access the concrete implementation from everywhere in your app. + get_it: ^4.0.1 + + # Functional programming in Dart. Purify your Dart code using efficient immutable data + # structures monads, lenses and other FP tools. + dartz: ^0.8.9 - # Flutter plugin for launching a URL on Android and iOS. - url_launcher: ^5.0.3 + # An abstract class that helps to implement equality without needing to explicitly + # override == and hashCode. + equatable: ^1.1.1 - # Flutter plugin for SQLite. - sqflite: ^1.1.5 + # A pure Dart library that checks for internet by opening a socket to a list of specified + # addresses, each with individual port and timeout. + data_connection_checker: ^0.3.4 - # Flutter plugin for getting commonly used locations on the Android & iOS. - path_provider: ^1.1.0 + # Flutter plugin providing detailed information about the device (make, model, etc) and + # Android or OS version the app is running on. + device_info: ^0.4.2+1 - # Get the version name, version code, platform, OS version and app ID. - get_version: ^0.1.0 + # A package provides an easy way to add shimmer effect in Flutter. + shimmer: ^1.1.1 dev_dependencies: flutter_test: sdk: flutter + # Tools to write binaries that run builders. + build_runner: ^1.10.0 + + # A mock framework inspired by Mockito. + mockito: ^4.1.1 + + # A testing library which make it easy to test blocs. + bloc_test: ^5.0.0 + + # Automatically generate code for converting to and from JSON by annotating Dart classes. + json_serializable: ^3.3.0 + + # Tools for provides messages extraction and code generation from translated messages for the + # Intl packages. + intl_translation: ^0.17.9 + + # How to get the most value from Dart static analysis. + pedantic: ^1.9.0 + flutter: # The following line ensures that the Material Icons font is @@ -50,34 +81,4 @@ flutter: uses-material-design: true assets: - - assets/images/img_not_found.jpg - - assets/images/img_placeholder.jpg - - assets/images/img_logo_512.png - - assets/images/img_yudi_setiawan.jpeg - - assets/images/img_logo_themealdb.png - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages + - assets/images/ \ No newline at end of file diff --git a/test/core/error/failure_test.dart b/test/core/error/failure_test.dart new file mode 100644 index 0000000..a1b015f --- /dev/null +++ b/test/core/error/failure_test.dart @@ -0,0 +1,52 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:food_recipe/core/error/failure.dart'; + +void main() { + final tErrorMessage = 'testErrorMessage'; + + group('ServerFailure', () { + final tServerFailure = ServerFailure(tErrorMessage); + + test( + 'pastikan nilai props adalah [errorMessage]', + () async { + // assert + expect(tServerFailure.props, [tErrorMessage]); + }, + ); + + test( + 'pastikan output dari fungsi toString adalah ' + 'ServerFailure(errorMessage: testErrorMessage}', + () async { + // assert + expect(tServerFailure.toString(), 'ServerFailure{errorMessage: $tErrorMessage}'); + }, + ); + }); + + group('ConnectionFailure', () { + final tConnectionFailure = ConnectionFailure(); + final tErrorMessage = tConnectionFailure.errorMessage; + + test( + 'pastikan nilai props adalah [errorMessage]', + () async { + // assert + expect(tConnectionFailure.props, [tErrorMessage]); + }, + ); + + test( + 'pastikan output dari fungsi toString adalah ' + 'ConnectionFailure{errorMessage: testErrorMessage}', + () async { + // assert + expect( + tConnectionFailure.toString(), + 'ConnectionFailure{errorMessage: ${tConnectionFailure.errorMessage}}', + ); + }, + ); + }); +} diff --git a/test/core/network/network_info_test.dart b/test/core/network/network_info_test.dart new file mode 100644 index 0000000..726e06e --- /dev/null +++ b/test/core/network/network_info_test.dart @@ -0,0 +1,51 @@ +import 'package:data_connection_checker/data_connection_checker.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:food_recipe/core/network/network_info.dart'; +import 'package:mockito/mockito.dart'; + +class MockDataConnectionChecker extends Mock implements DataConnectionChecker {} + +void main() { + NetworkInfoImpl networkInfoImpl; + MockDataConnectionChecker mockDataConnectionChecker; + + setUp(() { + mockDataConnectionChecker = MockDataConnectionChecker(); + networkInfoImpl = NetworkInfoImpl(mockDataConnectionChecker); + }); + + group('ConnectionChecker', () { + test( + 'pastikan fungsi DataConnectionChecker.hasConnection benar-benar terpanggil dan koneksi internetnya terhubung', + () async { + // arrange + final tHasConnection = Future.value(true); + when(mockDataConnectionChecker.hasConnection).thenAnswer((_) => tHasConnection); + + // act + final result = networkInfoImpl.isConnected; + + // assert + verify(mockDataConnectionChecker.hasConnection); + expect(result, tHasConnection); + }, + ); + + test( + 'pastikan fungsi DataConnectionChecker.hasConnection benar-benr terpanggil dan koneksi internetnya ' + 'tidak terhbung', + () async { + // arrange + final tHasConnection = Future.value(false); + when(mockDataConnectionChecker.hasConnection).thenAnswer((_) => tHasConnection); + + // act + final result = networkInfoImpl.isConnected; + + // assert + verify(mockDataConnectionChecker.hasConnection); + expect(result, tHasConnection); + }, + ); + }); +} \ No newline at end of file diff --git a/test/core/usecase/usecase_test.dart b/test/core/usecase/usecase_test.dart new file mode 100644 index 0000000..c5e1020 --- /dev/null +++ b/test/core/usecase/usecase_test.dart @@ -0,0 +1,12 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:food_recipe/core/usecase/usecase.dart'; + +void main() { + test( + 'pastikan nilai props dari class NoParams adalah []', + () async { + // assert + expect(NoParams().props, []); + }, + ); +} \ No newline at end of file diff --git a/test/feature/data/datasource/themealdb/themealdb_remote_data_source_test.dart b/test/feature/data/datasource/themealdb/themealdb_remote_data_source_test.dart new file mode 100644 index 0000000..c590336 --- /dev/null +++ b/test/feature/data/datasource/themealdb/themealdb_remote_data_source_test.dart @@ -0,0 +1,302 @@ +import 'dart:convert'; + +import 'package:dio/dio.dart'; +import 'package:food_recipe/feature/data/model/filterbycategory/filter_by_category_response.dart'; +import 'package:food_recipe/feature/data/model/mealcategory/meal_category_response.dart'; +import 'package:food_recipe/feature/data/model/searchmealbyname/search_meal_by_name_response.dart'; +import 'package:matcher/matcher.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:food_recipe/config/base_url_config.dart'; +import 'package:food_recipe/feature/data/datasource/themealdb/themealdb_remote_data_source.dart'; +import 'package:food_recipe/feature/data/model/detailmeal/detail_meal_response.dart'; +import 'package:mockito/mockito.dart'; + +import '../../../../fixture/fixture_reader.dart'; + +class MockDioAdapter extends Mock implements HttpClientAdapter {} + +class MockDio extends Mock implements Dio {} + +void main() { + TheMealDbRemoteDataSource theMealDbRemoteDataSource; + MockDio mockDio; + MockDioAdapter mockDioAdapter; + + setUp(() { + mockDio = MockDio(); + mockDioAdapter = MockDioAdapter(); + mockDio.httpClientAdapter = mockDioAdapter; + mockDio.options = BaseOptions(baseUrl: BaseUrlConfig().baseUrlMealDb); + theMealDbRemoteDataSource = TheMealDbRemoteDataSourceImpl(dio: mockDio); + }); + + group('getRandomMeal', () { + final tDetailMealResponse = DetailMealResponse.fromJson( + json.decode( + fixture('detail_meal_response.json'), + ), + ); + + void setUpMockDioSuccess() { + final responsePayload = json.decode(fixture('api_detail_meal_response.json')); + final response = Response( + data: responsePayload, + statusCode: 200, + headers: Headers.fromMap({ + Headers.contentTypeHeader: [Headers.jsonContentType], + }), + ); + when(mockDio.get(any)).thenAnswer((_) async => response); + } + + test( + 'pastikan endpoint getRandomMeal benar-benar terpanggil dengan method GET', + () async { + // arrange + setUpMockDioSuccess(); + + // act + await theMealDbRemoteDataSource.getRandomMeal(); + + // assert + verify(mockDio.get('/random.php')); + }, + ); + + test( + 'pastikan mengembalikan objek model DetailMealResponse ketika menerima respon sukses (200) ' + 'dari endpoint', + () async { + // arrange + setUpMockDioSuccess(); + + // act + final result = await theMealDbRemoteDataSource.getRandomMeal(); + + // assert + expect(result, tDetailMealResponse); + }, + ); + + test( + 'pastikan akan menerima exception DioError ketika menerima respon kegagalan dari endpoint', + () async { + // arrange + final response = Response( + data: 'Bad Request', + statusCode: 400, + ); + when(mockDio.get(any)).thenAnswer((_) async => response); + + // act + final call = theMealDbRemoteDataSource.getRandomMeal(); + + // assert + expect(() => call, throwsA(TypeMatcher())); + }, + ); + }); + + group('getCategoryMeal', () { + final tMealCategoryResponse = MealCategoryResponse.fromJson( + json.decode( + fixture('meal_category_response.json'), + ), + ); + + void setUpMockDioSuccess() { + final responsePayload = json.decode(fixture('meal_category_response.json')); + final response = Response( + data: responsePayload, + statusCode: 200, + headers: Headers.fromMap({ + Headers.contentTypeHeader: [Headers.jsonContentType], + }), + ); + when(mockDio.get(any)).thenAnswer((_) async => response); + } + + test( + 'pastikan endpoint getCategoryMeal benar-benar terpanggil dengan method GET', + () async { + // arrange + setUpMockDioSuccess(); + + // act + await theMealDbRemoteDataSource.getCategoryMeal(); + + // assert + verify(mockDio.get('/categories.php')); + }, + ); + + test( + 'pastikan mengembalikan objek model MealCategoryResponse ketika menerima respon sukses (200) ' + 'dari endpoint', + () async { + // arrange + setUpMockDioSuccess(); + + // act + final result = await theMealDbRemoteDataSource.getCategoryMeal(); + + // assert + expect(result, tMealCategoryResponse); + }, + ); + + test( + 'pastikan akan menerima exception DioError ketika menerima respon kegagalan dari endpoint', + () async { + // arrange + final response = Response( + data: 'Bad Request', + statusCode: 400, + ); + when(mockDio.get(any)).thenAnswer((_) async => response); + + // act + final call = theMealDbRemoteDataSource.getCategoryMeal(); + + // assert + expect(() => call, throwsA(TypeMatcher())); + }, + ); + }); + + group('getFilterByCategory', () { + final tCategory = 'testCategory'; + final tFilterByCategoryResponse = FilterByCategoryResponse.fromJson( + json.decode( + fixture('filter_by_category_response.json'), + ), + ); + + void setUpMockDioSuccess() { + final responsePayload = json.decode(fixture('filter_by_category_response.json')); + final response = Response( + data: responsePayload, + statusCode: 200, + headers: Headers.fromMap({ + Headers.contentTypeHeader: [Headers.jsonContentType], + }), + ); + when(mockDio.get(any, queryParameters: anyNamed('queryParameters'))).thenAnswer((_) async => response); + } + + test( + 'pastikan endpoint getFilterByCategory benar-benar terpanggil dengan method GET', + () async { + // arrange + setUpMockDioSuccess(); + + // act + await theMealDbRemoteDataSource.getFilterByCategory(tCategory); + + // assert + verify(mockDio.get('/filter.php', queryParameters: anyNamed('queryParameters'))); + }, + ); + + test( + 'pastikan mengembalikan objek model FilterByCategoryResponse ketika menerima respon sukses (200) ' + 'dari endpoint', + () async { + // arrange + setUpMockDioSuccess(); + + // act + final result = await theMealDbRemoteDataSource.getFilterByCategory(tCategory); + + // assert + expect(result, tFilterByCategoryResponse); + }, + ); + + test( + 'pastikan akan menerima exception DioError ketika menerima respon kegagalan dari endpoint', + () async { + // arrange + final response = Response( + data: 'Bad Request', + statusCode: 400, + ); + when(mockDio.get(any, queryParameters: anyNamed('queryParameters'))).thenAnswer((_) async => response); + + // act + final call = theMealDbRemoteDataSource.getFilterByCategory(tCategory); + + // assert + expect(() => call, throwsA(TypeMatcher())); + }, + ); + }); + + group('searchMealByName', () { + final tName = 'testName'; + final tSearchMealByNameResponse = SearchMealByNameResponse.fromJson( + json.decode( + fixture('search_meal_by_name_response.json'), + ), + ); + + void setUpMockDioSuccess() { + final responsePayload = json.decode(fixture('search_meal_by_name_response.json')); + final response = Response( + data: responsePayload, + statusCode: 200, + headers: Headers.fromMap({ + Headers.contentTypeHeader: [Headers.jsonContentType], + }), + ); + when(mockDio.get(any, queryParameters: anyNamed('queryParameters'))).thenAnswer((_) async => response); + } + + test( + 'pastikan endpoint searchMealByName benar-benar terpanggil dengan method GET', + () async { + // arrange + setUpMockDioSuccess(); + + // act + await theMealDbRemoteDataSource.searchMealByName(tName); + + // assert + verify(mockDio.get('/search.php', queryParameters: anyNamed('queryParameters'))); + }, + ); + + test( + 'pastikan mengembalikan objek model SearchMealByNameResponse ketika menerima respon sukses (200) ' + 'dari endpoint', + () async { + // arrange + setUpMockDioSuccess(); + + // act + final result = await theMealDbRemoteDataSource.searchMealByName(tName); + + // assert + expect(result, tSearchMealByNameResponse); + }, + ); + + test( + 'pastikan akan menerima exception DioError ketika menerima respon kegagalan dari endpoint', + () async { + // arrange + final response = Response( + data: 'Bad Request', + statusCode: 400, + ); + when(mockDio.get(any, queryParameters: anyNamed('queryParameters'))).thenAnswer((_) async => response); + + // act + final call = theMealDbRemoteDataSource.searchMealByName(tName); + + // assert + expect(() => call, throwsA(TypeMatcher())); + }, + ); + }); +} diff --git a/test/feature/data/model/detailmeal/detail_meal_response_test.dart b/test/feature/data/model/detailmeal/detail_meal_response_test.dart new file mode 100644 index 0000000..11f5aa0 --- /dev/null +++ b/test/feature/data/model/detailmeal/detail_meal_response_test.dart @@ -0,0 +1,83 @@ +import 'dart:convert'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:food_recipe/feature/data/model/detailmeal/detail_meal_response.dart'; + +import '../../../../fixture/fixture_reader.dart'; + +void main() { + final tDetailMealResponse = DetailMealResponse.fromJson( + json.decode( + fixture('detail_meal_response.json'), + ), + ); + + test( + 'pastikan nilai props adalah [idMeal, strMeal, strCategory, strArea, strInstructions, strMealThumb, strTags, ' + 'strYoutube, listIngredients, strSource]', + () async { + // assert + expect( + tDetailMealResponse.props, + [ + tDetailMealResponse.idMeal, + tDetailMealResponse.strMeal, + tDetailMealResponse.strCategory, + tDetailMealResponse.strArea, + tDetailMealResponse.strInstructions, + tDetailMealResponse.strMealThumb, + tDetailMealResponse.strTags, + tDetailMealResponse.strYoutube, + tDetailMealResponse.listIngredients, + tDetailMealResponse.strSource, + ], + ); + }, + ); + + test( + 'pastikan output dari fungsi toString adalah ' + 'DetailMealResponse{idMeal: testIdMeal, strMeal: testStrMeal, strCategory: testStrCategory, strArea: testStrArea, ' + 'strInstructions: testStrInstructions, strMealThumb: testStrMealThumb, strTags: testStrTags, ' + 'strYoutube: testStrYoutube, listIngredients: testListIngredients, strSource: testStrSource}', + () async { + // assert + expect( + tDetailMealResponse.toString(), + 'DetailMealResponse{idMeal: ${tDetailMealResponse.idMeal}, strMeal: ${tDetailMealResponse.strMeal}, ' + 'strCategory: ${tDetailMealResponse.strCategory}, strArea: ${tDetailMealResponse.strArea}, ' + 'strInstructions: ${tDetailMealResponse.strInstructions}, strMealThumb: ${tDetailMealResponse.strMealThumb}, ' + 'strTags: ${tDetailMealResponse.strTags}, strYoutube: ${tDetailMealResponse.strYoutube}, ' + 'listIngredients: ${tDetailMealResponse.listIngredients}, strSource: ${tDetailMealResponse.strSource}}', + ); + }, + ); + + test( + 'pastikan fungsi fromJson bisa mengembalikan objek class model DetailMealResponse', + () async { + // arrange + final jsonMap = json.decode(fixture('detail_meal_response.json')); + + // act + final actualModel = DetailMealResponse.fromJson(jsonMap); + + // assert + expect(actualModel, tDetailMealResponse); + }, + ); + + test( + 'pastikan fungsi toJson bisa mengembalikan objek Map', + () async { + // arrange + final model = DetailMealResponse.fromJson(json.decode(fixture('detail_meal_response.json'))); + + // act + final actualMap = json.encode(model.toJson()); + + // assert + expect(actualMap, json.encode(tDetailMealResponse.toJson())); + }, + ); +} diff --git a/test/feature/data/model/filterbycategory/filter_by_category_response_test.dart b/test/feature/data/model/filterbycategory/filter_by_category_response_test.dart new file mode 100644 index 0000000..ec0da4e --- /dev/null +++ b/test/feature/data/model/filterbycategory/filter_by_category_response_test.dart @@ -0,0 +1,66 @@ +import 'dart:convert'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:food_recipe/feature/data/model/filterbycategory/filter_by_category_response.dart'; + +import '../../../../fixture/fixture_reader.dart'; + +void main() { + final tFilterByCategoryResponse = FilterByCategoryResponse.fromJson( + json.decode( + fixture('filter_by_category_response.json'), + ), + ); + + test( + 'pastikan nilai props adalah [meals]', + () async { + // assert + expect(tFilterByCategoryResponse.props, [tFilterByCategoryResponse.meals]); + }, + ); + + test( + 'pastikan output dari fungsi toString adalah ' + 'FilterByCategoryResponse{meals: testMeals}', + () async { + // assert + expect( + tFilterByCategoryResponse.toString(), + 'FilterByCategoryResponse{meals: ${tFilterByCategoryResponse.meals}}', + ); + }, + ); + + test( + 'pastikan fungsi fromJson bisa mengembalikan objek class model FilterByCategoryResponse', + () async { + // arrange + final jsonMap = json.decode(fixture('filter_by_category_response.json')); + + // act + final actualModel = FilterByCategoryResponse.fromJson(jsonMap); + + // assert + expect(actualModel, tFilterByCategoryResponse); + }, + ); + + test( + 'pastikan fungsi toJson bisa mengembalikan objek Map', + () async { + // arrange + final model = FilterByCategoryResponse.fromJson( + json.decode( + fixture('filter_by_category_response.json'), + ), + ); + + // act + final map = json.encode(model.toJson()); + + // assert + expect(map, json.encode(tFilterByCategoryResponse.toJson())); + }, + ); +} diff --git a/test/feature/data/model/mealcategory/meal_category_response_test.dart b/test/feature/data/model/mealcategory/meal_category_response_test.dart new file mode 100644 index 0000000..debdba6 --- /dev/null +++ b/test/feature/data/model/mealcategory/meal_category_response_test.dart @@ -0,0 +1,63 @@ +import 'dart:convert'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:food_recipe/feature/data/model/mealcategory/meal_category_response.dart'; + +import '../../../../fixture/fixture_reader.dart'; + +void main() { + final tMealCategoryResponse = MealCategoryResponse.fromJson( + json.decode( + fixture('meal_category_response.json'), + ), + ); + + test( + 'pastikan nilai props adalah ' + '[categories]', + () async { + // assert + expect(tMealCategoryResponse.props, [tMealCategoryResponse.categories]); + }, + ); + + test( + 'pastikan output dari fungsi toString adalah ' + 'MealCategoryResponse{categories: testCategories}', + () async { + // assert + expect( + tMealCategoryResponse.toString(), + 'MealCategoryResponse{categories: ${tMealCategoryResponse.categories}}', + ); + }, + ); + + test( + 'pastikan fungsi fromJson bisa mengembalikan objek class model MealCategoryResponse', + () async { + // arrange + final jsonMap = json.decode(fixture('meal_category_response.json')); + + // act + final actualModel = MealCategoryResponse.fromJson(jsonMap); + + // assert + expect(actualModel, tMealCategoryResponse); + }, + ); + + test( + 'pastikan fungsi toJson bisa mengembalikan objek Map', + () async { + // arrange + final model = MealCategoryResponse.fromJson(json.decode(fixture('meal_category_response.json'))); + + // act + final actualMap = json.encode(model.toJson()); + + // assert + expect(actualMap, json.encode(tMealCategoryResponse.toJson())); + }, + ); +} diff --git a/test/feature/data/model/searchmealbyname/search_meal_by_name_response_test.dart b/test/feature/data/model/searchmealbyname/search_meal_by_name_response_test.dart new file mode 100644 index 0000000..1173104 --- /dev/null +++ b/test/feature/data/model/searchmealbyname/search_meal_by_name_response_test.dart @@ -0,0 +1,62 @@ +import 'dart:convert'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:food_recipe/feature/data/model/searchmealbyname/search_meal_by_name_response.dart'; + +import '../../../../fixture/fixture_reader.dart'; + +void main() { + final tSearchMealByNameResponse = SearchMealByNameResponse.fromJson( + json.decode( + fixture('search_meal_by_name_response.json'), + ), + ); + + test( + 'pastikan nilai props adalah [meals]', + () async { + // assert + expect(tSearchMealByNameResponse.props, [tSearchMealByNameResponse.meals]); + }, + ); + + test( + 'pastikan output dari fungsi toString adalah ' + 'SearchMealByNameResponse{meals: testMeals}', + () async { + // assert + expect( + tSearchMealByNameResponse.toString(), + 'SearchMealByNameResponse{meals: ${tSearchMealByNameResponse.meals}}', + ); + }, + ); + + test( + 'pastikan fungsi fromJson bisa mengembalikan objek class model SearchMealByNameResponse', + () async { + // arrange + final jsonMap = json.decode(fixture('search_meal_by_name_response.json')); + + // act + final actualModel = SearchMealByNameResponse.fromJson(jsonMap); + + // assert + expect(actualModel, tSearchMealByNameResponse); + }, + ); + + test( + 'pastikan fungsi toJson bisa mengembalikan objek Map', + () async { + // arrange + final model = SearchMealByNameResponse.fromJson(json.decode(fixture('search_meal_by_name_response.json'))); + + // act + final actualMap = json.encode(model.toJson()); + + // assert + expect(actualMap, json.encode(tSearchMealByNameResponse.toJson())); + }, + ); +} diff --git a/test/feature/data/repository/themealdb/themealdb_repository_impl_test.dart b/test/feature/data/repository/themealdb/themealdb_repository_impl_test.dart new file mode 100644 index 0000000..02ce6ef --- /dev/null +++ b/test/feature/data/repository/themealdb/themealdb_repository_impl_test.dart @@ -0,0 +1,262 @@ +import 'dart:convert'; + +import 'package:dartz/dartz.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:food_recipe/core/error/failure.dart'; +import 'package:food_recipe/core/network/network_info.dart'; +import 'package:food_recipe/feature/data/datasource/themealdb/themealdb_remote_data_source.dart'; +import 'package:food_recipe/feature/data/model/detailmeal/detail_meal_response.dart'; +import 'package:food_recipe/feature/data/model/filterbycategory/filter_by_category_response.dart'; +import 'package:food_recipe/feature/data/model/mealcategory/meal_category_response.dart'; +import 'package:food_recipe/feature/data/model/searchmealbyname/search_meal_by_name_response.dart'; +import 'package:food_recipe/feature/data/repository/themealdb/themealdb_repository_impl.dart'; +import 'package:mockito/mockito.dart'; + +import '../../../../fixture/fixture_reader.dart'; + +class MockTheMealDbRemoteDataSource extends Mock implements TheMealDbRemoteDataSource {} + +class MockNetworkInfo extends Mock implements NetworkInfo {} + +void main() { + TheMealDbRepositoryImpl theMealDbRepositoryImpl; + MockTheMealDbRemoteDataSource mockTheMealDbRemoteDataSource; + MockNetworkInfo mockNetworkInfo; + + setUp(() { + mockTheMealDbRemoteDataSource = MockTheMealDbRemoteDataSource(); + mockNetworkInfo = MockNetworkInfo(); + theMealDbRepositoryImpl = TheMealDbRepositoryImpl( + theMealDbRemoteDataSource: mockTheMealDbRemoteDataSource, + networkInfo: mockNetworkInfo, + ); + }); + + void setUpMockNetworkConnected() { + when(mockNetworkInfo.isConnected).thenAnswer((_) async => true); + } + + void setUpMockNetworkDisconnected() { + when(mockNetworkInfo.isConnected).thenAnswer((_) async => false); + } + + void testConnected(Function action) { + test( + 'pastikan device terhubung ke itnernet ketika memanggil endpoint', + () async { + // arrange + setUpMockNetworkConnected(); + + // act + await action.call(); + + // assert + verify(mockNetworkInfo.isConnected); + }, + ); + } + + void testDisconnected(Function action) { + test( + 'pastikan mengembalikan objek ConnectionFailure ketika device tidak terhubung ke internet', + () async { + // arrange + setUpMockNetworkDisconnected(); + + // act + final result = await action.call(); + + // assert + verify(mockNetworkInfo.isConnected); + expect(result, Left(ConnectionFailure())); + }, + ); + } + + group('getRandomMeal', () { + final tDetailMealResponse = DetailMealResponse.fromJson( + json.decode( + fixture('detail_meal_response.json'), + ), + ); + + testConnected(() => theMealDbRepositoryImpl.getRandomMeal()); + + test( + 'pastikan mengembalikan objek model DetailMealResponse ketika TheMealDbRemoteDataSource berhasil menerima ' + 'respon sukses dari endpoint', + () async { + // arrange + setUpMockNetworkConnected(); + when(mockTheMealDbRemoteDataSource.getRandomMeal()).thenAnswer((_) async => tDetailMealResponse); + + // act + final result = await theMealDbRepositoryImpl.getRandomMeal(); + + // assert + verify(mockTheMealDbRemoteDataSource.getRandomMeal()); + expect(result, Right(tDetailMealResponse)); + }, + ); + + test( + 'pastikan mengembalikan objek ServerFailure ketika TheMealDbRemoteDataSource menerima respon kegagalan dari ' + 'endpoint', + () async { + // arrange + setUpMockNetworkConnected(); + when(mockTheMealDbRemoteDataSource.getRandomMeal()).thenThrow(DioError(error: 'testError')); + + // act + final result = await theMealDbRepositoryImpl.getRandomMeal(); + + // assert + verify(mockTheMealDbRemoteDataSource.getRandomMeal()); + expect(result, Left(ServerFailure('testError'))); + }, + ); + + testDisconnected(() => theMealDbRepositoryImpl.getRandomMeal()); + }); + + group('getCategoryMeal', () { + final tMealCategoryResponse = MealCategoryResponse.fromJson( + json.decode( + fixture('meal_category_response.json'), + ), + ); + + testConnected(() => theMealDbRepositoryImpl.getCategoryMeal()); + + test( + 'pastikan mengembalikan objek model MealCategoryResponse ketika TheMealDbRemoteDataSource berhasil menerima ' + 'respon sukses dari endpoint', + () async { + // arrange + setUpMockNetworkConnected(); + when(mockTheMealDbRemoteDataSource.getCategoryMeal()).thenAnswer((_) async => tMealCategoryResponse); + + // act + final result = await theMealDbRepositoryImpl.getCategoryMeal(); + + // assert + verify(mockTheMealDbRemoteDataSource.getCategoryMeal()); + expect(result, Right(tMealCategoryResponse)); + }, + ); + + test( + 'pastikan mengembalikan objek ServerFailure ketika TheMealDbRemoteDataSource menerima respon kegagalan dari ' + 'endpoint', + () async { + // arrange + setUpMockNetworkConnected(); + when(mockTheMealDbRemoteDataSource.getCategoryMeal()).thenThrow(DioError(error: 'testError')); + + // act + final result = await theMealDbRepositoryImpl.getCategoryMeal(); + + // assert + verify(mockTheMealDbRemoteDataSource.getCategoryMeal()); + expect(result, Left(ServerFailure('testError'))); + }, + ); + + testDisconnected(() => theMealDbRepositoryImpl.getCategoryMeal()); + }); + + group('getFilterByCategory', () { + final tCategory = 'testCategory'; + final tFilterByCategoryResponse = FilterByCategoryResponse.fromJson( + json.decode( + fixture('filter_by_category_response.json'), + ), + ); + + testConnected(() => theMealDbRepositoryImpl.getFilterByCategory(tCategory)); + + test( + 'pastikan mengembalikan objek model FilterByCategoryResponse ketika TheMealDbRemoteDataSource berhasil menerima ' + 'respon sukses dari endpoint', + () async { + // arrange + setUpMockNetworkConnected(); + when(mockTheMealDbRemoteDataSource.getFilterByCategory(any)).thenAnswer((_) async => tFilterByCategoryResponse); + + // act + final result = await theMealDbRepositoryImpl.getFilterByCategory(tCategory); + + // assert + verify(mockTheMealDbRemoteDataSource.getFilterByCategory(tCategory)); + expect(result, Right(tFilterByCategoryResponse)); + }, + ); + + test( + 'pastikan mengembalikan objek ServerFailure ketika TheMealDbRemoteDataSource menerima respon kegagaalan dari ' + 'endpoint', + () async { + // arrange + setUpMockNetworkConnected(); + when(mockTheMealDbRemoteDataSource.getFilterByCategory(any)).thenThrow(DioError(error: 'testError')); + + // act + final result = await theMealDbRepositoryImpl.getFilterByCategory(tCategory); + + // assert + verify(mockTheMealDbRemoteDataSource.getFilterByCategory(tCategory)); + expect(result, Left(ServerFailure('testError'))); + }, + ); + + testDisconnected(() => theMealDbRepositoryImpl.getFilterByCategory(tCategory)); + }); + + group('searchMealByName', () { + final tName = 'testName'; + final tSearchMealByNameResponse = SearchMealByNameResponse.fromJson( + json.decode( + fixture('search_meal_by_name_response.json'), + ), + ); + + testConnected(() => theMealDbRepositoryImpl.searchMealByName(tName)); + + test( + 'pastikan mengembalikan objek model SearchMealByNameResponse ketika TheMealDbRemoteDataSource berhasil menerima ' + 'respon sukses dari endpoint', + () async { + // arrange + setUpMockNetworkConnected(); + when(mockTheMealDbRemoteDataSource.searchMealByName(any)).thenAnswer((_) async => tSearchMealByNameResponse); + + // act + final result = await theMealDbRepositoryImpl.searchMealByName(tName); + + // assert + verify(mockTheMealDbRemoteDataSource.searchMealByName(tName)); + expect(result, Right(tSearchMealByNameResponse)); + }, + ); + + test( + 'pastikan mengembalikan objek ServerFailure ketika TheMealDbRemoteDataSource menerima respon kegagalan dari ' + 'endpoint', + () async { + // arrange + setUpMockNetworkConnected(); + when(mockTheMealDbRemoteDataSource.searchMealByName(any)).thenThrow(DioError(error: 'testError')); + + // act + final result = await theMealDbRepositoryImpl.searchMealByName(tName); + + // assert + verify(mockTheMealDbRemoteDataSource.searchMealByName(tName)); + expect(result, Left(ServerFailure('testError'))); + }, + ); + + testDisconnected(() => theMealDbRepositoryImpl.searchMealByName(tName)); + }); +} diff --git a/test/feature/domain/usecase/getcategorymeal/get_category_meal_test.dart b/test/feature/domain/usecase/getcategorymeal/get_category_meal_test.dart new file mode 100644 index 0000000..3be6df7 --- /dev/null +++ b/test/feature/domain/usecase/getcategorymeal/get_category_meal_test.dart @@ -0,0 +1,44 @@ +import 'dart:convert'; + +import 'package:dartz/dartz.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:food_recipe/core/usecase/usecase.dart'; +import 'package:food_recipe/feature/data/model/mealcategory/meal_category_response.dart'; +import 'package:food_recipe/feature/domain/repository/themealdb/themealdb_repository.dart'; +import 'package:food_recipe/feature/domain/usecase/getcategorymeal/get_category_meal.dart'; +import 'package:mockito/mockito.dart'; + +import '../../../../fixture/fixture_reader.dart'; + +class MockTheMealDbRepository extends Mock implements TheMealDbRepository {} + +void main() { + GetCategoryMeal getCategoryMeal; + MockTheMealDbRepository mockTheMealDbRepository; + + setUp(() { + mockTheMealDbRepository = MockTheMealDbRepository(); + getCategoryMeal = GetCategoryMeal(theMealDbRepository: mockTheMealDbRepository); + }); + + test( + 'pastikan TheMealDbRepository berhasil menerima respon sukses dan gagal dari endpoint', + () async { + // arrange + final mealCategoryResponse = MealCategoryResponse.fromJson( + json.decode( + fixture('meal_category_response.json'), + ), + ); + when(mockTheMealDbRepository.getCategoryMeal()).thenAnswer((_) async => Right(mealCategoryResponse)); + + // act + final result = await getCategoryMeal(NoParams()); + + // assert + expect(result, Right(mealCategoryResponse)); + verify(mockTheMealDbRepository.getCategoryMeal()); + verifyNoMoreInteractions(mockTheMealDbRepository); + }, + ); +} \ No newline at end of file diff --git a/test/feature/domain/usecase/getfilterbycategory/get_filter_by_category_test.dart b/test/feature/domain/usecase/getfilterbycategory/get_filter_by_category_test.dart new file mode 100644 index 0000000..b015bba --- /dev/null +++ b/test/feature/domain/usecase/getfilterbycategory/get_filter_by_category_test.dart @@ -0,0 +1,66 @@ +import 'dart:convert'; + +import 'package:dartz/dartz.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:food_recipe/feature/data/model/filterbycategory/filter_by_category_response.dart'; +import 'package:food_recipe/feature/domain/repository/themealdb/themealdb_repository.dart'; +import 'package:food_recipe/feature/domain/usecase/getfilterbycategory/get_filter_by_category.dart'; +import 'package:mockito/mockito.dart'; + +import '../../../../fixture/fixture_reader.dart'; + +class MockTheMealDbRepository extends Mock implements TheMealDbRepository {} + +void main() { + GetFilterByCategory getFilterByCategory; + MockTheMealDbRepository mockTheMealDbRepository; + + setUp(() { + mockTheMealDbRepository = MockTheMealDbRepository(); + getFilterByCategory = GetFilterByCategory(theMealDbRepository: mockTheMealDbRepository); + }); + + final tCategory = 'testCategory'; + final tParamsGetFilterByCategory = ParamsGetFilterByCategory(category: tCategory); + + test( + 'pastikan TheMealDbRepository berhasil menerima respon sukses dan gagal dari endpoint getFilterByCategory', + () async { + // arrange + final tFilterByCategoryResponse = FilterByCategoryResponse.fromJson( + json.decode( + fixture('filter_by_category_response.json'), + ), + ); + when(mockTheMealDbRepository.getFilterByCategory(any)).thenAnswer((_) async => Right(tFilterByCategoryResponse)); + + // act + final result = await getFilterByCategory(tParamsGetFilterByCategory); + + // assert + expect(result, Right(tFilterByCategoryResponse)); + verify(mockTheMealDbRepository.getFilterByCategory(tCategory)); + verifyNoMoreInteractions(mockTheMealDbRepository); + }, + ); + + test( + 'pastikan nilai props ParamsGetFilterByCategory adalah [category]', + () async { + // assert + expect(tParamsGetFilterByCategory.props, [tParamsGetFilterByCategory.category]); + }, + ); + + test( + 'pastikan output dari fungsi toString ParamsGetFilterByCategory adalah ' + 'ParamsGetFilterByCategory{category: testCategory}', + () async { + // assert + expect( + tParamsGetFilterByCategory.toString(), + 'ParamsGetFilterByCategory{category: ${tParamsGetFilterByCategory.category}}', + ); + }, + ); +} diff --git a/test/feature/domain/usecase/getrandommeal/get_random_meal_test.dart b/test/feature/domain/usecase/getrandommeal/get_random_meal_test.dart new file mode 100644 index 0000000..2c68879 --- /dev/null +++ b/test/feature/domain/usecase/getrandommeal/get_random_meal_test.dart @@ -0,0 +1,40 @@ +import 'dart:convert'; + +import 'package:dartz/dartz.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:food_recipe/core/usecase/usecase.dart'; +import 'package:food_recipe/feature/data/model/detailmeal/detail_meal_response.dart'; +import 'package:food_recipe/feature/domain/repository/themealdb/themealdb_repository.dart'; +import 'package:food_recipe/feature/domain/usecase/getrandommeal/get_random_meal.dart'; +import 'package:mockito/mockito.dart'; + +import '../../../../fixture/fixture_reader.dart'; + +class MockTheMealDbRepository extends Mock implements TheMealDbRepository {} + +void main() { + GetRandomMeal getRandomMeal; + MockTheMealDbRepository mockTheMealDbRepository; + + setUp(() { + mockTheMealDbRepository = MockTheMealDbRepository(); + getRandomMeal = GetRandomMeal(theMealDbRepository: mockTheMealDbRepository); + }); + + test( + 'pastikan TheMealDbRepository berhasil menerima respon sukses dan gagal dari endpoint getRandomMeal', + () async { + // arrange + final tDetailMealResponse = DetailMealResponse.fromJson(json.decode(fixture('detail_meal_response.json'))); + when(mockTheMealDbRepository.getRandomMeal()).thenAnswer((_) async => Right(tDetailMealResponse)); + + // act + final result = await getRandomMeal(NoParams()); + + // assert + expect(result, Right(tDetailMealResponse)); + verify(mockTheMealDbRepository.getRandomMeal()); + verifyNoMoreInteractions(mockTheMealDbRepository); + }, + ); +} diff --git a/test/feature/domain/usecase/searchmealbyname/search_meal_by_name_test.dart b/test/feature/domain/usecase/searchmealbyname/search_meal_by_name_test.dart new file mode 100644 index 0000000..b3a69ed --- /dev/null +++ b/test/feature/domain/usecase/searchmealbyname/search_meal_by_name_test.dart @@ -0,0 +1,66 @@ +import 'dart:convert'; + +import 'package:dartz/dartz.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:food_recipe/feature/data/model/searchmealbyname/search_meal_by_name_response.dart'; +import 'package:food_recipe/feature/domain/repository/themealdb/themealdb_repository.dart'; +import 'package:food_recipe/feature/domain/usecase/searchmealbyname/search_meal_by_name.dart'; +import 'package:mockito/mockito.dart'; + +import '../../../../fixture/fixture_reader.dart'; + +class MockTheMealDbRepository extends Mock implements TheMealDbRepository {} + +void main() { + SearchMealByName searchMealByName; + MockTheMealDbRepository mockTheMealDbRepository; + + setUp(() { + mockTheMealDbRepository = MockTheMealDbRepository(); + searchMealByName = SearchMealByName(theMealDbRepository: mockTheMealDbRepository); + }); + + final tName = 'testName'; + final tParamsSearchMealByName = ParamsSearchMealByName(name: tName); + + test( + 'pastikan TheMealDbRepository berhasil menerima respon sukses dan gagal dari endpoint', + () async { + // arrange + final searchMealByNameResponse = SearchMealByNameResponse.fromJson( + json.decode( + fixture('search_meal_by_name_response.json'), + ), + ); + when(mockTheMealDbRepository.searchMealByName(any)).thenAnswer((_) async => Right(searchMealByNameResponse)); + + // act + final result = await searchMealByName(tParamsSearchMealByName); + + // assert + expect(result, Right(searchMealByNameResponse)); + verify(mockTheMealDbRepository.searchMealByName(tName)); + verifyNoMoreInteractions(mockTheMealDbRepository); + }, + ); + + test( + 'pastikan nilai props ParamsSearchMealByName adalah [name]', + () async { + // assert + expect(tParamsSearchMealByName.props, [tParamsSearchMealByName.name]); + }, + ); + + test( + 'pastikan output dari fungsi toString adalah ' + 'ParamsSearchMealByName{name: testName}', + () async { + // assert + expect( + tParamsSearchMealByName.toString(), + 'ParamsSearchMealByName{name: ${tParamsSearchMealByName.name}}', + ); + }, + ); +} diff --git a/test/feature/presentation/categorymeal/category_meal_bloc_test.dart b/test/feature/presentation/categorymeal/category_meal_bloc_test.dart new file mode 100644 index 0000000..7c8ed17 --- /dev/null +++ b/test/feature/presentation/categorymeal/category_meal_bloc_test.dart @@ -0,0 +1,258 @@ +import 'dart:convert'; + +import 'package:bloc_test/bloc_test.dart'; +import 'package:dartz/dartz.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:food_recipe/core/error/failure.dart'; +import 'package:food_recipe/core/usecase/usecase.dart'; +import 'package:food_recipe/core/util/constant_error_message.dart'; +import 'package:food_recipe/feature/data/model/filterbycategory/filter_by_category_response.dart'; +import 'package:food_recipe/feature/data/model/mealcategory/meal_category_response.dart'; +import 'package:food_recipe/feature/domain/usecase/getcategorymeal/get_category_meal.dart'; +import 'package:food_recipe/feature/domain/usecase/getfilterbycategory/get_filter_by_category.dart'; +import 'package:food_recipe/feature/presentation/bloc/categorymeal/bloc.dart'; +import 'package:mockito/mockito.dart'; + +import '../../../fixture/fixture_reader.dart'; + +class MockGetCategoryMeal extends Mock implements GetCategoryMeal {} + +class MockGetFilterByCategory extends Mock implements GetFilterByCategory {} + +void main() { + CategoryMealBloc categoryMealBloc; + MockGetCategoryMeal mockGetCategoryMeal; + MockGetFilterByCategory mockGetFilterByCategory; + + setUp(() { + mockGetCategoryMeal = MockGetCategoryMeal(); + mockGetFilterByCategory = MockGetFilterByCategory(); + categoryMealBloc = CategoryMealBloc( + getCategoryMeal: mockGetCategoryMeal, + getFilterByCategory: mockGetFilterByCategory, + ); + }); + + tearDown(() { + categoryMealBloc?.close(); + }); + + test( + 'pastikan initialState adalah InitialCategoryMealState', + () async { + // assert + expect(categoryMealBloc.initialState, InitialCategoryMealState()); + }, + ); + + test( + 'pastikan AssertionError akan terpanggil ketika menerima argumen null', + () async { + // assert + expect( + () => CategoryMealBloc( + getCategoryMeal: null, + getFilterByCategory: mockGetFilterByCategory, + ), + throwsAssertionError, + ); + expect( + () => CategoryMealBloc( + getCategoryMeal: mockGetCategoryMeal, + getFilterByCategory: null, + ), + throwsAssertionError, + ); + }, + ); + + test( + 'pastikan tidak ada state yang ter-emit ketika bloc sudah ter-close', + () async { + // act + await categoryMealBloc.close(); + + // assert + await expectLater(categoryMealBloc, emitsInOrder([InitialCategoryMealState(), emitsDone])); + }, + ); + + group('load category meal', () { + final tMealCategoryResponse = MealCategoryResponse.fromJson(json.decode(fixture('meal_category_response.json'))); + final tFilterByCategoryResponse = FilterByCategoryResponse.fromJson( + json.decode( + fixture('filter_by_category_response.json'), + ), + ); + final tNoParams = NoParams(); + final tParamsGetFilterByCategory = ParamsGetFilterByCategory(category: 'testStrCategory'); + + blocTest( + 'pastikan emit [LoadingCategoryMealState, LoadedDetailCategoryMealState] ketika terima event ' + 'LoadCategoryMealEvent dengan proses berhasil', + build: () async { + when(mockGetCategoryMeal(any)).thenAnswer((_) async => Right(tMealCategoryResponse)); + when(mockGetFilterByCategory(any)).thenAnswer((_) async => Right(tFilterByCategoryResponse)); + return categoryMealBloc; + }, + act: (bloc) { + return bloc.add(LoadCategoryMealEvent()); + }, + expect: [ + LoadingCategoryMealState(), + LoadedCategoryMealState(tMealCategoryResponse, tFilterByCategoryResponse), + ], + verify: (_) async { + verify(mockGetCategoryMeal(tNoParams)); + verify(mockGetFilterByCategory(tParamsGetFilterByCategory)); + }, + ); + + blocTest( + 'pastikan emit [LoadingCategoryMealState, FailureCategoryMealState] ketika terima event ' + 'LoadCategoryMealEvent dengan proses gagal dari endpoint getCategoryMeal', + build: () async { + when(mockGetCategoryMeal(any)).thenAnswer((_) async => Left(ServerFailure('testErrorMessage'))); + return categoryMealBloc; + }, + act: (bloc) { + return bloc.add(LoadCategoryMealEvent()); + }, + expect: [ + LoadingCategoryMealState(), + FailureCategoryMealState('testErrorMessage'), + ], + verify: (_) async { + verify(mockGetCategoryMeal(tNoParams)); + }, + ); + + blocTest( + 'pastikan emit [LoadingCategoryMealState, FailureCategoryMealState] ketika terima event ' + 'LoadCategoryMealEvent dengan kondisi internet tidak terhubung dari endpoint getCategoryMeal', + build: () async { + when(mockGetCategoryMeal(any)).thenAnswer((_) async => Left(ConnectionFailure())); + return categoryMealBloc; + }, + act: (bloc) { + return bloc.add(LoadCategoryMealEvent()); + }, + expect: [ + LoadingCategoryMealState(), + FailureCategoryMealState(ConstantErrorMessage().connectionError), + ], + verify: (_) async { + verify(mockGetCategoryMeal(tNoParams)); + }, + ); + + blocTest( + 'pastikan emit [LoadingCategoryMealState, FailureCategoryMealState] ketika terima event ' + 'LoadCategoryMealEvent dengan proses gagal dari endpoint getFilterByCategory', + build: () async { + when(mockGetCategoryMeal(any)).thenAnswer((_) async => Right(tMealCategoryResponse)); + when(mockGetFilterByCategory(any)).thenAnswer((_) async => Left(ServerFailure('testErrorMessage'))); + return categoryMealBloc; + }, + act: (bloc) { + return bloc.add(LoadCategoryMealEvent()); + }, + expect: [ + LoadingCategoryMealState(), + FailureCategoryMealState('testErrorMessage'), + ], + verify: (_) async { + verify(mockGetCategoryMeal(tNoParams)); + verify(mockGetFilterByCategory(tParamsGetFilterByCategory)); + }, + ); + + blocTest( + 'pastikan emit [LoadingCategoryMealState, FailureCategoryMealState] ketika terima event ' + 'LoadCategoryMealEvent dengan kondisi internet tidak terhubung dari endpoint getFilterByCategory', + build: () async { + when(mockGetCategoryMeal(any)).thenAnswer((_) async => Right(tMealCategoryResponse)); + when(mockGetFilterByCategory(any)).thenAnswer((_) async => Left(ConnectionFailure())); + return categoryMealBloc; + }, + act: (bloc) { + return bloc.add(LoadCategoryMealEvent()); + }, + expect: [ + LoadingCategoryMealState(), + FailureCategoryMealState(ConstantErrorMessage().connectionError), + ], + verify: (_) async { + verify(mockGetCategoryMeal(tNoParams)); + verify(mockGetFilterByCategory(tParamsGetFilterByCategory)); + }, + ); + }); + + group('load detail category meal', () { + final tFilterByCategoryResponse = FilterByCategoryResponse.fromJson( + json.decode( + fixture('filter_by_category_response.json'), + ), + ); + final tCategory = 'testStrCategory'; + final tParamsGetFilterByCategory = ParamsGetFilterByCategory(category: tCategory); + + blocTest( + 'pastikan emit [LoadingDetailCategoryMealState, LoadedDetailCategoryMealState] ketika terima event ' + 'LoadDetailCategoryMealEvent dengan proses berhasil', + build: () async { + when(mockGetFilterByCategory(any)).thenAnswer((_) async => Right(tFilterByCategoryResponse)); + return categoryMealBloc; + }, + act: (bloc) { + return bloc.add(LoadDetailCategoryMealEvent(tCategory)); + }, + expect: [ + LoadingDetailCategoryMealState(), + LoadedDetailCategoryMealState(tFilterByCategoryResponse), + ], + verify: (_) async { + verify(mockGetFilterByCategory(tParamsGetFilterByCategory)); + }, + ); + + blocTest( + 'pastikan emit [LoadingDetailCategoryMealState, FailureDetailCategoryMealState] ketika terima event ' + 'LoadDetailCategoryMealEvent dengan proses gagal dari API', + build: () async { + when(mockGetFilterByCategory(any)).thenAnswer((_) async => Left(ServerFailure('testErrorMessage'))); + return categoryMealBloc; + }, + act: (bloc) { + return bloc.add(LoadDetailCategoryMealEvent(tCategory)); + }, + expect: [ + LoadingDetailCategoryMealState(), + FailureDetailCategoryMealState('testErrorMessage'), + ], + verify: (_) async { + verify(mockGetFilterByCategory(tParamsGetFilterByCategory)); + }, + ); + + blocTest( + 'pastikan emit [LoadingDetailCategoryMealState, FailureDetailCategoryMealState] ketika terima event ' + 'LoadDetailCategoryMealEvent dengan kondisi internet tidak terhubung', + build: () async { + when(mockGetFilterByCategory(any)).thenAnswer((_) async => Left(ConnectionFailure())); + return categoryMealBloc; + }, + act: (bloc) { + return bloc.add(LoadDetailCategoryMealEvent(tCategory)); + }, + expect: [ + LoadingDetailCategoryMealState(), + FailureDetailCategoryMealState(ConstantErrorMessage().connectionError), + ], + verify: (_) async { + verify(mockGetFilterByCategory(tParamsGetFilterByCategory)); + }, + ); + }); +} diff --git a/test/feature/presentation/categorymeal/category_meal_event_test.dart b/test/feature/presentation/categorymeal/category_meal_event_test.dart new file mode 100644 index 0000000..b0747b6 --- /dev/null +++ b/test/feature/presentation/categorymeal/category_meal_event_test.dart @@ -0,0 +1,48 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:food_recipe/feature/presentation/bloc/categorymeal/bloc.dart'; + +void main() { + group('LoadCategoryMealEvent', () { + final tLoadCategoryMealEvent = LoadCategoryMealEvent(); + + test( + 'pastikan nilai props adalah []', + () async { + // assert + expect(tLoadCategoryMealEvent.props, []); + }, + ); + + test( + 'pastikan output dari fungsi toString adalah LoadCategoryMealEvent', + () async { + // assert + expect(tLoadCategoryMealEvent.toString(), 'LoadCategoryMealEvent'); + }, + ); + }); + + group('LoadDetailCategoryMealEvent', () { + final tCategory = 'testCategory'; + final tLoadDetailCategoryMealEvent = LoadDetailCategoryMealEvent(tCategory); + + test( + 'pastikan nilai props adalah [category]', + () async { + // assert + expect(tLoadDetailCategoryMealEvent.props, [tLoadDetailCategoryMealEvent.category]); + }, + ); + + test( + 'pastikan output dari fungsi toString adalah LoadDetailCategoryMealEvent{category: testCategory}', + () async { + // assert + expect( + tLoadDetailCategoryMealEvent.toString(), + 'LoadDetailCategoryMealEvent{category: ${tLoadDetailCategoryMealEvent.category}}', + ); + }, + ); + }); +} diff --git a/test/feature/presentation/categorymeal/category_meal_state_test.dart b/test/feature/presentation/categorymeal/category_meal_state_test.dart new file mode 100644 index 0000000..2e07654 --- /dev/null +++ b/test/feature/presentation/categorymeal/category_meal_state_test.dart @@ -0,0 +1,184 @@ +import 'dart:convert'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:food_recipe/feature/data/model/filterbycategory/filter_by_category_response.dart'; +import 'package:food_recipe/feature/data/model/mealcategory/meal_category_response.dart'; +import 'package:food_recipe/feature/presentation/bloc/categorymeal/bloc.dart'; + +import '../../../fixture/fixture_reader.dart'; + +void main() { + group('InitialCategoryMealState', () { + final tInitialCategoryMealState = InitialCategoryMealState(); + + test( + 'pastikan nilai props adalah []', + () async { + // assert + expect(tInitialCategoryMealState.props, []); + }, + ); + + test( + 'pastikan output dari fungsi toString adalah InitialCategoryMealState', + () async { + // assert + expect(tInitialCategoryMealState.toString(), 'InitialCategoryMealState'); + }, + ); + }); + + group('LoadingCategoryMealState', () { + final tLoadingCategoryMealState = LoadingCategoryMealState(); + + test( + 'pastikan nilai props adalah []', + () async { + // assert + expect(tLoadingCategoryMealState.props, []); + }, + ); + + test( + 'pastikan output dari fungsi toString adalah LoadingCategoryMealState', + () async { + // assert + expect(tLoadingCategoryMealState.toString(), 'LoadingCategoryMealState'); + }, + ); + }); + + group('LoadingDetailCategoryMealState', () { + final tLoadingDetailCategoryMealState = LoadingDetailCategoryMealState(); + + test( + 'pastikan nilai props adalah []', + () async { + // assert + expect(tLoadingDetailCategoryMealState.props, []); + }, + ); + + test( + 'pastikan output dari fungsi toString adalah LoadingDetailCategoryMealState', + () async { + // assert + expect(tLoadingDetailCategoryMealState.toString(), 'LoadingDetailCategoryMealState'); + }, + ); + }); + + group('FailureCategoryMealState', () { + final tFailureCategoryMealState = FailureCategoryMealState('testErrorMessage'); + + test( + 'pastikan nilai props adalah [errorMessage]', + () async { + // assert + expect(tFailureCategoryMealState.props, [tFailureCategoryMealState.errorMessage]); + }, + ); + + test( + 'pastikan output dari fungsi toString adalah ' + 'FailureCategoryMealState{errorMessage: testErrorMessage]', + () async { + // assert + expect( + tFailureCategoryMealState.toString(), + 'FailureCategoryMealState{errorMessage: ${tFailureCategoryMealState.errorMessage}}', + ); + }, + ); + }); + + group('FailureDetailCategoryMealState', () { + final tFailureDetailCategoryMealState = FailureDetailCategoryMealState('testErrorMessage'); + + test( + 'pastikan nilai props adalah [errorMessage]', + () async { + // assert + expect(tFailureDetailCategoryMealState.props, [tFailureDetailCategoryMealState.errorMessage]); + }, + ); + + test( + 'pastikan output dari fungsi toString adalah ' + 'FailureDetailCategoryMealState{errorMessage: ${tFailureDetailCategoryMealState.errorMessage}}', + () async { + // assert + expect( + tFailureDetailCategoryMealState.toString(), + 'FailureDetailCategoryMealState{errorMessage: ${tFailureDetailCategoryMealState.errorMessage}}', + ); + }, + ); + }); + + group('LoadedCategoryMealState', () { + final tMealCategoryResponse = MealCategoryResponse.fromJson(json.decode(fixture('meal_category_response.json'))); + final tFilterByCategoryResponse = FilterByCategoryResponse.fromJson( + json.decode( + fixture('filter_by_category_response.json'), + ), + ); + final tLoadedCategoryMealState = LoadedCategoryMealState( + tMealCategoryResponse, + tFilterByCategoryResponse, + ); + + test( + 'pastikan nilai props adalah [mealCategoryResponse, filterByCategoryResponse]', + () async { + // assert + expect( + tLoadedCategoryMealState.props, + [tLoadedCategoryMealState.mealCategoryResponse, tLoadedCategoryMealState.filterByCategoryResponse], + ); + }, + ); + + test( + 'pastikan output dari fungsi toString adalah ' + 'LoadedCategoryMealState{mealCategoryResponse: testMealCategoryResponse, filterByCategoryResponse: testFilterByCategoryResponse}', + () async { + // assert + expect( + tLoadedCategoryMealState.toString(), + 'LoadedCategoryMealState{mealCategoryResponse: ${tLoadedCategoryMealState.mealCategoryResponse}, ' + 'filterByCategoryResponse: ${tLoadedCategoryMealState.filterByCategoryResponse}}', + ); + }, + ); + }); + + group('LoadedDetailCategoryMealState', () { + final tFilterByCategoryResponse = FilterByCategoryResponse.fromJson( + json.decode( + fixture('filter_by_category_response.json'), + ), + ); + final tLoadedDetailCategoryMealState = LoadedDetailCategoryMealState(tFilterByCategoryResponse); + + test( + 'pastikan nilai props adalah [filterByCategoryResponse]', + () async { + // assert + expect(tLoadedDetailCategoryMealState.props, [tLoadedDetailCategoryMealState.filterByCategoryResponse]); + }, + ); + + test( + 'pastikan output dari fungsi toString adalah ' + 'LoadedDetailCategoryMealState{filterByCategoryResponse: testFilterByCategoryResponse}', + () async { + // assert + expect( + tLoadedDetailCategoryMealState.toString(), + 'LoadedDetailCategoryMealState{filterByCategoryResponse: ${tLoadedDetailCategoryMealState.filterByCategoryResponse}}', + ); + }, + ); + }); +} diff --git a/test/feature/presentation/randommeal/random_meal_bloc_test.dart b/test/feature/presentation/randommeal/random_meal_bloc_test.dart new file mode 100644 index 0000000..f62f6f1 --- /dev/null +++ b/test/feature/presentation/randommeal/random_meal_bloc_test.dart @@ -0,0 +1,125 @@ +import 'dart:convert'; + +import 'package:bloc_test/bloc_test.dart'; +import 'package:dartz/dartz.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:food_recipe/core/error/failure.dart'; +import 'package:food_recipe/core/usecase/usecase.dart'; +import 'package:food_recipe/core/util/constant_error_message.dart'; +import 'package:food_recipe/feature/data/model/detailmeal/detail_meal_response.dart'; +import 'package:food_recipe/feature/domain/usecase/getrandommeal/get_random_meal.dart'; +import 'package:food_recipe/feature/presentation/bloc/randommeal/random_meal_bloc.dart'; +import 'package:food_recipe/feature/presentation/bloc/randommeal/random_meal_event.dart'; +import 'package:food_recipe/feature/presentation/bloc/randommeal/random_meal_state.dart'; +import 'package:mockito/mockito.dart'; + +import '../../../fixture/fixture_reader.dart'; + +class MockGetRandomMeal extends Mock implements GetRandomMeal {} + +void main() { + RandomMealBloc randomMealBloc; + MockGetRandomMeal mockGetRandomMeal; + + setUp(() { + mockGetRandomMeal = MockGetRandomMeal(); + randomMealBloc = RandomMealBloc(getRandomMeal: mockGetRandomMeal); + }); + + tearDown(() { + randomMealBloc?.close(); + }); + + test( + 'pastikan AssertionError akan terpanggil ketika menerima argumen null', + () async { + // assert + expect(() => RandomMealBloc(getRandomMeal: null), throwsAssertionError); + }, + ); + + test( + 'pastikan initialState adalah InitialRandomMealState', + () async { + // assert + expect(randomMealBloc.initialState, InitialRandomMealState()); + }, + ); + + test( + 'pastikan tidak ada state yang ter-emit ketika bloc sudah ter-close', + () async { + // act + await randomMealBloc.close(); + + // assert + await expectLater(randomMealBloc, emitsInOrder([InitialRandomMealState(), emitsDone])); + }, + ); + + group('load random meal', () { + final tDetailMealResponse = DetailMealResponse.fromJson( + json.decode( + fixture('detail_meal_response.json'), + ), + ); + final tNoParams = NoParams(); + + blocTest( + 'pastikan emit [LoadingRandomMealState, LoadedRandomMealState] ketika terima event ' + 'LoadRandomMealEvent dengan proses berhasil', + build: () async { + when(mockGetRandomMeal(any)).thenAnswer((_) async => Right(tDetailMealResponse)); + return randomMealBloc; + }, + act: (bloc) { + return bloc.add(LoadRandomMealEvent()); + }, + expect: [ + LoadingRandomMealState(), + LoadedRandomMealState(tDetailMealResponse), + ], + verify: (_) async { + verify(mockGetRandomMeal(tNoParams)); + }, + ); + + blocTest( + 'pastikan emit [LoadingRandomMealState, FailureRandomMealState] ketika terima event ' + 'LoadRandomMealEvent dengan proses gagal dari API', + build: () async { + when(mockGetRandomMeal(any)).thenAnswer((_) async => Left(ServerFailure('testErrorMessage'))); + return randomMealBloc; + }, + act: (bloc) { + return bloc.add(LoadRandomMealEvent()); + }, + expect: [ + LoadingRandomMealState(), + FailureRandomMealState('testErrorMessage'), + ], + verify: (_) async { + verify(mockGetRandomMeal(tNoParams)); + }, + ); + + blocTest( + 'pastikan emit [LoadingRandomMealState, FailureRandomMealState] ketika terima event ' + 'LoadRandomMealEvent dengan kondisi internet tidak terhubung', + build: () async { + when(mockGetRandomMeal(any)).thenAnswer((_) async => Left(ConnectionFailure())); + return randomMealBloc; + }, + act: (bloc) { + return bloc.add(LoadRandomMealEvent()); + }, + expect: [ + LoadingRandomMealState(), + FailureRandomMealState(ConstantErrorMessage().connectionError), + ], + verify: (_) async { + verify(mockGetRandomMeal(tNoParams)); + }, + ); + }); +} diff --git a/test/feature/presentation/randommeal/random_meal_event_test.dart b/test/feature/presentation/randommeal/random_meal_event_test.dart new file mode 100644 index 0000000..7905912 --- /dev/null +++ b/test/feature/presentation/randommeal/random_meal_event_test.dart @@ -0,0 +1,24 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:food_recipe/feature/presentation/bloc/randommeal/random_meal_event.dart'; + +void main() { + group('LoadRandomMealEvent', () { + final tLoadRandomMealEvent = LoadRandomMealEvent(); + + test( + 'pastikan nilai props adalah []', + () async { + // assert + expect(tLoadRandomMealEvent.props, []); + }, + ); + + test( + 'pastikan output dari fungsi toString adalah LoadRandomMealEvent', + () async { + // assert + expect(tLoadRandomMealEvent.toString(), 'LoadRandomMealEvent'); + }, + ); + }); +} diff --git a/test/feature/presentation/randommeal/random_meal_state_test.dart b/test/feature/presentation/randommeal/random_meal_state_test.dart new file mode 100644 index 0000000..907d563 --- /dev/null +++ b/test/feature/presentation/randommeal/random_meal_state_test.dart @@ -0,0 +1,97 @@ +import 'dart:convert'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:food_recipe/feature/data/model/detailmeal/detail_meal_response.dart'; +import 'package:food_recipe/feature/presentation/bloc/randommeal/random_meal_state.dart'; + +import '../../../fixture/fixture_reader.dart'; + +void main() { + group('InitialRandomMealState', () { + final tInitialRandomMealState = InitialRandomMealState(); + + test( + 'pastikan nilai props adalah []', + () async { + // assert + expect(tInitialRandomMealState.props, []); + }, + ); + + test( + 'pastikan output dari fungsi toString adalah InitialRandomMealState', + () async { + // assert + expect(tInitialRandomMealState.toString(), 'InitialRandomMealState'); + }, + ); + }); + + group('LoadingRandomMealState', () { + final tLoadingRandomMealState = LoadingRandomMealState(); + + test( + 'pastikan nilai props adalah []', + () async { + // assert + expect(tLoadingRandomMealState.props, []); + }, + ); + + test( + 'pastikan output dari fungsi toString adalah LoadingRandomMealState', + () async { + // assert + expect(tLoadingRandomMealState.toString(), 'LoadingRandomMealState'); + }, + ); + }); + + group('FailureRandomMealState', () { + final tFailureRandomMealState = FailureRandomMealState('testErrorMessage'); + + test( + 'pastikan nilai props adalah [errorMessage]', + () async { + // assert + expect(tFailureRandomMealState.props, [tFailureRandomMealState.errorMessage]); + }, + ); + + test( + 'pastikan output dari fungsi toString adalah FailureRandomMealState{errorMessage: testErrorMessage}', + () async { + // assert + expect( + tFailureRandomMealState.toString(), + 'FailureRandomMealState{errorMessage: ${tFailureRandomMealState.errorMessage}}', + ); + }, + ); + }); + + group('LoadedRandomMealState', () { + final tDetailMealResponse = DetailMealResponse.fromJson(json.decode(fixture('detail_meal_response.json'))); + final tLoadedRandomMealState = LoadedRandomMealState(tDetailMealResponse); + + test( + 'pastikan nilai props adalah [detailMealResponse]', + () async { + // assert + expect(tLoadedRandomMealState.props, [tLoadedRandomMealState.detailMealResponse]); + }, + ); + + test( + 'pastikan output dari fungsi toString adalah ' + 'LoadedRandomMealState{detailMealResponse: testDetailMealResponse}', + () async { + // assert + expect( + tLoadedRandomMealState.toString(), + 'LoadedRandomMealState{detailMealResponse: ${tLoadedRandomMealState.detailMealResponse}}', + ); + }, + ); + }); +} diff --git a/test/feature/presentation/searchmeal/search_meal_bloc_test.dart b/test/feature/presentation/searchmeal/search_meal_bloc_test.dart new file mode 100644 index 0000000..9f5835a --- /dev/null +++ b/test/feature/presentation/searchmeal/search_meal_bloc_test.dart @@ -0,0 +1,123 @@ +import 'dart:convert'; + +import 'package:bloc_test/bloc_test.dart'; +import 'package:dartz/dartz.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:food_recipe/core/error/failure.dart'; +import 'package:food_recipe/core/util/constant_error_message.dart'; +import 'package:food_recipe/feature/data/model/searchmealbyname/search_meal_by_name_response.dart'; +import 'package:food_recipe/feature/domain/usecase/searchmealbyname/search_meal_by_name.dart'; +import 'package:food_recipe/feature/presentation/bloc/searchmeal/bloc.dart'; +import 'package:mockito/mockito.dart'; + +import '../../../fixture/fixture_reader.dart'; + +class MockSearchMealByName extends Mock implements SearchMealByName {} + +void main() { + SearchMealBloc searchMealBloc; + MockSearchMealByName mockSearchMealByName; + + setUp(() { + mockSearchMealByName = MockSearchMealByName(); + searchMealBloc = SearchMealBloc(searchMealByName: mockSearchMealByName); + }); + + tearDown(() { + searchMealBloc?.close(); + }); + + test( + 'pastikan AssertionError akan terpanggil ketika menerima argumen null', + () async { + // assert + expect(() => SearchMealBloc(searchMealByName: null), throwsAssertionError); + }, + ); + + test( + 'pastikan initialState adalah InitialSearchMealState', + () async { + // assert + expect(searchMealBloc.initialState, InitialSearchMealState()); + }, + ); + + test( + 'pastikan tidak ada state yang ter-emit ketika bloc sudah ter-close', + () async { + // act + await searchMealBloc.close(); + + // assert + await expectLater(searchMealBloc, emitsInOrder([InitialSearchMealState(), emitsDone])); + }, + ); + + group('load search meal', () { + final tSearchMealByNameResponse = SearchMealByNameResponse.fromJson( + json.decode( + fixture('search_meal_by_name_response.json'), + ), + ); + final tName = 'testName'; + final tParamsSearchMealByName = ParamsSearchMealByName(name: tName); + + blocTest( + 'pastikan emit [LoadingSearchMealState, LoadedSearchMealState] ketika terima event ' + 'LoadSearchMealEvent dengan proses berhasil', + build: () async { + when(mockSearchMealByName(any)).thenAnswer((_) async => Right(tSearchMealByNameResponse)); + return searchMealBloc; + }, + act: (bloc) { + return bloc.add(LoadSearchMealEvent(tName)); + }, + expect: [ + LoadingSearchMealState(), + LoadedSearchMealState(tSearchMealByNameResponse), + ], + verify: (_) async { + verify(mockSearchMealByName(tParamsSearchMealByName)); + }, + ); + + blocTest( + 'pastikan emit [LoadingSearchMealState, FailureSearchMealState] ketika terima event ' + 'LoadSearchMealEvent dengan proses gagal dari API', + build: () async { + when(mockSearchMealByName(any)).thenAnswer((_) async => Left(ServerFailure('testErrorMessage'))); + return searchMealBloc; + }, + act: (bloc) { + return bloc.add(LoadSearchMealEvent(tName)); + }, + expect: [ + LoadingSearchMealState(), + FailureSearchMealState('testErrorMessage'), + ], + verify: (_) async { + verify(mockSearchMealByName(tParamsSearchMealByName)); + }, + ); + + blocTest( + 'pastikan emit [LoadingSearchMealState, FailureSearchMealState] ketika terima event ' + 'LoadSearchMealEvent dengan kondisi internet tidak terhubung', + build: () async { + when(mockSearchMealByName(any)).thenAnswer((_) async => Left(ConnectionFailure())); + return searchMealBloc; + }, + act: (bloc) { + return bloc.add(LoadSearchMealEvent(tName)); + }, + expect: [ + LoadingSearchMealState(), + FailureSearchMealState(ConstantErrorMessage().connectionError), + ], + verify: (_) async { + verify(mockSearchMealByName(tParamsSearchMealByName)); + }, + ); + }); +} diff --git a/test/feature/presentation/searchmeal/search_meal_event_test.dart b/test/feature/presentation/searchmeal/search_meal_event_test.dart new file mode 100644 index 0000000..c266d68 --- /dev/null +++ b/test/feature/presentation/searchmeal/search_meal_event_test.dart @@ -0,0 +1,25 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:food_recipe/feature/presentation/bloc/searchmeal/bloc.dart'; + +void main() { + group('LoadSearchMealEvent', () { + final tLoadSearchMealEvent = LoadSearchMealEvent('testName'); + + test( + 'pastikan nilai props adalah [name]', + () async { + // assert + expect(tLoadSearchMealEvent.props, [tLoadSearchMealEvent.name]); + }, + ); + + test( + 'pastikan output dari fungsi toString adalah ' + 'LoadSearchMealEvent{name: testName}', + () async { + // assert + expect(tLoadSearchMealEvent.toString(), 'LoadSearchMealEvent{name: ${tLoadSearchMealEvent.name}}'); + }, + ); + }); +} diff --git a/test/feature/presentation/searchmeal/search_meal_state_test.dart b/test/feature/presentation/searchmeal/search_meal_state_test.dart new file mode 100644 index 0000000..a475501 --- /dev/null +++ b/test/feature/presentation/searchmeal/search_meal_state_test.dart @@ -0,0 +1,102 @@ +import 'dart:convert'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:food_recipe/feature/data/model/searchmealbyname/search_meal_by_name_response.dart'; +import 'package:food_recipe/feature/presentation/bloc/searchmeal/bloc.dart'; + +import '../../../fixture/fixture_reader.dart'; + +void main() { + group('InitialSearchMealState', () { + final tInitialSearchMealState = InitialSearchMealState(); + + test( + 'pastikan nilai props adalah []', + () async { + // assert + expect(tInitialSearchMealState.props, []); + }, + ); + + test( + 'pastikan output dari fungsi toString adalah InitialSearchMealState', + () async { + // assert + expect(tInitialSearchMealState.toString(), 'InitialSearchMealState'); + }, + ); + }); + + group('LoadingSearchMealState', () { + final tLoadingSearchMealState = LoadingSearchMealState(); + + test( + 'pastikan nilai props adalah []', + () async { + // assert + expect(tLoadingSearchMealState.props, []); + }, + ); + + test( + 'pastikan output dari fungsi toString adalah LoadingSearchMealState', + () async { + // assert + expect(tLoadingSearchMealState.toString(), 'LoadingSearchMealState'); + }, + ); + }); + + group('FailureSearchMealState', () { + final tFailureSearchMealState = FailureSearchMealState('testErrorMessage'); + + test( + 'pastikan nilai props adalah [errorMessage]', + () async { + // assert + expect(tFailureSearchMealState.props, [tFailureSearchMealState.errorMessage]); + }, + ); + + test( + 'pastikan output dari fungsi toString adalah ' + 'FailureSearchMealState{errorMessage: testErrorMessage}', + () async { + // assert + expect( + tFailureSearchMealState.toString(), + 'FailureSearchMealState{errorMessage: ${tFailureSearchMealState.errorMessage}}', + ); + }, + ); + }); + + group('LoadedSearchMealState', () { + final tSearchMealByNameResponse = SearchMealByNameResponse.fromJson( + json.decode( + fixture('search_meal_by_name_response.json'), + ), + ); + final tLoadedSearchMealState = LoadedSearchMealState(tSearchMealByNameResponse); + + test( + 'pastikan nilai props adalah [searchMealByNameResponse]', + () async { + // assert + expect(tLoadedSearchMealState.props, [tLoadedSearchMealState.searchMealByNameResponse]); + }, + ); + + test( + 'pastikan output dari fungsi toString adalah ' + 'LoadedSearchMealState{searchMealByNameResponse: ${tLoadedSearchMealState.searchMealByNameResponse}}', + () async { + // assert + expect( + tLoadedSearchMealState.toString(), + 'LoadedSearchMealState{searchMealByNameResponse: ${tLoadedSearchMealState.searchMealByNameResponse}}', + ); + }, + ); + }); +} diff --git a/test/fixture/api_detail_meal_response.json b/test/fixture/api_detail_meal_response.json new file mode 100644 index 0000000..a73cab0 --- /dev/null +++ b/test/fixture/api_detail_meal_response.json @@ -0,0 +1,57 @@ +{ + "meals": [ + { + "idMeal": "testIdMeal", + "strMeal": "testStrMeal", + "strDrinkAlternate": null, + "strCategory": "testStrCategory", + "strArea": "testStrArea", + "strInstructions": "testStrInstructions", + "strMealThumb": "testStrMealThumb", + "strTags": "testStrTags", + "strYoutube": "testStrYoutube", + "strIngredient1": "testListIngredients", + "strIngredient2": "", + "strIngredient3": "", + "strIngredient4": "", + "strIngredient5": "", + "strIngredient6": "", + "strIngredient7": "", + "strIngredient8": "", + "strIngredient9": "", + "strIngredient10": "", + "strIngredient11": "", + "strIngredient12": "", + "strIngredient13": "", + "strIngredient14": "", + "strIngredient15": "", + "strIngredient16": "", + "strIngredient17": "", + "strIngredient18": "", + "strIngredient19": "", + "strIngredient20": "", + "strMeasure1": "testMeasure", + "strMeasure2": "", + "strMeasure3": "", + "strMeasure4": "", + "strMeasure5": "", + "strMeasure6": "", + "strMeasure7": "", + "strMeasure8": "", + "strMeasure9": "", + "strMeasure10": "", + "strMeasure11": "", + "strMeasure12": "", + "strMeasure13": "", + "strMeasure14": "", + "strMeasure15": "", + "strMeasure16": "", + "strMeasure17": "", + "strMeasure18": "", + "strMeasure19": "", + "strMeasure20": "", + "strSource": "testStrSource", + "dateModified": null + } + ] +} \ No newline at end of file diff --git a/test/fixture/detail_meal_response.json b/test/fixture/detail_meal_response.json new file mode 100644 index 0000000..9e94bef --- /dev/null +++ b/test/fixture/detail_meal_response.json @@ -0,0 +1,14 @@ +{ + "idMeal": "testIdMeal", + "strMeal": "testStrMeal", + "strCategory": "testStrCategory", + "strArea": "testStrArea", + "strInstructions": "testStrInstructions", + "strMealThumb": "testStrMealThumb", + "strTags": "testStrTags", + "strYoutube": "testStrYoutube", + "listIngredients": [ + "testListIngredients testMeasure" + ], + "strSource": "testStrSource" +} \ No newline at end of file diff --git a/test/fixture/filter_by_category_response.json b/test/fixture/filter_by_category_response.json new file mode 100644 index 0000000..f115adf --- /dev/null +++ b/test/fixture/filter_by_category_response.json @@ -0,0 +1,9 @@ +{ + "meals": [ + { + "strMeal": "testStrMeal", + "strMealThumb": "testStrMealThumb", + "idMeal": "testIdMeal" + } + ] +} \ No newline at end of file diff --git a/test/fixture/fixture_reader.dart b/test/fixture/fixture_reader.dart new file mode 100644 index 0000000..1d42ef6 --- /dev/null +++ b/test/fixture/fixture_reader.dart @@ -0,0 +1,11 @@ +import 'dart:io'; + +String fixture(String name) { + var currentDirectory = Directory.current.toString().replaceAll('\'', ''); + var lastDirectory = currentDirectory.split('/')[currentDirectory.split('/').length - 1]; + if (lastDirectory == 'test') { + return File('fixture/$name').readAsStringSync(); + } else { + return File('test/fixture/$name').readAsStringSync(); + } +} \ No newline at end of file diff --git a/test/fixture/meal_category_response.json b/test/fixture/meal_category_response.json new file mode 100644 index 0000000..f813868 --- /dev/null +++ b/test/fixture/meal_category_response.json @@ -0,0 +1,9 @@ +{ + "categories": [ + { + "idCategory": "testIdCategory", + "strCategory": "testStrCategory", + "strCategoryThumb": "testStrCategoryThumb" + } + ] +} \ No newline at end of file diff --git a/test/fixture/search_meal_by_name_response.json b/test/fixture/search_meal_by_name_response.json new file mode 100644 index 0000000..21b4434 --- /dev/null +++ b/test/fixture/search_meal_by_name_response.json @@ -0,0 +1,9 @@ +{ + "meals": [ + { + "idMeal": "testIdMeal", + "strMeal": "testStrMeal", + "strMealThumb": "testStrMealThumb" + } + ] +} \ No newline at end of file