Menambahkan utensil page, dan utensil contract
This commit is contained in:
parent
2bdf328d20
commit
6c46c04008
|
@ -1,26 +1,4 @@
|
|||
A
|
||||
B
|
||||
C
|
||||
D
|
||||
E
|
||||
F
|
||||
G
|
||||
H
|
||||
I
|
||||
J
|
||||
K
|
||||
L
|
||||
M
|
||||
N
|
||||
O
|
||||
P
|
||||
Q
|
||||
R
|
||||
S
|
||||
T
|
||||
U
|
||||
V
|
||||
W
|
||||
X
|
||||
Y
|
||||
Z
|
||||
Telur
|
||||
Ayam
|
||||
Kentang
|
||||
Wortel
|
Binary file not shown.
|
@ -165,7 +165,9 @@ class CustomCameraWidgetState extends State<CustomCameraWidget> {
|
|||
ImagePicker();
|
||||
final XFile? image =
|
||||
await picker0.pickImage(
|
||||
source: ImageSource.gallery);
|
||||
source: ImageSource.gallery,
|
||||
|
||||
);
|
||||
if (image == null) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:snap_and_cook_mobile/styles/text_styles/tt_commons_text_styles.dart';
|
||||
|
||||
import '../../styles/colors.dart';
|
||||
|
||||
class UtensilItem extends StatelessWidget {
|
||||
final String name;
|
||||
final bool isSelected;
|
||||
const UtensilItem({super.key, required this.name, required this.isSelected});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 32,
|
||||
width: 108,
|
||||
margin: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(26),
|
||||
border: Border.all(
|
||||
color: isSelected ? Colors.white : AppColors.copper,
|
||||
width: 1.5
|
||||
),
|
||||
color: isSelected ? AppColors.copper : Colors.white
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
name,
|
||||
style: TTCommonsTextStyles.textMd
|
||||
.textRegular()
|
||||
.copyWith(color: isSelected ? Colors.white : AppColors.copper),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -26,7 +26,7 @@ class DatabaseHelper {
|
|||
await db.execute(
|
||||
'''CREATE TABLE ${DatabaseConstant.utensilsTable} (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT, is_selected INTEGER
|
||||
name TEXT, isSelected INTEGER
|
||||
)''',
|
||||
);
|
||||
},
|
||||
|
|
|
@ -35,4 +35,11 @@ class UtensilContract {
|
|||
await db.query(DatabaseConstant.utensilsTable);
|
||||
return results.map((res) => Utensil.fromJson(res)).toList();
|
||||
}
|
||||
|
||||
Future<List<String>> getSelectedUtensils() async {
|
||||
final Database db = await _databaseHelper.database;
|
||||
List<Map<String, dynamic>> results =
|
||||
await db.query(DatabaseConstant.utensilsTable,where: 'isSelected = 1');
|
||||
return results.map((res) => Utensil.fromJson(res).name ?? '').toList();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ part 'ingredient_model.g.dart';
|
|||
class Ingredient {
|
||||
String? name;
|
||||
double? quantity;
|
||||
@JsonKey(name: 'unit', includeIfNull: false)
|
||||
String? unit;
|
||||
|
||||
Ingredient({
|
||||
|
|
|
@ -12,9 +12,18 @@ Ingredient _$IngredientFromJson(Map<String, dynamic> json) => Ingredient(
|
|||
unit: json['unit'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$IngredientToJson(Ingredient instance) =>
|
||||
<String, dynamic>{
|
||||
Map<String, dynamic> _$IngredientToJson(Ingredient instance) {
|
||||
final val = <String, dynamic>{
|
||||
'name': instance.name,
|
||||
'quantity': instance.quantity,
|
||||
'unit': instance.unit,
|
||||
};
|
||||
|
||||
void writeNotNull(String key, dynamic value) {
|
||||
if (value != null) {
|
||||
val[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
writeNotNull('unit', instance.unit);
|
||||
return val;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'utensil_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
Utensil _$UtensilFromJson(Map<String, dynamic> json) => Utensil(
|
||||
id: json['id'] as int?,
|
||||
name: json['name'] as String?,
|
||||
isSelected: json['isSelected'] as int?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$UtensilToJson(Utensil instance) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'name': instance.name,
|
||||
'isSelected': instance.isSelected,
|
||||
};
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:snap_and_cook_mobile/data/remote/models/ingredient_model.dart';
|
||||
|
||||
part 'detect_ingredient_request.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class DetectIngredientRequest {
|
||||
List<Ingredient>? ingredients;
|
||||
List<String>? utensils;
|
||||
|
||||
DetectIngredientRequest({
|
||||
this.ingredients,
|
||||
this.utensils,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => _$DetectIngredientRequestToJson(this);
|
||||
|
||||
factory DetectIngredientRequest.fromJson(Map<String, dynamic> json) =>
|
||||
_$DetectIngredientRequestFromJson(json);
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'detect_ingredient_request.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
DetectIngredientRequest _$DetectIngredientRequestFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
DetectIngredientRequest(
|
||||
ingredients: (json['ingredients'] as List<dynamic>?)
|
||||
?.map((e) => Ingredient.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
utensils: (json['utensils'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$DetectIngredientRequestToJson(
|
||||
DetectIngredientRequest instance) =>
|
||||
<String, dynamic>{
|
||||
'ingredients': instance.ingredients,
|
||||
'utensils': instance.utensils,
|
||||
};
|
|
@ -1,20 +0,0 @@
|
|||
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'forgot_password_request.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class ForgotPasswordRequest {
|
||||
String phone;
|
||||
String key;
|
||||
|
||||
ForgotPasswordRequest({
|
||||
required this.phone,
|
||||
required this.key,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => _$ForgotPasswordRequestToJson(this);
|
||||
|
||||
factory ForgotPasswordRequest.fromJson(Map<String, dynamic> json) =>
|
||||
_$ForgotPasswordRequestFromJson(json);
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'forgot_password_request.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
ForgotPasswordRequest _$ForgotPasswordRequestFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
ForgotPasswordRequest(
|
||||
phone: json['phone'] as String,
|
||||
key: json['key'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ForgotPasswordRequestToJson(
|
||||
ForgotPasswordRequest instance) =>
|
||||
<String, dynamic>{
|
||||
'phone': instance.phone,
|
||||
'key': instance.key,
|
||||
};
|
|
@ -1,20 +0,0 @@
|
|||
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'login_request.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class LoginRequest {
|
||||
String phone;
|
||||
String password;
|
||||
|
||||
LoginRequest({
|
||||
required this.phone,
|
||||
required this.password,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => _$LoginRequestToJson(this);
|
||||
|
||||
factory LoginRequest.fromJson(Map<String, dynamic> json) =>
|
||||
_$LoginRequestFromJson(json);
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'login_request.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
LoginRequest _$LoginRequestFromJson(Map<String, dynamic> json) => LoginRequest(
|
||||
phone: json['phone'] as String,
|
||||
password: json['password'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$LoginRequestToJson(LoginRequest instance) =>
|
||||
<String, dynamic>{
|
||||
'phone': instance.phone,
|
||||
'password': instance.password,
|
||||
};
|
|
@ -1,16 +0,0 @@
|
|||
import 'package:json_annotation/json_annotation.dart';
|
||||
part 'phone_number_check_request.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class PhoneNumberCheckRequest {
|
||||
String phone;
|
||||
|
||||
PhoneNumberCheckRequest({
|
||||
required this.phone,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => _$PhoneNumberCheckRequestToJson(this);
|
||||
|
||||
factory PhoneNumberCheckRequest.fromJson(Map<String, dynamic> json) =>
|
||||
_$PhoneNumberCheckRequestFromJson(json);
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'phone_number_check_request.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
PhoneNumberCheckRequest _$PhoneNumberCheckRequestFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
PhoneNumberCheckRequest(
|
||||
phone: json['phone'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$PhoneNumberCheckRequestToJson(
|
||||
PhoneNumberCheckRequest instance) =>
|
||||
<String, dynamic>{
|
||||
'phone': instance.phone,
|
||||
};
|
|
@ -1,23 +0,0 @@
|
|||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'register_request.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class RegisterRequest {
|
||||
String name;
|
||||
String phone;
|
||||
String email;
|
||||
String password;
|
||||
|
||||
RegisterRequest({
|
||||
required this.name,
|
||||
required this.phone,
|
||||
required this.email,
|
||||
required this.password,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => _$RegisterRequestToJson(this);
|
||||
|
||||
factory RegisterRequest.fromJson(Map<String, dynamic> json) =>
|
||||
_$RegisterRequestFromJson(json);
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'register_request.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
RegisterRequest _$RegisterRequestFromJson(Map<String, dynamic> json) =>
|
||||
RegisterRequest(
|
||||
name: json['name'] as String,
|
||||
phone: json['phone'] as String,
|
||||
email: json['email'] as String,
|
||||
password: json['password'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$RegisterRequestToJson(RegisterRequest instance) =>
|
||||
<String, dynamic>{
|
||||
'name': instance.name,
|
||||
'phone': instance.phone,
|
||||
'email': instance.email,
|
||||
'password': instance.password,
|
||||
};
|
|
@ -1,24 +0,0 @@
|
|||
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'reset_password_request.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class ResetPasswordRequest {
|
||||
String code;
|
||||
String phone;
|
||||
String password;
|
||||
String passwordConfirmation;
|
||||
|
||||
ResetPasswordRequest({
|
||||
required this.code,
|
||||
required this.phone,
|
||||
required this.password,
|
||||
required this.passwordConfirmation,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => _$ResetPasswordRequestToJson(this);
|
||||
|
||||
factory ResetPasswordRequest.fromJson(Map<String, dynamic> json) =>
|
||||
_$ResetPasswordRequestFromJson(json);
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'reset_password_request.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
ResetPasswordRequest _$ResetPasswordRequestFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
ResetPasswordRequest(
|
||||
code: json['code'] as String,
|
||||
phone: json['phone'] as String,
|
||||
password: json['password'] as String,
|
||||
passwordConfirmation: json['passwordConfirmation'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ResetPasswordRequestToJson(
|
||||
ResetPasswordRequest instance) =>
|
||||
<String, dynamic>{
|
||||
'code': instance.code,
|
||||
'phone': instance.phone,
|
||||
'password': instance.password,
|
||||
'passwordConfirmation': instance.passwordConfirmation,
|
||||
};
|
|
@ -1,19 +0,0 @@
|
|||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'verify_otp_request.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class VerifyOtpRequest {
|
||||
String phone;
|
||||
String otpCode;
|
||||
|
||||
VerifyOtpRequest({
|
||||
required this.phone,
|
||||
required this.otpCode,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => _$VerifyOtpRequestToJson(this);
|
||||
|
||||
factory VerifyOtpRequest.fromJson(Map<String, dynamic> json) =>
|
||||
_$VerifyOtpRequestFromJson(json);
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'verify_otp_request.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
VerifyOtpRequest _$VerifyOtpRequestFromJson(Map<String, dynamic> json) =>
|
||||
VerifyOtpRequest(
|
||||
phone: json['phone'] as String,
|
||||
otpCode: json['otpCode'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$VerifyOtpRequestToJson(VerifyOtpRequest instance) =>
|
||||
<String, dynamic>{
|
||||
'phone': instance.phone,
|
||||
'otpCode': instance.otpCode,
|
||||
};
|
|
@ -4,6 +4,7 @@ import 'package:snap_and_cook_mobile/data/remote/models/recipe_model.dart';
|
|||
import 'package:snap_and_cook_mobile/data/remote/responses/base_response.dart';
|
||||
|
||||
import '../../../resources/services/recipe_service_constant.dart';
|
||||
import '../requests/detect_ingredient_request.dart';
|
||||
|
||||
part 'recipe_service.g.dart';
|
||||
|
||||
|
@ -13,9 +14,18 @@ abstract class RecipeServices {
|
|||
|
||||
@GET(RecipeServiceConstants.listRecipe)
|
||||
Future<BaseResponse<List<RecipeModel>>> getAllRecipes(
|
||||
@CancelRequest() CancelToken cancelToken);
|
||||
@CancelRequest() CancelToken cancelToken,
|
||||
@Query("page[size]") int size,
|
||||
@Query("page[current]") int currentPage,
|
||||
@Query("filter[search]") String? search,
|
||||
);
|
||||
|
||||
@GET(RecipeServiceConstants.detailRecipe)
|
||||
Future<BaseResponse<RecipeModel>> getDetailRecipe(
|
||||
@CancelRequest() CancelToken cancelToken, @Path("uuid") String uuid);
|
||||
|
||||
@POST(RecipeServiceConstants.recipeRecommendation)
|
||||
Future<BaseResponse<List<RecipeModel>>> getRecipeRecommendation(
|
||||
@CancelRequest() CancelToken cancelToken,
|
||||
@Body() DetectIngredientRequest request);
|
||||
}
|
||||
|
|
|
@ -19,9 +19,19 @@ class _RecipeServices implements RecipeServices {
|
|||
String? baseUrl;
|
||||
|
||||
@override
|
||||
Future<BaseResponse<List<RecipeModel>>> getAllRecipes(cancelToken) async {
|
||||
Future<BaseResponse<List<RecipeModel>>> getAllRecipes(
|
||||
cancelToken,
|
||||
size,
|
||||
currentPage,
|
||||
search,
|
||||
) async {
|
||||
const _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{
|
||||
r'page[size]': size,
|
||||
r'page[current]': currentPage,
|
||||
r'filter[search]': search,
|
||||
};
|
||||
queryParameters.removeWhere((k, v) => v == null);
|
||||
final _headers = <String, dynamic>{};
|
||||
final _data = <String, dynamic>{};
|
||||
final _result = await _dio.fetch<Map<String, dynamic>>(
|
||||
|
@ -78,6 +88,40 @@ class _RecipeServices implements RecipeServices {
|
|||
return value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<BaseResponse<List<RecipeModel>>> getRecipeRecommendation(
|
||||
cancelToken,
|
||||
request,
|
||||
) async {
|
||||
const _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{};
|
||||
final _headers = <String, dynamic>{};
|
||||
final _data = <String, dynamic>{};
|
||||
_data.addAll(request.toJson());
|
||||
final _result = await _dio.fetch<Map<String, dynamic>>(
|
||||
_setStreamType<BaseResponse<List<RecipeModel>>>(Options(
|
||||
method: 'POST',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'recipe/recommendation',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
cancelToken: cancelToken,
|
||||
)
|
||||
.copyWith(baseUrl: baseUrl ?? _dio.options.baseUrl)));
|
||||
final value = BaseResponse<List<RecipeModel>>.fromJson(
|
||||
_result.data!,
|
||||
(json) => (json as List<dynamic>)
|
||||
.map<RecipeModel>(
|
||||
(i) => RecipeModel.fromJson(i as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
return value;
|
||||
}
|
||||
|
||||
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
|
||||
if (T != dynamic &&
|
||||
!(requestOptions.responseType == ResponseType.bytes ||
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
import 'package:dartz/dartz.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../../../data/remote/models/ingredient_model.dart';
|
||||
import '../../entities/recipe.dart';
|
||||
|
||||
abstract class RecipeInterface {
|
||||
Future<Either<DioError, List<Recipe>>> fetchRecipes(CancelToken cancelToken);
|
||||
Future<Either<DioError, List<Recipe>>> fetchRecipes(CancelToken cancelToken, {
|
||||
required int size,
|
||||
required int currentPage,
|
||||
String? search,
|
||||
});
|
||||
|
||||
Future<Either<DioError, List<Recipe>>> fetchRecipeRecommendations(
|
||||
CancelToken cancelToken,
|
||||
List<Ingredient> ingredients,
|
||||
List<String> utensils);
|
||||
|
||||
Future<Either<DioError, Recipe>> fetchDetailRecipe(
|
||||
CancelToken cancelToken, String uuid);
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import 'package:dartz/dartz.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:snap_and_cook_mobile/data/remote/models/ingredient_model.dart';
|
||||
import 'package:snap_and_cook_mobile/data/remote/requests/detect_ingredient_request.dart';
|
||||
|
||||
import '../../../data/remote/services/recipe_service.dart';
|
||||
import '../../entities/recipe.dart';
|
||||
|
@ -11,9 +13,13 @@ class RecipeUseCase implements RecipeInterface {
|
|||
|
||||
@override
|
||||
Future<Either<DioError, List<Recipe>>> fetchRecipes(
|
||||
CancelToken cancelToken) async {
|
||||
CancelToken cancelToken, {
|
||||
required int size,
|
||||
required int currentPage,
|
||||
String? search,
|
||||
}) async {
|
||||
try {
|
||||
final response = await service.getAllRecipes(cancelToken);
|
||||
final response = await service.getAllRecipes(cancelToken, size, currentPage, search);
|
||||
List<Recipe> recipes = [];
|
||||
response.data?.forEach((element) {
|
||||
recipes.add(element.toEntity());
|
||||
|
@ -36,4 +42,28 @@ class RecipeUseCase implements RecipeInterface {
|
|||
return Left(DioError(requestOptions: RequestOptions(path: "")));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<DioError, List<Recipe>>> fetchRecipeRecommendations(
|
||||
CancelToken cancelToken,
|
||||
List<Ingredient> ingredients,
|
||||
List<String> utensils) async {
|
||||
try {
|
||||
final response = await service.getRecipeRecommendation(
|
||||
cancelToken,
|
||||
DetectIngredientRequest(
|
||||
ingredients: ingredients, utensils: utensils));
|
||||
List<Recipe> recipes = [];
|
||||
response.data?.forEach((element) {
|
||||
recipes.add(element.toEntity());
|
||||
});
|
||||
return Right(recipes);
|
||||
} on DioError catch (e) {
|
||||
print("DioError is ${e}");
|
||||
return Left(e);
|
||||
} catch (e) {
|
||||
print("Error is ${e}");
|
||||
return Left(DioError(requestOptions: RequestOptions(path: "")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
import 'package:snap_and_cook_mobile/data/remote/models/utensil_model.dart';
|
||||
|
||||
abstract class UtensilInterface {
|
||||
Future<List<Utensil>> fetchUtensils();
|
||||
|
||||
Future<List<String>> fetchSelectedUtensils();
|
||||
|
||||
|
||||
Future<void> updateUtensil(Utensil utensil);
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import '../../../data/remote/models/utensil_model.dart';
|
||||
|
||||
List<Utensil> utensilResource = [
|
||||
Utensil(
|
||||
id: 1,
|
||||
name: "Wajan",
|
||||
isSelected: 0
|
||||
),
|
||||
Utensil(
|
||||
id: 2,
|
||||
name: "Pisau",
|
||||
isSelected: 0
|
||||
),
|
||||
Utensil(
|
||||
id: 3,
|
||||
name: "Spatula",
|
||||
isSelected: 0
|
||||
),
|
||||
Utensil(
|
||||
id: 4,
|
||||
name: "Kompor",
|
||||
isSelected: 0
|
||||
),
|
||||
Utensil(
|
||||
id: 5,
|
||||
name: "Mangkuk",
|
||||
isSelected: 0
|
||||
),
|
||||
Utensil(
|
||||
id: 6,
|
||||
name: "Pemanggang",
|
||||
isSelected: 0
|
||||
),
|
||||
];
|
|
@ -0,0 +1,32 @@
|
|||
import 'package:snap_and_cook_mobile/data/local/utensils_contract.dart';
|
||||
import 'package:snap_and_cook_mobile/domain/use_case/utensils/utensil_resource.dart';
|
||||
|
||||
import '../../../data/remote/models/utensil_model.dart';
|
||||
import 'utensil_interface.dart';
|
||||
|
||||
class UtensilUseCase implements UtensilInterface {
|
||||
final _dbContract = UtensilContract();
|
||||
|
||||
@override
|
||||
Future<List<Utensil>> fetchUtensils() async {
|
||||
List<Utensil> utensils = await _dbContract.getUtensils();
|
||||
if (utensils.isEmpty){
|
||||
_dbContract.insertAllUtensil(utensilResource);
|
||||
return utensilResource;
|
||||
}
|
||||
|
||||
return utensils;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateUtensil(Utensil utensil) async {
|
||||
await _dbContract.updateUtensil(utensil);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<String>> fetchSelectedUtensils() async {
|
||||
List<String> utensils = await _dbContract.getSelectedUtensils();
|
||||
return utensils;
|
||||
|
||||
}
|
||||
}
|
|
@ -6,7 +6,9 @@ import 'package:snap_and_cook_mobile/utils/extension/dio_extension.dart';
|
|||
import 'package:snap_and_cook_mobile/utils/interceptor/platform_header_interceptor.dart';
|
||||
|
||||
import 'components/app/app.dart';
|
||||
import 'configuration/app_build_config.dart';
|
||||
import 'configuration/app_environtment.dart';
|
||||
import 'data/enums/environment_enum.dart';
|
||||
import 'utils/session/session.dart';
|
||||
|
||||
Future<void> init() async {
|
||||
|
@ -16,7 +18,13 @@ Future<void> init() async {
|
|||
await Get.putAsync<Dio>(
|
||||
() async => Dio()
|
||||
.baseUrl(AppEnvironment.apiUrl)
|
||||
.addInterceptor(PlatformHeaderInterceptor()),
|
||||
.addInterceptor(PlatformHeaderInterceptor())
|
||||
.modify((dio) {
|
||||
if (AppBuildConfig.instance.config == BuildConfigEnum.staging) {
|
||||
dio.usePrettyLogger();
|
||||
}
|
||||
return dio;
|
||||
}),
|
||||
);
|
||||
|
||||
await Get.putAsync(() async => Session());
|
||||
|
|
|
@ -2,7 +2,6 @@ import 'configuration/app_build_config.dart';
|
|||
import 'data/enums/environment_enum.dart';
|
||||
import 'init.dart';
|
||||
|
||||
|
||||
void main() async{
|
||||
AppBuildConfig.instantiate(config: BuildConfigEnum.staging);
|
||||
await init();
|
||||
|
|
|
@ -56,7 +56,7 @@ class HomeView extends BaseView<HomeViewModel> {
|
|||
),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
onPressed: controller.navigateToUtensilPage,
|
||||
icon: const Icon(
|
||||
Icons.settings,
|
||||
color: Colors.white,
|
||||
|
@ -76,12 +76,6 @@ class HomeView extends BaseView<HomeViewModel> {
|
|||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12),
|
||||
child: Text("Cari berdasarkan 1 bahan",
|
||||
style: TTCommonsTextStyles.textLg.textMedium()),
|
||||
),
|
||||
_ingredientsWidget(),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
|
@ -89,11 +83,9 @@ class HomeView extends BaseView<HomeViewModel> {
|
|||
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Text("Rekomendasi",
|
||||
Text("Rekomendasi Resep :",
|
||||
style: TTCommonsTextStyles.textLg.textMedium()),
|
||||
const Spacer(),
|
||||
Text("Lihat Semua",
|
||||
style: TTCommonsTextStyles.textSm.textRegular()),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -8,15 +8,17 @@ import '../../../routes/routes/main_route.dart';
|
|||
import '../../base/base_view_model.dart';
|
||||
|
||||
class HomeViewModel extends BaseViewModel {
|
||||
String version = "Version 0.0.1-dev";
|
||||
|
||||
TextEditingController searchController = TextEditingController();
|
||||
final RecipeUseCase _recipeUseCase = RecipeUseCase();
|
||||
|
||||
final RxList<Recipe> recipes = RxList<Recipe>();
|
||||
|
||||
void onSearchSubmitted(String value) {
|
||||
print(value);
|
||||
if (value.isEmpty) {
|
||||
return;
|
||||
}
|
||||
Get.toNamed(MainRoute.searchResult,
|
||||
arguments: {ArgumentConstants.search: value});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -27,9 +29,9 @@ class HomeViewModel extends BaseViewModel {
|
|||
|
||||
Future<void> _fetchAllRecipes() async {
|
||||
showLoadingContainer();
|
||||
var data = await _recipeUseCase.fetchRecipes(cancelToken);
|
||||
data.fold((l){
|
||||
}, (result){
|
||||
var data = await _recipeUseCase.fetchRecipes(cancelToken,
|
||||
size: 20, currentPage: 1);
|
||||
data.fold((l) {}, (result) {
|
||||
hideLoadingContainer();
|
||||
recipes.clear();
|
||||
recipes.addAll(result);
|
||||
|
@ -46,6 +48,10 @@ class HomeViewModel extends BaseViewModel {
|
|||
Get.toNamed(MainRoute.detection);
|
||||
}
|
||||
|
||||
void navigateToUtensilPage() {
|
||||
Get.toNamed(MainRoute.utensil);
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:snap_and_cook_mobile/components/recipe/utensils.dart';
|
||||
|
||||
class UtensilsListWidget extends StatelessWidget {
|
||||
final List<String> utensils;
|
||||
|
||||
const UtensilsListWidget({super.key, required this.utensils});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 50,
|
||||
child: ListView.builder(
|
||||
itemBuilder: (context, index) {
|
||||
return UtensilItem(name: utensils[index], isSelected: false,);
|
||||
},
|
||||
itemCount: utensils.length,
|
||||
scrollDirection: Axis.horizontal,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ import '../../base/base_view.dart';
|
|||
import '../components/food_prep_widget.dart';
|
||||
import '../components/recipe_detail_divider_widget.dart';
|
||||
import '../components/step_list_widget.dart';
|
||||
import '../components/utensils_list_widget.dart';
|
||||
import '../view_model/recipe_detail_view_model.dart';
|
||||
|
||||
class RecipeDetailView extends BaseView<RecipeDetailViewModel> {
|
||||
|
@ -80,6 +81,7 @@ class RecipeDetailView extends BaseView<RecipeDetailViewModel> {
|
|||
const RecipeDetailDividerWidget(
|
||||
title: 'Alat Memasak',
|
||||
),
|
||||
UtensilsListWidget(utensils: controller.recipe.value?.utensils ?? [],),
|
||||
const RecipeDetailDividerWidget(
|
||||
title: 'Langkah-langkah',
|
||||
),
|
||||
|
|
|
@ -40,13 +40,12 @@ class RecipeDetectionViewModel extends BaseViewModel {
|
|||
Future<void> _loadMachineLearningModel() async {
|
||||
showLoadingContainer();
|
||||
await _vision.loadYoloModel(
|
||||
labels: 'assets/labels.txt',
|
||||
// labels: 'assets/labels.txt',
|
||||
modelPath: 'assets/yolov8m_float16.tflite',
|
||||
// labels: 'assets/labels_alpha.txt',
|
||||
// modelPath: 'assets/wicaratanganV2.tflite',
|
||||
labels: 'assets/labels.txt',
|
||||
modelVersion: "yolov8",
|
||||
quantization: false,
|
||||
numThreads: 2,
|
||||
numThreads: 3,
|
||||
useGpu: true,
|
||||
);
|
||||
hideLoadingContainer();
|
||||
|
@ -87,10 +86,12 @@ class RecipeDetectionViewModel extends BaseViewModel {
|
|||
bytesList: byte,
|
||||
imageHeight: image.height,
|
||||
imageWidth: image.width,
|
||||
iouThreshold: 0.8,
|
||||
iouThreshold: 0.2,
|
||||
confThreshold: 0.2,
|
||||
classThreshold: 0.3,
|
||||
classThreshold: 0.2,
|
||||
);
|
||||
|
||||
print("DATA IS ${result.length}");
|
||||
if (result.isNotEmpty) {
|
||||
modelResults.value = result;
|
||||
imageBytes.value = await drawOnImage(modelResults);
|
||||
|
@ -118,6 +119,27 @@ class RecipeDetectionViewModel extends BaseViewModel {
|
|||
}
|
||||
}
|
||||
|
||||
final translationDict = {
|
||||
'carrot': 'Wortel',
|
||||
};
|
||||
|
||||
Ingredient translateIngredient(Ingredient ingredient, Map<String, String> translationDict) {
|
||||
final translatedName = translationDict[ingredient.name] ?? ingredient.name;
|
||||
return Ingredient(
|
||||
name: translatedName,
|
||||
quantity: ingredient.quantity,
|
||||
unit: ingredient.unit,
|
||||
);
|
||||
}
|
||||
|
||||
List<Ingredient> translateIngredients(List<Ingredient> ingredients, Map<String, String> translationDict) {
|
||||
return ingredients.map((ingredient) => translateIngredient(ingredient, translationDict)).toList();
|
||||
}
|
||||
|
||||
// List<String> translateIngredients(List<Ingredient> originalList) {
|
||||
// return originalList.map((Ingredient item) => item.name?.replaceAll('carrot', 'wortel')).toList();
|
||||
// }
|
||||
|
||||
void incrementIngredientQuantity(int index) {
|
||||
detectedIngredients[index].quantity =
|
||||
(detectedIngredients[index].quantity ?? 0) + 1;
|
||||
|
@ -157,7 +179,9 @@ class RecipeDetectionViewModel extends BaseViewModel {
|
|||
imageWidth: imageWidth.value,
|
||||
);
|
||||
|
||||
detectedIngredients.value = detectedObject;
|
||||
detectedIngredients.value = translateIngredients(detectedObject, translationDict);
|
||||
|
||||
// detectedIngredients.value = detectedObject;
|
||||
|
||||
final picture = recorder.endRecording();
|
||||
final imgWithBoxes = await picture.toImage(img.width, img.height);
|
||||
|
|
|
@ -19,7 +19,7 @@ class DetectedIngredientItem extends StatelessWidget {
|
|||
color: AppColors.copper),
|
||||
),
|
||||
child: Text(
|
||||
'${ingredient.name} ${ingredient.quantity} ${ingredient.unit}',
|
||||
'${ingredient.name} ${ingredient.quantity}',
|
||||
style: TTCommonsTextStyles.textMd.textMedium().copyWith(
|
||||
color: AppColors.copper,
|
||||
),
|
||||
|
|
|
@ -10,20 +10,19 @@ class RecipeResultList extends GetView<RecipeDetectionResultViewModel> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView.builder(
|
||||
return Obx(
|
||||
() => ListView.builder(
|
||||
itemBuilder: (context, index) {
|
||||
Recipe recipe = controller.recipes[index];
|
||||
return RecipeFullItem(
|
||||
recipe: Recipe(
|
||||
title: 'Masakan',
|
||||
cookTime: 0,
|
||||
image:
|
||||
'https://img.freepik.com/free-photo/tasty-burger-isolated-white-background-fresh-hamburger-fastfood-with-beef-cheese_90220-1063.jpg',
|
||||
),
|
||||
recipe: recipe,
|
||||
onTap: controller.navigateToRecipeDetail,
|
||||
);
|
||||
},
|
||||
itemCount: 4,
|
||||
itemCount: controller.recipes.length,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics());
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,54 @@
|
|||
import 'package:get/get.dart';
|
||||
import 'package:snap_and_cook_mobile/domain/use_case/general/recipe_use_case.dart';
|
||||
|
||||
import '../../../data/remote/models/ingredient_model.dart';
|
||||
import '../../../domain/entities/recipe.dart';
|
||||
import '../../../domain/use_case/utensils/utensil_use_case.dart';
|
||||
import '../../../resources/arguments/argument_constants.dart';
|
||||
import '../../../routes/routes/main_route.dart';
|
||||
import '../../base/base_view_model.dart';
|
||||
|
||||
class RecipeDetectionResultViewModel extends BaseViewModel {
|
||||
final _utensilUseCase = UtensilUseCase();
|
||||
final _recipeUseCase = RecipeUseCase();
|
||||
|
||||
final _argument = Get.arguments as Map<String, dynamic>;
|
||||
|
||||
final List<Ingredient> ingredients = [];
|
||||
|
||||
final RxList<String> selectedUtensil = RxList();
|
||||
final RxList<Recipe> recipes = RxList<Recipe>();
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
ingredients.addAll(_argument[ArgumentConstants.ingredients] as List<Ingredient>);
|
||||
ingredients
|
||||
.addAll(_argument[ArgumentConstants.ingredients] as List<Ingredient>);
|
||||
|
||||
_fetchRecipeRecommendation();
|
||||
}
|
||||
|
||||
Future<void> _fetchSelectedUtensil() async {
|
||||
selectedUtensil.value = await _utensilUseCase.fetchSelectedUtensils();
|
||||
}
|
||||
|
||||
Future<void> _fetchRecipeRecommendation() async {
|
||||
showLoadingContainer();
|
||||
await _fetchSelectedUtensil();
|
||||
var data = await _recipeUseCase.fetchRecipeRecommendations(
|
||||
cancelToken, ingredients, selectedUtensil);
|
||||
data.fold((l) {
|
||||
hideLoadingContainer();
|
||||
}, (result) {
|
||||
hideLoadingContainer();
|
||||
recipes.clear();
|
||||
recipes.addAll(result);
|
||||
});
|
||||
}
|
||||
|
||||
void navigateToRecipeDetail(String uuid) {
|
||||
Get.toNamed(MainRoute.detail, arguments: {
|
||||
ArgumentConstants.recipeUuid: uuid
|
||||
});
|
||||
Get.toNamed(MainRoute.detail,
|
||||
arguments: {ArgumentConstants.recipeUuid: uuid});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -29,5 +58,4 @@ class RecipeDetectionResultViewModel extends BaseViewModel {
|
|||
|
||||
@override
|
||||
void onClose() {}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import 'package:get/get.dart';
|
||||
|
||||
import '../view_model/recipe_search_result_view_model.dart';
|
||||
|
||||
class RecipeSearchResultBinding extends Bindings {
|
||||
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<RecipeSearchResultViewModel>(
|
||||
() => RecipeSearchResultViewModel(),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../components/recipe/recipe_full_item.dart';
|
||||
import '../../../domain/entities/recipe.dart';
|
||||
import '../view_model/recipe_search_result_view_model.dart';
|
||||
|
||||
class RecipeResultList extends GetView<RecipeSearchResultViewModel> {
|
||||
const RecipeResultList({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Obx(
|
||||
() => ListView.builder(
|
||||
itemBuilder: (context, index) {
|
||||
Recipe recipe = controller.recipes[index];
|
||||
return RecipeFullItem(
|
||||
recipe: recipe,
|
||||
onTap: controller.navigateToRecipeDetail,
|
||||
);
|
||||
},
|
||||
itemCount: controller.recipes.length,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:snap_and_cook_mobile/styles/text_styles/tt_commons_text_styles.dart';
|
||||
|
||||
import '../../../components/appbar/basic_appbar.dart';
|
||||
import '../../../components/form/search_text_field.dart';
|
||||
import '../../../styles/colors.dart';
|
||||
import '../../base/base_view.dart';
|
||||
import '../components/recipe_result_list.dart';
|
||||
import '../view_model/recipe_search_result_view_model.dart';
|
||||
|
||||
class RecipeSearchResultView
|
||||
extends BaseView<RecipeSearchResultViewModel> {
|
||||
const RecipeSearchResultView({super.key});
|
||||
|
||||
@override
|
||||
PreferredSizeWidget? appBar(BuildContext context) {
|
||||
return BasicAppBar(
|
||||
appBarTitleText: "",
|
||||
centerTitle: false,
|
||||
leadingIconData: Icons.arrow_back,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_recipeResultSearch(),
|
||||
const SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: Text('Hasil Pencarian:',
|
||||
style: TTCommonsTextStyles.textLg.textMedium()),
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: RecipeResultList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _recipeResultSearch(){
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
color: AppColors.primary,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
SearchTextField(
|
||||
controller: controller.searchController,
|
||||
hintText: 'Cari Resep..',
|
||||
inputType: TextInputType.text,
|
||||
isOptional: true,
|
||||
onSubmitted: controller.onSearchSubmitted,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:snap_and_cook_mobile/domain/use_case/general/recipe_use_case.dart';
|
||||
|
||||
import '../../../data/remote/models/ingredient_model.dart';
|
||||
import '../../../domain/entities/recipe.dart';
|
||||
import '../../../domain/use_case/utensils/utensil_use_case.dart';
|
||||
import '../../../resources/arguments/argument_constants.dart';
|
||||
import '../../../routes/routes/main_route.dart';
|
||||
import '../../base/base_view_model.dart';
|
||||
|
||||
class RecipeSearchResultViewModel extends BaseViewModel {
|
||||
final _recipeUseCase = RecipeUseCase();
|
||||
TextEditingController searchController = TextEditingController();
|
||||
|
||||
final _argument = Get.arguments as Map<String, dynamic>;
|
||||
|
||||
String search = "";
|
||||
|
||||
final RxList<String> selectedUtensil = RxList();
|
||||
final RxList<Recipe> recipes = RxList<Recipe>();
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
search = _argument[ArgumentConstants.search] as String;
|
||||
searchController.text = search;
|
||||
_fetchRecipes();
|
||||
}
|
||||
|
||||
Future<void> _fetchRecipes() async {
|
||||
showLoadingContainer();
|
||||
var data = await _recipeUseCase.fetchRecipes(cancelToken, size: 20, currentPage: 1, search: search);
|
||||
data.fold((l) {
|
||||
hideLoadingContainer();
|
||||
}, (result) {
|
||||
hideLoadingContainer();
|
||||
recipes.clear();
|
||||
recipes.addAll(result);
|
||||
});
|
||||
}
|
||||
|
||||
void onSearchSubmitted(String value) {
|
||||
search = value;
|
||||
_fetchRecipes();
|
||||
}
|
||||
|
||||
void navigateToRecipeDetail(String uuid) {
|
||||
Get.toNamed(MainRoute.detail,
|
||||
arguments: {ArgumentConstants.recipeUuid: uuid});
|
||||
}
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
super.onReady();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:snap_and_cook_mobile/styles/text_styles/tt_commons_text_styles.dart';
|
||||
import '../../../components/asset_image_view.dart';
|
||||
import '../../../styles/values.dart';
|
||||
import '../../base/base_view.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get/get_state_manager/get_state_manager.dart';
|
||||
import 'package:snap_and_cook_mobile/components/recipe/utensils.dart';
|
||||
|
||||
import '../../../components/appbar/basic_appbar.dart';
|
||||
import '../../../styles/images.dart';
|
||||
import '../../base/base_view.dart';
|
||||
import '../view_model/utensil_view_model.dart';
|
||||
|
||||
class UtensilView extends BaseView<UtensilViewModel> {
|
||||
|
@ -12,26 +12,39 @@ class UtensilView extends BaseView<UtensilViewModel> {
|
|||
|
||||
@override
|
||||
PreferredSizeWidget? appBar(BuildContext context) {
|
||||
return BasicAppBar(appBarTitleText: "", centerTitle: false);
|
||||
return BasicAppBar(
|
||||
appBarTitleText: "Alat-Alat Memasak",
|
||||
onTapBack: () => Get.back(),
|
||||
leadingIconData: Icons.arrow_back,
|
||||
centerTitle: false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return Column(
|
||||
return Obx(
|
||||
() => Wrap(
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: double.infinity,
|
||||
),
|
||||
const Expanded(
|
||||
child: AssetImageView(
|
||||
fileName: AppImages.logoFull,
|
||||
width: AppValues.logoWidth,
|
||||
height: AppValues.logoHeight,
|
||||
)),
|
||||
Text(controller.version, style: TTCommonsTextStyles.textLg.textMedium()),
|
||||
const SizedBox(
|
||||
height: 32,
|
||||
for (int i = 0; i < controller.utensils.length; i++)
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
controller.onSelectUtensil(controller.utensils[i], i);
|
||||
},
|
||||
child: UtensilItem(
|
||||
name: controller.utensils[i].name ?? '',
|
||||
isSelected: controller.utensils[i].isSelected == 1),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _utensilsWidget() {
|
||||
return Wrap(
|
||||
children: [
|
||||
for (int i = 0; i < controller.utensils.length; i++)
|
||||
UtensilItem(
|
||||
name: controller.utensils[i].name ?? '',
|
||||
isSelected: controller.utensils[i].isSelected == 1)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,22 +1,34 @@
|
|||
import 'package:get/get.dart';
|
||||
import 'package:snap_and_cook_mobile/domain/use_case/utensils/utensil_use_case.dart';
|
||||
|
||||
import '../../../routes/routes/main_route.dart';
|
||||
import '../../../data/remote/models/utensil_model.dart';
|
||||
import '../../base/base_view_model.dart';
|
||||
|
||||
class UtensilViewModel extends BaseViewModel {
|
||||
String version = "Version 0.0.1-dev";
|
||||
final _useCase = UtensilUseCase();
|
||||
|
||||
RxList<Utensil> utensils = RxList();
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
super.onReady();
|
||||
_startSplash();
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
_fetchUtensils();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {}
|
||||
Future<void> _fetchUtensils() async {
|
||||
showLoadingContainer();
|
||||
utensils.value = await _useCase.fetchUtensils();
|
||||
hideLoadingContainer();
|
||||
}
|
||||
|
||||
Future<void> _startSplash() async {
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
Get.offNamed(MainRoute.home);
|
||||
void onSelectUtensil(Utensil utensil, int index){
|
||||
if (utensil.isSelected == 0){
|
||||
utensil.isSelected = 1;
|
||||
} else{
|
||||
utensil.isSelected = 0;
|
||||
}
|
||||
utensils[index] = utensil;
|
||||
_useCase.updateUtensil(utensil);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,5 +2,6 @@ class ArgumentConstants {
|
|||
static const String receivedFile = "received_file_args";
|
||||
static const String ingredients = "ingredients_args";
|
||||
static const String recipeUuid = "recipe_uuid_args";
|
||||
static const String search = "search_args";
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,10 @@ import 'package:snap_and_cook_mobile/presentation/home/binding/home_binding.dart
|
|||
import 'package:snap_and_cook_mobile/presentation/home/view/home_view.dart';
|
||||
import 'package:snap_and_cook_mobile/presentation/recipe_detection/binding/recipe_detection_binding.dart';
|
||||
import 'package:snap_and_cook_mobile/presentation/recipe_detection/view/recipe_detection_view.dart';
|
||||
import 'package:snap_and_cook_mobile/presentation/recipe_search_result/binding/recipe_search_result_binding.dart';
|
||||
import 'package:snap_and_cook_mobile/presentation/recipe_search_result/view/recipe_search_result_view.dart';
|
||||
import 'package:snap_and_cook_mobile/presentation/utensils/binding/utensil_binding.dart';
|
||||
import 'package:snap_and_cook_mobile/presentation/utensils/view/utensil_view.dart';
|
||||
|
||||
import '../../presentation/recipe_detail/binding/recipe_detail_binding.dart';
|
||||
import '../../presentation/recipe_detail/view/recipe_detail_view.dart';
|
||||
|
@ -18,6 +22,8 @@ class MainRoute {
|
|||
static const detection = "/recipe-detection-page";
|
||||
static const detectionResult = "/recipe-detection-result-page";
|
||||
static const detail = "/recipe-detail-page";
|
||||
static const utensil = "/utensil-page";
|
||||
static const searchResult = "/recipe-search-result-page";
|
||||
|
||||
static final routes = [
|
||||
GetPage(
|
||||
|
@ -45,5 +51,15 @@ class MainRoute {
|
|||
page: () => const RecipeDetailView(),
|
||||
binding: RecipeDetailBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: utensil,
|
||||
page: () => const UtensilView(),
|
||||
binding: UtensilBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: searchResult,
|
||||
page: () => const RecipeSearchResultView(),
|
||||
binding: RecipeSearchResultBinding(),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue