Menambahkan utensil page, dan utensil contract
This commit is contained in:
parent
279a03be5d
commit
2bdf328d20
|
@ -6,19 +6,32 @@ import '../../domain/entities/recipe.dart';
|
|||
|
||||
class RecipeItem extends StatelessWidget {
|
||||
final Recipe recipe;
|
||||
|
||||
const RecipeItem({super.key, required this.recipe});
|
||||
final Function(String) onTap;
|
||||
const RecipeItem({super.key, required this.recipe, required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
print("IMAGE IS ${recipe.image}");
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
onTap(recipe.uuid ?? '');
|
||||
},
|
||||
child: Card(
|
||||
elevation: 2,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
BasicNetworkImage(
|
||||
imageUrl: recipe.image ?? 'https://img.freepik.com/free-photo/tasty-burger-isolated-white-background-fresh-hamburger-fastfood-with-beef-cheese_90220-1063.jpg',
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(4),
|
||||
topRight: Radius.circular(4),
|
||||
),
|
||||
child: BasicNetworkImage(
|
||||
imageUrl: recipe.image ?? '',
|
||||
height: 160,
|
||||
width: double.infinity,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
|
@ -43,6 +56,8 @@ class RecipeItem extends StatelessWidget {
|
|||
|
||||
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'app_build_config.dart';
|
|||
|
||||
class AppEnvironment {
|
||||
static load() async {
|
||||
|
||||
if (AppBuildConfig.instance.config == BuildConfigEnum.production) {
|
||||
await dotenv.load(fileName: "production/.env");
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
import 'package:path/path.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
import '../../resources/constants/database_constant.dart';
|
||||
|
||||
class DatabaseHelper {
|
||||
static DatabaseHelper? _databaseHelper;
|
||||
static late Database _database;
|
||||
|
||||
DatabaseHelper._internal() {
|
||||
_databaseHelper = this;
|
||||
}
|
||||
|
||||
factory DatabaseHelper() => _databaseHelper ?? DatabaseHelper._internal();
|
||||
|
||||
Future<Database> get database async {
|
||||
_database = await _initializeDb();
|
||||
return _database;
|
||||
}
|
||||
|
||||
Future<Database> _initializeDb() async {
|
||||
var path = await getDatabasesPath();
|
||||
var db = openDatabase(
|
||||
join(path, DatabaseConstant.dbName),
|
||||
onCreate: (db, version) async {
|
||||
await db.execute(
|
||||
'''CREATE TABLE ${DatabaseConstant.utensilsTable} (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT, is_selected INTEGER
|
||||
)''',
|
||||
);
|
||||
},
|
||||
version: 1,
|
||||
);
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import 'package:snap_and_cook_mobile/data/remote/models/utensil_model.dart';
|
||||
import 'package:snap_and_cook_mobile/utils/extension/database_extension.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
import '../../resources/constants/database_constant.dart';
|
||||
import 'database_helper.dart';
|
||||
|
||||
class UtensilContract {
|
||||
final DatabaseHelper _databaseHelper = DatabaseHelper();
|
||||
|
||||
Future<void> insertUtensil(Utensil utensil) async {
|
||||
final Database db = await _databaseHelper.database;
|
||||
await db.insert(DatabaseConstant.utensilsTable, utensil.toJson());
|
||||
print('Data saved!');
|
||||
}
|
||||
|
||||
Future<void> insertAllUtensil(List<Utensil> utensil) async {
|
||||
final Database db = await _databaseHelper.database;
|
||||
await db.insertMultiple(
|
||||
DatabaseConstant.utensilsTable, utensil.map((e) => e.toJson()),
|
||||
conflictAlgorithm: ConflictAlgorithm.ignore);
|
||||
print('All Data saved!');
|
||||
}
|
||||
|
||||
Future<void> updateUtensil(Utensil utensil) async {
|
||||
final Database db = await _databaseHelper.database;
|
||||
await db.update(DatabaseConstant.utensilsTable, utensil.toJson(),
|
||||
where: 'id = ?', whereArgs: [utensil.id]);
|
||||
print('Data changed!');
|
||||
}
|
||||
|
||||
Future<List<Utensil>> getUtensils() async {
|
||||
final Database db = await _databaseHelper.database;
|
||||
List<Map<String, dynamic>> results =
|
||||
await db.query(DatabaseConstant.utensilsTable);
|
||||
return results.map((res) => Utensil.fromJson(res)).toList();
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ part 'ingredient_model.g.dart';
|
|||
@JsonSerializable()
|
||||
class Ingredient {
|
||||
String? name;
|
||||
int? quantity;
|
||||
double? quantity;
|
||||
String? unit;
|
||||
|
||||
Ingredient({
|
||||
|
|
|
@ -8,7 +8,7 @@ part of 'ingredient_model.dart';
|
|||
|
||||
Ingredient _$IngredientFromJson(Map<String, dynamic> json) => Ingredient(
|
||||
name: json['name'] as String?,
|
||||
quantity: json['quantity'] as int?,
|
||||
quantity: (json['quantity'] as num?)?.toDouble(),
|
||||
unit: json['unit'] as String?,
|
||||
);
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ class RecipeModel {
|
|||
uuid: uuid,
|
||||
title: title,
|
||||
description: description,
|
||||
image: AppEnvironment.apiUrl + (image ?? ''),
|
||||
image: AppEnvironment.imageUrl + (image ?? ''),
|
||||
ingredients: ingredients,
|
||||
instructions: instructions,
|
||||
prepTime: prepTime,
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'utensil_model.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class Utensil {
|
||||
int? id;
|
||||
String? name;
|
||||
int? isSelected;
|
||||
|
||||
Utensil({
|
||||
this.id,
|
||||
this.name,
|
||||
this.isSelected,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => _$UtensilToJson(this);
|
||||
|
||||
factory Utensil.fromJson(Map<String, dynamic> json) =>
|
||||
_$UtensilFromJson(json);
|
||||
}
|
|
@ -21,8 +21,6 @@ class RecipeUseCase implements RecipeInterface {
|
|||
return Right(recipes);
|
||||
} on DioError catch (e) {
|
||||
return Left(e);
|
||||
} catch (e) {
|
||||
return Left(DioError(requestOptions: RequestOptions(path: "")));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,19 +3,20 @@ import 'package:flutter/material.dart';
|
|||
import 'package:get/get.dart';
|
||||
import 'package:snap_and_cook_mobile/data/remote/services/recipe_service.dart';
|
||||
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_environtment.dart';
|
||||
import 'resources/constants/session_constants.dart';
|
||||
import 'utils/interceptor/authorization_header_interceptor.dart';
|
||||
import 'utils/session/session.dart';
|
||||
|
||||
Future<void> init() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await AppEnvironment.load();
|
||||
|
||||
await Get.putAsync<Dio>(
|
||||
() async => Dio().baseUrl(AppEnvironment.apiUrl).addInterceptor(
|
||||
AuthorizationHeaderInterceptor(
|
||||
onToken: () async =>
|
||||
await Session.get(SessionConstants.token) ?? "")),
|
||||
() async => Dio()
|
||||
.baseUrl(AppEnvironment.apiUrl)
|
||||
.addInterceptor(PlatformHeaderInterceptor()),
|
||||
);
|
||||
|
||||
await Get.putAsync(() async => Session());
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
|
|||
import 'package:get/get.dart';
|
||||
|
||||
import '../../components/dismissable_keyboard.dart';
|
||||
import '../../components/progress_container.dart';
|
||||
import '../../styles/colors.dart';
|
||||
import 'base_view_model.dart';
|
||||
|
||||
|
@ -62,9 +63,24 @@ abstract class BaseView<T extends BaseViewModel> extends GetView<T>{
|
|||
|
||||
Widget pageContent(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: Stack(
|
||||
children: [
|
||||
Obx(
|
||||
() => AnimatedOpacity(
|
||||
opacity: controller.isLoadingContainer ? 0 : 1,
|
||||
duration: const Duration(milliseconds: 750),
|
||||
child: body(context),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => ProgressContainer(
|
||||
isShow: controller.isLoadingContainer, onDismiss: null),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget? drawer() {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|||
import 'package:get/get.dart';
|
||||
import 'package:snap_and_cook_mobile/components/recipe/recipe_item.dart';
|
||||
|
||||
import '../../../domain/entities/recipe.dart';
|
||||
import '../view_model/home_view_model.dart';
|
||||
|
||||
class RecipeRecommendationWidget extends GetView<HomeViewModel> {
|
||||
|
@ -12,26 +11,22 @@ class RecipeRecommendationWidget extends GetView<HomeViewModel> {
|
|||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: GridView.builder(
|
||||
child: Obx(() => GridView.builder(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: 8,
|
||||
itemCount: controller.recipes.length,
|
||||
primary: false,
|
||||
shrinkWrap: true,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisSpacing: 8,
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: 0,
|
||||
childAspectRatio: 0.75,
|
||||
),
|
||||
itemBuilder: (ctx, index) => GestureDetector(
|
||||
onTap: () {
|
||||
controller.navigateToRecipeDetail();
|
||||
},
|
||||
child: RecipeItem(
|
||||
recipe: Recipe(),
|
||||
),
|
||||
mainAxisSpacing: 16,
|
||||
childAspectRatio: 0.675,
|
||||
),
|
||||
itemBuilder: (ctx, index) => RecipeItem(
|
||||
recipe: controller.recipes[index],
|
||||
onTap: controller.navigateToRecipeDetail,
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -97,7 +97,8 @@ class HomeView extends BaseView<HomeViewModel> {
|
|||
],
|
||||
),
|
||||
),
|
||||
RecipeRecommendationWidget()
|
||||
RecipeRecommendationWidget(),
|
||||
SizedBox(height: 64,)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
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 '../../../domain/entities/recipe.dart';
|
||||
import '../../../resources/arguments/argument_constants.dart';
|
||||
import '../../../routes/routes/main_route.dart';
|
||||
import '../../base/base_view_model.dart';
|
||||
|
||||
|
@ -8,18 +11,35 @@ 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);
|
||||
}
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
super.onReady();
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
_fetchAllRecipes();
|
||||
}
|
||||
|
||||
void navigateToRecipeDetail() {
|
||||
Get.toNamed(MainRoute.detail);
|
||||
Future<void> _fetchAllRecipes() async {
|
||||
showLoadingContainer();
|
||||
var data = await _recipeUseCase.fetchRecipes(cancelToken);
|
||||
data.fold((l){
|
||||
}, (result){
|
||||
hideLoadingContainer();
|
||||
recipes.clear();
|
||||
recipes.addAll(result);
|
||||
});
|
||||
}
|
||||
|
||||
void navigateToRecipeDetail(String uuid) {
|
||||
Get.toNamed(MainRoute.detail, arguments: {
|
||||
ArgumentConstants.recipeUuid: uuid,
|
||||
});
|
||||
}
|
||||
|
||||
void navigateToRecipeDetection() {
|
||||
|
|
|
@ -3,16 +3,17 @@ import 'package:flutter/material.dart';
|
|||
import '../../../styles/text_styles/tt_commons_text_styles.dart';
|
||||
|
||||
class FoodPrepWidget extends StatelessWidget {
|
||||
const FoodPrepWidget({super.key});
|
||||
final int? serving, prepTime, cookTime;
|
||||
const FoodPrepWidget({super.key, this.serving, this.prepTime, this.cookTime});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
_foodPrepItem('Porsi', '4'),
|
||||
_foodPrepItem('Persiapan', '20 menit'),
|
||||
_foodPrepItem('Memasak', '30 menit'),
|
||||
_foodPrepItem('Porsi', '${serving ?? 0}'),
|
||||
_foodPrepItem('Persiapan', '${prepTime ?? 0} menit'),
|
||||
_foodPrepItem('Memasak', '${cookTime ?? 0} menit'),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,19 +3,17 @@ import 'package:snap_and_cook_mobile/data/remote/models/ingredient_model.dart';
|
|||
import 'package:snap_and_cook_mobile/presentation/recipe_detail/components/ingredients_item.dart';
|
||||
|
||||
class IngredientListWidget extends StatelessWidget {
|
||||
const IngredientListWidget({super.key});
|
||||
final List<Ingredient> ingredients;
|
||||
|
||||
const IngredientListWidget({super.key, required this.ingredients});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView.builder(
|
||||
itemBuilder: (context, index) {
|
||||
return IngredientItem(ingredient: Ingredient(
|
||||
name: "Telur",
|
||||
quantity: 1,
|
||||
unit: "Butir"
|
||||
));
|
||||
return IngredientItem(ingredient: ingredients[index]);
|
||||
},
|
||||
itemCount: 5,
|
||||
itemCount: ingredients.length,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:snap_and_cook_mobile/data/remote/models/ingredient_model.dart';
|
||||
import 'package:snap_and_cook_mobile/styles/text_styles/tt_commons_text_styles.dart';
|
||||
import 'package:snap_and_cook_mobile/utils/extension/double_extension.dart';
|
||||
|
||||
import '../../../styles/colors.dart';
|
||||
|
||||
|
@ -19,15 +20,43 @@ class IngredientItem extends StatelessWidget {
|
|||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Text(
|
||||
'${ingredient.name} ${ingredient.quantity} ${ingredient.unit}',
|
||||
Expanded(
|
||||
child: Text(
|
||||
'${ingredient.name} ${_getQuantity()} ${ingredient.unit}',
|
||||
style: TTCommonsTextStyles.textMd.textMedium(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _getQuantity() {
|
||||
if (ingredient.quantity == null || ingredient.quantity == 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (ingredient.quantity! % 1 == 0) {
|
||||
return '${ingredient.quantity?.toInt()}';
|
||||
} else {
|
||||
int wholeNumber = ingredient.quantity!.toInt();
|
||||
double decimal = ingredient.quantity! - wholeNumber;
|
||||
String fraction = decimal.decimalToFraction();
|
||||
|
||||
if (wholeNumber == 0) return fraction.trim();
|
||||
|
||||
return '$wholeNumber $fraction';
|
||||
}
|
||||
}
|
||||
|
||||
// String _getQuantity(){
|
||||
// if(ingredient.quantity == null || ingredient.quantity == 0){
|
||||
// return '';
|
||||
// }
|
||||
//
|
||||
// return '${ingredient.quantity?.toInt()}';
|
||||
// }
|
||||
|
||||
Widget _circleWidget() {
|
||||
return Container(
|
||||
width: 8.0,
|
||||
|
|
|
@ -14,15 +14,18 @@ class StepItem extends StatelessWidget {
|
|||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_stepContainer(),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Text(
|
||||
Expanded(
|
||||
child: Text(
|
||||
step,
|
||||
style: TTCommonsTextStyles.textMd.textMedium(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -2,15 +2,16 @@ import 'package:flutter/material.dart';
|
|||
import 'package:snap_and_cook_mobile/presentation/recipe_detail/components/step_item.dart';
|
||||
|
||||
class StepListWidget extends StatelessWidget {
|
||||
const StepListWidget({super.key});
|
||||
final List<String> steps;
|
||||
const StepListWidget({super.key, required this.steps});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView.builder(
|
||||
itemBuilder: (context, index) {
|
||||
return StepItem(step: "Panaskan minyak goreng", index: index);
|
||||
return StepItem(step: steps[index], index: index);
|
||||
},
|
||||
itemCount: 5,
|
||||
itemCount: steps.length,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get_state_manager/get_state_manager.dart';
|
||||
import 'package:snap_and_cook_mobile/components/basic_button.dart';
|
||||
import 'package:snap_and_cook_mobile/components/image/basic_network_image.dart';
|
||||
import 'package:snap_and_cook_mobile/presentation/recipe_detail/components/ingredient_list_widget.dart';
|
||||
|
@ -17,13 +18,19 @@ class RecipeDetailView extends BaseView<RecipeDetailViewModel> {
|
|||
|
||||
@override
|
||||
PreferredSizeWidget? appBar(BuildContext context) {
|
||||
return BasicAppBar(appBarTitleText: "", centerTitle: false, leadingIconData: Icons.arrow_back,);
|
||||
return BasicAppBar(
|
||||
appBarTitleText: "",
|
||||
centerTitle: false,
|
||||
leadingIconData: Icons.arrow_back,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24,),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
child: Column(
|
||||
|
@ -31,33 +38,62 @@ class RecipeDetailView extends BaseView<RecipeDetailViewModel> {
|
|||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24.0),
|
||||
child: Text('Nasi Goreng Dadakan', style: TTCommonsTextStyles.textXl.textMedium()),
|
||||
child: Obx(() => Text(controller.recipe.value?.title ?? '',
|
||||
style: TTCommonsTextStyles.textXl.textMedium())),
|
||||
),
|
||||
SizedBox(
|
||||
height: 200,
|
||||
width: double.infinity,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||
child: BasicNetworkImage(imageUrl: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcT80b6egSM9UngjcWwCu92vjmfRQux7WcZCMQ&usqp=CAU')),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||
child: Obx(
|
||||
() => BasicNetworkImage(
|
||||
imageUrl: controller.recipe.value?.image ?? ''),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24.0),
|
||||
child: Text('Orak arik telur buncis adalah hidangan lezat yang cocok sebagai lauk makan siang atau makan malam.', style: TTCommonsTextStyles.textMd.textRegular()),
|
||||
child: Obx(
|
||||
() => Text(controller.recipe.value?.description ?? '',
|
||||
style: TTCommonsTextStyles.textMd.textRegular()),
|
||||
),
|
||||
Divider(
|
||||
),
|
||||
const Divider(
|
||||
color: Colors.grey,
|
||||
),
|
||||
FoodPrepWidget(),
|
||||
RecipeDetailDividerWidget(title: 'Bahan',),
|
||||
IngredientListWidget(),
|
||||
RecipeDetailDividerWidget(title: 'Alat Memasak',),
|
||||
RecipeDetailDividerWidget(title: 'Langkah-langkah',),
|
||||
StepListWidget(),
|
||||
SizedBox(height: 24,),
|
||||
BasicButton(onPress: (){},
|
||||
Obx(
|
||||
() => FoodPrepWidget(
|
||||
cookTime: controller.recipe.value?.cookTime,
|
||||
prepTime: controller.recipe.value?.prepTime,
|
||||
serving: controller.recipe.value?.servings,
|
||||
),
|
||||
),
|
||||
const RecipeDetailDividerWidget(
|
||||
title: 'Bahan',
|
||||
),
|
||||
Obx(
|
||||
() => IngredientListWidget(
|
||||
ingredients: controller.recipe.value?.ingredients ?? [],
|
||||
),
|
||||
),
|
||||
const RecipeDetailDividerWidget(
|
||||
title: 'Alat Memasak',
|
||||
),
|
||||
const RecipeDetailDividerWidget(
|
||||
title: 'Langkah-langkah',
|
||||
),
|
||||
StepListWidget(steps:controller.recipe.value?.instructions ?? [],),
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
BasicButton(
|
||||
onPress: () {},
|
||||
bgColor: AppColors.copper,
|
||||
text: 'Mulai Memasak'),
|
||||
SizedBox(height: 24,),
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -1,10 +1,31 @@
|
|||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../domain/entities/recipe.dart';
|
||||
import '../../../domain/use_case/general/recipe_use_case.dart';
|
||||
import '../../../resources/arguments/argument_constants.dart';
|
||||
import '../../base/base_view_model.dart';
|
||||
|
||||
class RecipeDetailViewModel extends BaseViewModel {
|
||||
final _arguments = Get.arguments;
|
||||
String get recipeUuid => _arguments[ArgumentConstants.recipeUuid];
|
||||
|
||||
final RecipeUseCase _recipeUseCase = RecipeUseCase();
|
||||
final Rxn<Recipe> recipe = Rxn<Recipe>();
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
super.onReady();
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
_fetchRecipeDetail();
|
||||
}
|
||||
|
||||
Future<void> _fetchRecipeDetail() async {
|
||||
showLoadingContainer();
|
||||
var data = await _recipeUseCase.fetchDetailRecipe(cancelToken, recipeUuid);
|
||||
data.fold((l){
|
||||
}, (result){
|
||||
hideLoadingContainer();
|
||||
recipe.value = result;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import 'package:get/get.dart';
|
||||
|
||||
import '../view_model/utensil_view_model.dart';
|
||||
|
||||
class UtensilBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<UtensilViewModel>(
|
||||
() => UtensilViewModel(),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
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 '../../../components/appbar/basic_appbar.dart';
|
||||
import '../../../styles/images.dart';
|
||||
import '../view_model/utensil_view_model.dart';
|
||||
|
||||
class UtensilView extends BaseView<UtensilViewModel> {
|
||||
const UtensilView({super.key});
|
||||
|
||||
@override
|
||||
PreferredSizeWidget? appBar(BuildContext context) {
|
||||
return BasicAppBar(appBarTitleText: "", centerTitle: false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return Column(
|
||||
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,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../routes/routes/main_route.dart';
|
||||
import '../../base/base_view_model.dart';
|
||||
|
||||
class UtensilViewModel extends BaseViewModel {
|
||||
String version = "Version 0.0.1-dev";
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
super.onReady();
|
||||
_startSplash();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {}
|
||||
|
||||
Future<void> _startSplash() async {
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
Get.offNamed(MainRoute.home);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
class DatabaseConstant {
|
||||
static const String dbName = 'snapandcook_db.db';
|
||||
|
||||
static const String utensilsTable = 'utensil_tb';
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
import 'package:snap_and_cook_mobile/utils/extension/list_extension.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:sqflite/sql.dart';
|
||||
|
||||
Iterable<Iterable<T>> chunk<T>(Iterable<T> iterable, int chunkSize) sync* {
|
||||
var start = 0;
|
||||
while (start < iterable.length) {
|
||||
yield iterable.skip(start).take(chunkSize);
|
||||
start += chunkSize;
|
||||
}
|
||||
}
|
||||
|
||||
const _conflictValues = {
|
||||
ConflictAlgorithm.rollback: 'OR ROLLBACK',
|
||||
ConflictAlgorithm.abort: 'OR ABORT',
|
||||
ConflictAlgorithm.fail: 'OR FAIL',
|
||||
ConflictAlgorithm.ignore: 'OR IGNORE',
|
||||
ConflictAlgorithm.replace: 'OR REPLACE'
|
||||
};
|
||||
|
||||
extension MultipleInsert on Database {
|
||||
Future<int?> insertMultiple(
|
||||
String table,
|
||||
Iterable<Map<String, Object?>> data, {
|
||||
ConflictAlgorithm? conflictAlgorithm,
|
||||
int blockSize = 100,
|
||||
}) async {
|
||||
final conflictStr = conflictAlgorithm == null
|
||||
? ''
|
||||
: '${_conflictValues[conflictAlgorithm]}';
|
||||
final cols = data.first.keys.toList();
|
||||
final colsString = cols.map((e) => '"$e"').join(',\n\t');
|
||||
final command =
|
||||
'INSERT $conflictStr INTO "$table" (\n\t$colsString\n\t)\nVALUES\n\t';
|
||||
final argsString = '(${cols.map((e) => '?').join(', ')})';
|
||||
|
||||
int? result;
|
||||
for (var chunk in chunk(data, blockSize)) {
|
||||
final sql = StringBuffer(command);
|
||||
final params = <Object?>[];
|
||||
chunk.forEachIndexed((i, row) {
|
||||
sql.write(argsString);
|
||||
if (i == chunk.length - 1) {
|
||||
sql.write(';');
|
||||
} else {
|
||||
sql.write(',\n\t');
|
||||
}
|
||||
for (var col in cols) {
|
||||
params.add(row[col]);
|
||||
}
|
||||
});
|
||||
result = await rawInsert(sql.toString(), params);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
import 'package:chucker_flutter/chucker_flutter.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../interceptor/pretty_dio_logger_interceptor.dart';
|
||||
|
||||
extension DioExtention on Dio {
|
||||
Dio addInterceptor(Interceptor interceptor) {
|
||||
interceptors.add(interceptor);
|
||||
|
@ -14,8 +17,19 @@ extension DioExtention on Dio {
|
|||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
Dio modify(Function (Dio) modify) {
|
||||
modify.call(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
Dio usePrettyLogger() {
|
||||
addInterceptor(PrettyDioLoggerInterceptor());
|
||||
return this;
|
||||
}
|
||||
|
||||
Dio useChucker() {
|
||||
addInterceptor(ChuckerDioInterceptor());
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
extension DoubleExtension on double {
|
||||
String decimalToFraction() {
|
||||
const EPSILON = 1.0E-15;
|
||||
double h1 = 1;
|
||||
double h2 = 0;
|
||||
double k1 = 0;
|
||||
double k2 = 1;
|
||||
double b = this;
|
||||
do {
|
||||
double a = b.floorToDouble();
|
||||
double aux = h1;
|
||||
h1 = a * h1 + h2;
|
||||
h2 = aux;
|
||||
aux = k1;
|
||||
k1 = a * k1 + k2;
|
||||
k2 = aux;
|
||||
b = 1 / (b - a);
|
||||
} while ((this - h1 / k1).abs() > this * EPSILON);
|
||||
|
||||
String fraction = "${h1.toInt()}/${k1.toInt()}";
|
||||
return fraction;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
extension ListKt<T> on Iterable<T> {
|
||||
///forEach with index
|
||||
void forEachIndexed(void Function(int index, T element) action) {
|
||||
int index = 0;
|
||||
|
||||
for (T element in this) {
|
||||
action(index++, element);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import 'package:dio/dio.dart';
|
||||
|
||||
class PlatformHeaderInterceptor extends Interceptor {
|
||||
PlatformHeaderInterceptor();
|
||||
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
options.headers.addAll({
|
||||
'content-type': 'application/json',
|
||||
'x-platform': 'apps',
|
||||
'Accept': 'application/json',
|
||||
});
|
||||
super.onRequest(options, handler);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,307 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
/// code copied from https://pub.dev/packages/pretty_dio_logger
|
||||
class PrettyDioLoggerInterceptor extends Interceptor {
|
||||
/// Print request [Options]
|
||||
final bool request;
|
||||
|
||||
/// Print request header [Options.headers]
|
||||
final bool requestHeader;
|
||||
|
||||
/// Print request data [Options.tribeCollectionData]
|
||||
final bool requestBody;
|
||||
|
||||
/// Print [Response.data]
|
||||
final bool responseBody;
|
||||
|
||||
/// Print [Response.headers]
|
||||
final bool responseHeader;
|
||||
|
||||
/// Print error message
|
||||
final bool error;
|
||||
|
||||
/// InitialTab count to logPrint json response
|
||||
static const int initialTab = 1;
|
||||
|
||||
/// 1 tab length
|
||||
static const String tabStep = ' ';
|
||||
|
||||
/// Print compact json response
|
||||
final bool compact;
|
||||
|
||||
/// Width size per logPrint
|
||||
final int maxWidth;
|
||||
|
||||
/// Log printer; defaults logPrint log to console.
|
||||
/// In flutter, you'd better use debugPrint.
|
||||
/// you can also write log in a file.
|
||||
void Function(Object object) logPrint;
|
||||
|
||||
static const int defaultMaxWidth = 160;
|
||||
|
||||
PrettyDioLoggerInterceptor({
|
||||
this.request = true,
|
||||
this.requestHeader = true,
|
||||
this.requestBody = true,
|
||||
this.responseHeader = false,
|
||||
this.responseBody = true,
|
||||
this.error = true,
|
||||
this.maxWidth = defaultMaxWidth,
|
||||
this.compact = true,
|
||||
this.logPrint = print,
|
||||
});
|
||||
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
if (request) {
|
||||
_printRequestHeader(options);
|
||||
}
|
||||
if (requestHeader) {
|
||||
_printMapAsTable(options.queryParameters, header: 'Query Parameters');
|
||||
final requestHeaders = <String, dynamic>{};
|
||||
requestHeaders.addAll(options.headers);
|
||||
requestHeaders['contentType'] = options.contentType?.toString();
|
||||
requestHeaders['responseType'] = options.responseType.toString();
|
||||
requestHeaders['followRedirects'] = options.followRedirects;
|
||||
requestHeaders['connectTimeout'] = options.connectTimeout;
|
||||
requestHeaders['receiveTimeout'] = options.receiveTimeout;
|
||||
_printMapAsTable(requestHeaders, header: 'Headers');
|
||||
_printMapAsTable(options.extra, header: 'Extras');
|
||||
}
|
||||
if (requestBody && options.method != 'GET') {
|
||||
final dynamic data = options.data;
|
||||
if (data != null) {
|
||||
if (data is Map) _printMapAsTable(options.data as Map?, header: 'Body');
|
||||
if (data is FormData) {
|
||||
final formDataMap = <String, dynamic>{}
|
||||
..addEntries(data.fields)
|
||||
..addEntries(data.files);
|
||||
_printMapAsTable(formDataMap, header: 'Form data | ${data.boundary}');
|
||||
} else {
|
||||
try {
|
||||
var encoder = const JsonEncoder.withIndent(" ");
|
||||
// _printBoxed(header: "Body (Request)", text: encoder.convert(data));
|
||||
logPrint('╔ Body (Request)');
|
||||
logPrint('║');
|
||||
// logPrint(encoder.convert(data));
|
||||
_printData(encoder.convert(data));
|
||||
logPrint('║');
|
||||
_printLine('╚');
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
super.onRequest(options, handler);
|
||||
}
|
||||
|
||||
@override
|
||||
void onError(DioError err, ErrorInterceptorHandler handler) {
|
||||
if (error) {
|
||||
if (err.type == DioErrorType.response) {
|
||||
final uri = err.response?.requestOptions.uri;
|
||||
_printBoxed(
|
||||
header:
|
||||
'DioError ║ Status: ${err.response?.statusCode} ${err.response?.statusMessage}',
|
||||
text: uri.toString());
|
||||
if (err.response != null && err.response?.data != null) {
|
||||
logPrint('╔ ${err.type.toString()}');
|
||||
_printResponse(err.response!);
|
||||
}
|
||||
_printLine('╚');
|
||||
logPrint('');
|
||||
} else {
|
||||
_printBoxed(header: 'DioError ║ ${err.type}', text: err.message);
|
||||
}
|
||||
}
|
||||
super.onError(err, handler);
|
||||
}
|
||||
|
||||
@override
|
||||
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||
_printResponseHeader(response);
|
||||
if (responseHeader) {
|
||||
final responseHeaders = <String, String>{};
|
||||
response.headers
|
||||
.forEach((k, list) => responseHeaders[k] = list.toString());
|
||||
_printMapAsTable(responseHeaders, header: 'Headers');
|
||||
}
|
||||
|
||||
if (responseBody) {
|
||||
logPrint('╔ Body (Response)');
|
||||
logPrint('║');
|
||||
_printResponse(response);
|
||||
logPrint('║');
|
||||
_printLine('╚');
|
||||
}
|
||||
super.onResponse(response, handler);
|
||||
}
|
||||
|
||||
void _printBoxed({String? header, String? text}) {
|
||||
logPrint('');
|
||||
logPrint('╔╣ $header');
|
||||
logPrint('║ $text');
|
||||
_printLine('╚');
|
||||
}
|
||||
|
||||
void _printResponse(Response response) {
|
||||
if (response.data != null) {
|
||||
if (response.data is Map) {
|
||||
_printPrettyMap(response.data as Map);
|
||||
} else if (response.data is List) {
|
||||
logPrint('║${_indent()}[');
|
||||
_printList(response.data as List);
|
||||
logPrint('║${_indent()}[');
|
||||
} else {
|
||||
_printBlock(response.data.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _printData(dynamic data) {
|
||||
if (data != null) {
|
||||
if (data is Map) {
|
||||
_printPrettyMap(data as Map);
|
||||
} else if (data is List) {
|
||||
logPrint('║${_indent()}[');
|
||||
_printList(data as List);
|
||||
logPrint('║${_indent()}[');
|
||||
} else {
|
||||
_printBlock(data.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _printResponseHeader(Response response) {
|
||||
final uri = response.requestOptions.uri;
|
||||
final method = response.requestOptions.method;
|
||||
_printBoxed(
|
||||
header:
|
||||
'Response ║ $method ║ Status: ${response.statusCode} ${response.statusMessage}',
|
||||
text: uri.toString());
|
||||
}
|
||||
|
||||
void _printRequestHeader(RequestOptions options) {
|
||||
final uri = options.uri;
|
||||
final method = options.method;
|
||||
_printBoxed(header: 'Request ║ $method ', text: uri.toString());
|
||||
}
|
||||
|
||||
void _printLine([String pre = '', String suf = '╝']) =>
|
||||
logPrint('$pre${'═' * maxWidth}$suf');
|
||||
|
||||
void _printKV(String? key, Object? v) {
|
||||
final pre = '╟ "$key": ';
|
||||
final msg = v.toString();
|
||||
|
||||
if (pre.length + msg.length > maxWidth) {
|
||||
logPrint(pre);
|
||||
_printBlock(msg);
|
||||
} else {
|
||||
logPrint('$pre$msg');
|
||||
}
|
||||
}
|
||||
|
||||
void _printBlock(String msg) {
|
||||
final lines = (msg.length / maxWidth).ceil();
|
||||
for (var i = 0; i < lines; ++i) {
|
||||
logPrint((i >= 0 ? '' : '') +
|
||||
msg.substring(i * maxWidth,
|
||||
math.min<int>(i * maxWidth + maxWidth, msg.length)));
|
||||
}
|
||||
}
|
||||
|
||||
String _indent([int tabCount = initialTab]) => tabStep * tabCount;
|
||||
|
||||
void _printPrettyMap(
|
||||
Map data, {
|
||||
int tabs = initialTab,
|
||||
bool isListItem = false,
|
||||
bool isLast = false,
|
||||
}) {
|
||||
var _tabs = tabs;
|
||||
final isRoot = _tabs == initialTab;
|
||||
final initialIndent = _indent(_tabs);
|
||||
_tabs++;
|
||||
|
||||
if (isRoot || isListItem) logPrint('║$initialIndent{');
|
||||
|
||||
data.keys.toList().asMap().forEach((index, dynamic key) {
|
||||
final isLast = index == data.length - 1;
|
||||
dynamic value = data[key];
|
||||
if (value is String) {
|
||||
value = '"${value.toString().replaceAll(RegExp(r'(\r|\n)+'), " ")}"';
|
||||
}
|
||||
if (value is Map) {
|
||||
if (compact && _canFlattenMap(value)) {
|
||||
logPrint('║${_indent(_tabs)} "$key": $value${!isLast ? ',' : ''}');
|
||||
} else {
|
||||
logPrint('║${_indent(_tabs)} "$key": {');
|
||||
_printPrettyMap(value, tabs: _tabs);
|
||||
}
|
||||
} else if (value is List) {
|
||||
if (compact && _canFlattenList(value)) {
|
||||
logPrint('║${_indent(_tabs)} "$key": ${value.toString()}');
|
||||
} else {
|
||||
logPrint('║${_indent(_tabs)} "$key": [');
|
||||
_printList(value, tabs: _tabs);
|
||||
logPrint('║${_indent(_tabs)} ]${isLast ? '' : ','}');
|
||||
}
|
||||
} else {
|
||||
final msg = value.toString().replaceAll('\n', '');
|
||||
final indent = _indent(_tabs);
|
||||
final linWidth = maxWidth - indent.length;
|
||||
if (msg.length + indent.length > linWidth) {
|
||||
final lines = (msg.length / linWidth).ceil();
|
||||
for (var i = 0; i < lines; ++i) {
|
||||
logPrint(
|
||||
'║${_indent(_tabs)} ${msg.substring(i * linWidth, math.min<int>(i * linWidth + linWidth, msg.length))}');
|
||||
}
|
||||
} else {
|
||||
logPrint('║${_indent(_tabs)} "$key": $msg${!isLast ? ',' : ''}');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
logPrint('║$initialIndent}${isListItem && !isLast ? ',' : ''}');
|
||||
}
|
||||
|
||||
void _printList(List list, {int tabs = initialTab}) {
|
||||
int tabsCount = 2;
|
||||
list.asMap().forEach((i, dynamic e) {
|
||||
final isLast = i == list.length - 1;
|
||||
if (e is Map) {
|
||||
if (compact && _canFlattenMap(e)) {
|
||||
logPrint('║${_indent(tabs)} $e${!isLast ? ',' : ''}');
|
||||
} else {
|
||||
_printPrettyMap(e, tabs: tabs + 1, isListItem: true, isLast: isLast);
|
||||
}
|
||||
} else {
|
||||
logPrint('║${_indent(tabs + tabsCount)} $e${isLast ? '' : ','}');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool _canFlattenMap(Map map) {
|
||||
return map.values
|
||||
.where((dynamic val) => val is Map || val is List)
|
||||
.isEmpty &&
|
||||
map.toString().length < maxWidth;
|
||||
}
|
||||
|
||||
bool _canFlattenList(List list) {
|
||||
int maxListLength = 10;
|
||||
|
||||
return list.length < maxListLength && list.toString().length < maxWidth;
|
||||
}
|
||||
|
||||
void _printMapAsTable(Map? map, {String? header}) {
|
||||
if (map == null || map.isEmpty) return;
|
||||
logPrint('╔ $header ');
|
||||
map.forEach(
|
||||
(dynamic key, dynamic value) => _printKV(key.toString(), value));
|
||||
_printLine('╚');
|
||||
}
|
||||
}
|
143
pubspec.lock
143
pubspec.lock
|
@ -193,6 +193,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
chucker_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: chucker_flutter
|
||||
sha256: e8df01ec830e1364dee83a66e304df490b9b416008faf559bfc3a6e341608513
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0+1"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -293,10 +301,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
||||
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
version: "6.1.4"
|
||||
file_selector_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -366,6 +374,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.3"
|
||||
flutter_json_viewer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_json_viewer
|
||||
sha256: "3acc20693c3e6465ff2e2a7884269e362cb8acc2b11e7caa9b283872f854e7bf"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
@ -374,6 +390,11 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
flutter_localizations:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -548,10 +569,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: intl
|
||||
sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91"
|
||||
sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.17.0"
|
||||
version: "0.18.0"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -856,6 +877,54 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.27.7"
|
||||
share_plus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus
|
||||
sha256: f582d5741930f3ad1bf0211d358eddc0508cc346e5b4b248bd1e569c995ebb7a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.3"
|
||||
share_plus_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_linux
|
||||
sha256: dc32bf9f1151b9864bb86a997c61a487967a08f2e0b4feaa9a10538712224da4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
share_plus_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_macos
|
||||
sha256: "44daa946f2845045ecd7abb3569b61cd9a55ae9cc4cbec9895b2067b270697ae"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
share_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_platform_interface
|
||||
sha256: df08bc3a07d01f5ea47b45d03ffcba1fa9cd5370fb44b3f38c70e42cced0f956
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.1"
|
||||
share_plus_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_web
|
||||
sha256: eaef05fa8548b372253e772837dd1fbe4ce3aca30ea330765c945d7d4f7c9935
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
share_plus_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_windows
|
||||
sha256: "3a21515ae7d46988d42130cd53294849e280a5de6ace24bae6912a1bffd757d4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -974,7 +1043,7 @@ packages:
|
|||
source: hosted
|
||||
version: "7.0.0"
|
||||
sqflite:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sqflite
|
||||
sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a"
|
||||
|
@ -1069,6 +1138,70 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
url_launcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher
|
||||
sha256: "47e208a6711459d813ba18af120d9663c20bdf6985d6ad39fe165d2538378d27"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.14"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.2.2"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.2.4"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_platform_interface
|
||||
sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
url_launcher_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: ba140138558fcc3eead51a1c42e92a9fb074a1b1149ed3c73e66035b2ccd94f2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.19"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -42,7 +42,7 @@ dependencies:
|
|||
fluttertoast: ^8.2.1
|
||||
retrofit: ^3.0.1+1
|
||||
pull_to_refresh: ^2.0.0
|
||||
intl: ^0.17.0
|
||||
intl:
|
||||
flutter_screenutil: ^5.6.1
|
||||
get: ^4.6.5
|
||||
shared_preferences: ^2.2.2
|
||||
|
@ -54,7 +54,8 @@ dependencies:
|
|||
cached_network_image: ^3.2.3
|
||||
shimmer:
|
||||
flutter_dotenv: ^5.0.2
|
||||
|
||||
chucker_flutter:
|
||||
sqflite: ^2.2.8+4
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
Loading…
Reference in New Issue