From 2bdf328d205292373dcbca0b6ddfcec7fdc256e4 Mon Sep 17 00:00:00 2001 From: IbnuBatutah Date: Wed, 24 Jan 2024 21:23:15 +0700 Subject: [PATCH] Menambahkan utensil page, dan utensil contract --- lib/components/recipe/recipe_item.dart | 79 +++-- lib/configuration/app_environtment.dart | 1 + lib/data/local/database_helper.dart | 40 +++ lib/data/local/utensils_contract.dart | 38 +++ lib/data/remote/models/ingredient_model.dart | 2 +- .../remote/models/ingredient_model.g.dart | 2 +- lib/data/remote/models/recipe_model.dart | 2 +- lib/data/remote/models/utensil_model.dart | 21 ++ .../use_case/general/recipe_use_case.dart | 2 - lib/init.dart | 13 +- lib/presentation/base/base_view.dart | 22 +- .../recipe_recommendation_widget.dart | 37 +-- lib/presentation/home/view/home_view.dart | 3 +- .../home/view_model/home_view_model.dart | 28 +- .../components/food_prep_widget.dart | 9 +- .../components/ingredient_list_widget.dart | 12 +- .../components/ingredients_item.dart | 35 +- .../recipe_detail/components/step_item.dart | 9 +- .../components/step_list_widget.dart | 7 +- .../view/recipe_detail_view.dart | 68 +++- .../view_model/recipe_detail_view_model.dart | 25 +- .../utensils/binding/utensil_binding.dart | 12 + .../utensils/view/utensil_view.dart | 38 +++ .../view_model/utensil_view_model.dart | 22 ++ .../constants/database_constant.dart | 5 + lib/utils/extension/database_extension.dart | 56 ++++ lib/utils/extension/dio_extension.dart | 14 + lib/utils/extension/double_extension.dart | 23 ++ lib/utils/extension/list_extension.dart | 10 + .../platform_header_interceptor.dart | 15 + .../pretty_dio_logger_interceptor.dart | 307 ++++++++++++++++++ pubspec.lock | 143 +++++++- pubspec.yaml | 5 +- 33 files changed, 988 insertions(+), 117 deletions(-) create mode 100644 lib/data/local/database_helper.dart create mode 100644 lib/data/local/utensils_contract.dart create mode 100644 lib/data/remote/models/utensil_model.dart create mode 100644 lib/presentation/utensils/binding/utensil_binding.dart create mode 100644 lib/presentation/utensils/view/utensil_view.dart create mode 100644 lib/presentation/utensils/view_model/utensil_view_model.dart create mode 100644 lib/resources/constants/database_constant.dart create mode 100644 lib/utils/extension/database_extension.dart create mode 100644 lib/utils/extension/double_extension.dart create mode 100644 lib/utils/extension/list_extension.dart create mode 100644 lib/utils/interceptor/platform_header_interceptor.dart create mode 100644 lib/utils/interceptor/pretty_dio_logger_interceptor.dart diff --git a/lib/components/recipe/recipe_item.dart b/lib/components/recipe/recipe_item.dart index b7dd9d6..1d9ef16 100644 --- a/lib/components/recipe/recipe_item.dart +++ b/lib/components/recipe/recipe_item.dart @@ -6,43 +6,58 @@ 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( - 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', - height: 160, - width: double.infinity, - ), - const SizedBox( - height: 8, - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Text( - recipe.title ?? 'Masakan', - style: TTCommonsTextStyles.textMd.textMedium(), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Text( - '${recipe.cookTime ?? 0} menit', - style: TTCommonsTextStyles.textSm.textRegular(), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), + print("IMAGE IS ${recipe.image}"); + return GestureDetector( + onTap: () { + onTap(recipe.uuid ?? ''); + }, + child: Card( + elevation: 2, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 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, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Text( + recipe.title ?? 'Masakan', + style: TTCommonsTextStyles.textMd.textMedium(), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Text( + '${recipe.cookTime ?? 0} menit', + style: TTCommonsTextStyles.textSm.textRegular(), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), - ], + ], + ), + ), ); } } diff --git a/lib/configuration/app_environtment.dart b/lib/configuration/app_environtment.dart index 9a9f568..816797c 100644 --- a/lib/configuration/app_environtment.dart +++ b/lib/configuration/app_environtment.dart @@ -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 { diff --git a/lib/data/local/database_helper.dart b/lib/data/local/database_helper.dart new file mode 100644 index 0000000..73fe933 --- /dev/null +++ b/lib/data/local/database_helper.dart @@ -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 get database async { + _database = await _initializeDb(); + return _database; + } + + Future _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; + } + + +} \ No newline at end of file diff --git a/lib/data/local/utensils_contract.dart b/lib/data/local/utensils_contract.dart new file mode 100644 index 0000000..10a47ee --- /dev/null +++ b/lib/data/local/utensils_contract.dart @@ -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 insertUtensil(Utensil utensil) async { + final Database db = await _databaseHelper.database; + await db.insert(DatabaseConstant.utensilsTable, utensil.toJson()); + print('Data saved!'); + } + + Future insertAllUtensil(List 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 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> getUtensils() async { + final Database db = await _databaseHelper.database; + List> results = + await db.query(DatabaseConstant.utensilsTable); + return results.map((res) => Utensil.fromJson(res)).toList(); + } +} diff --git a/lib/data/remote/models/ingredient_model.dart b/lib/data/remote/models/ingredient_model.dart index 0f6e30f..9d5a362 100644 --- a/lib/data/remote/models/ingredient_model.dart +++ b/lib/data/remote/models/ingredient_model.dart @@ -5,7 +5,7 @@ part 'ingredient_model.g.dart'; @JsonSerializable() class Ingredient { String? name; - int? quantity; + double? quantity; String? unit; Ingredient({ diff --git a/lib/data/remote/models/ingredient_model.g.dart b/lib/data/remote/models/ingredient_model.g.dart index 1c6c1d3..efc8999 100644 --- a/lib/data/remote/models/ingredient_model.g.dart +++ b/lib/data/remote/models/ingredient_model.g.dart @@ -8,7 +8,7 @@ part of 'ingredient_model.dart'; Ingredient _$IngredientFromJson(Map json) => Ingredient( name: json['name'] as String?, - quantity: json['quantity'] as int?, + quantity: (json['quantity'] as num?)?.toDouble(), unit: json['unit'] as String?, ); diff --git a/lib/data/remote/models/recipe_model.dart b/lib/data/remote/models/recipe_model.dart index 38b81c4..135c819 100644 --- a/lib/data/remote/models/recipe_model.dart +++ b/lib/data/remote/models/recipe_model.dart @@ -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, diff --git a/lib/data/remote/models/utensil_model.dart b/lib/data/remote/models/utensil_model.dart new file mode 100644 index 0000000..f59550b --- /dev/null +++ b/lib/data/remote/models/utensil_model.dart @@ -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 toJson() => _$UtensilToJson(this); + + factory Utensil.fromJson(Map json) => + _$UtensilFromJson(json); +} \ No newline at end of file diff --git a/lib/domain/use_case/general/recipe_use_case.dart b/lib/domain/use_case/general/recipe_use_case.dart index 14cb792..fa61cd8 100644 --- a/lib/domain/use_case/general/recipe_use_case.dart +++ b/lib/domain/use_case/general/recipe_use_case.dart @@ -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: ""))); } } diff --git a/lib/init.dart b/lib/init.dart index e5fd09e..7c3f100 100644 --- a/lib/init.dart +++ b/lib/init.dart @@ -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 init() async { WidgetsFlutterBinding.ensureInitialized(); + await AppEnvironment.load(); + await Get.putAsync( - () 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()); diff --git a/lib/presentation/base/base_view.dart b/lib/presentation/base/base_view.dart index efd4044..6d2ff24 100644 --- a/lib/presentation/base/base_view.dart +++ b/lib/presentation/base/base_view.dart @@ -3,10 +3,11 @@ 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'; -abstract class BaseView extends GetView{ +abstract class BaseView extends GetView { const BaseView({super.key}); Widget body(BuildContext context); @@ -62,9 +63,24 @@ abstract class BaseView extends GetView{ Widget pageContent(BuildContext context) { return SafeArea( - child: body(context), + 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; } @@ -72,4 +88,4 @@ abstract class BaseView extends GetView{ Widget? bottomNavigationBar() { return null; } -} \ No newline at end of file +} diff --git a/lib/presentation/home/components/recipe_recommendation_widget.dart b/lib/presentation/home/components/recipe_recommendation_widget.dart index 984f885..07e32b6 100644 --- a/lib/presentation/home/components/recipe_recommendation_widget.dart +++ b/lib/presentation/home/components/recipe_recommendation_widget.dart @@ -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 { @@ -12,26 +11,22 @@ class RecipeRecommendationWidget extends GetView { Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: GridView.builder( - physics: const NeverScrollableScrollPhysics(), - itemCount: 8, - primary: false, - shrinkWrap: true, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisSpacing: 8, - crossAxisCount: 2, - mainAxisSpacing: 0, - childAspectRatio: 0.75, - ), - itemBuilder: (ctx, index) => GestureDetector( - onTap: () { - controller.navigateToRecipeDetail(); - }, - child: RecipeItem( - recipe: Recipe(), - ), - ), - ), + child: Obx(() => GridView.builder( + physics: const NeverScrollableScrollPhysics(), + itemCount: controller.recipes.length, + primary: false, + shrinkWrap: true, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisSpacing: 8, + crossAxisCount: 2, + mainAxisSpacing: 16, + childAspectRatio: 0.675, + ), + itemBuilder: (ctx, index) => RecipeItem( + recipe: controller.recipes[index], + onTap: controller.navigateToRecipeDetail, + ), + )), ); } diff --git a/lib/presentation/home/view/home_view.dart b/lib/presentation/home/view/home_view.dart index 6944555..1d7f94b 100644 --- a/lib/presentation/home/view/home_view.dart +++ b/lib/presentation/home/view/home_view.dart @@ -97,7 +97,8 @@ class HomeView extends BaseView { ], ), ), - RecipeRecommendationWidget() + RecipeRecommendationWidget(), + SizedBox(height: 64,) ], ), ); diff --git a/lib/presentation/home/view_model/home_view_model.dart b/lib/presentation/home/view_model/home_view_model.dart index 7275f96..812e237 100644 --- a/lib/presentation/home/view_model/home_view_model.dart +++ b/lib/presentation/home/view_model/home_view_model.dart @@ -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 recipes = RxList(); void onSearchSubmitted(String value) { print(value); } @override - void onReady() { - super.onReady(); + void onInit() { + super.onInit(); + _fetchAllRecipes(); } - void navigateToRecipeDetail() { - Get.toNamed(MainRoute.detail); + Future _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() { diff --git a/lib/presentation/recipe_detail/components/food_prep_widget.dart b/lib/presentation/recipe_detail/components/food_prep_widget.dart index 7dcb964..7176558 100644 --- a/lib/presentation/recipe_detail/components/food_prep_widget.dart +++ b/lib/presentation/recipe_detail/components/food_prep_widget.dart @@ -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'), ], ); } diff --git a/lib/presentation/recipe_detail/components/ingredient_list_widget.dart b/lib/presentation/recipe_detail/components/ingredient_list_widget.dart index c2f3816..f2bd78d 100644 --- a/lib/presentation/recipe_detail/components/ingredient_list_widget.dart +++ b/lib/presentation/recipe_detail/components/ingredient_list_widget.dart @@ -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 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(), ); diff --git a/lib/presentation/recipe_detail/components/ingredients_item.dart b/lib/presentation/recipe_detail/components/ingredients_item.dart index 4c80ee4..9cf0ac8 100644 --- a/lib/presentation/recipe_detail/components/ingredients_item.dart +++ b/lib/presentation/recipe_detail/components/ingredients_item.dart @@ -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}', - style: TTCommonsTextStyles.textMd.textMedium(), + 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, diff --git a/lib/presentation/recipe_detail/components/step_item.dart b/lib/presentation/recipe_detail/components/step_item.dart index eb35559..c8091ee 100644 --- a/lib/presentation/recipe_detail/components/step_item.dart +++ b/lib/presentation/recipe_detail/components/step_item.dart @@ -14,14 +14,17 @@ class StepItem extends StatelessWidget { return Padding( padding: const EdgeInsets.all(8.0), child: Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ _stepContainer(), const SizedBox( width: 12, ), - Text( - step, - style: TTCommonsTextStyles.textMd.textMedium(), + Expanded( + child: Text( + step, + style: TTCommonsTextStyles.textMd.textMedium(), + ), ), ], ), diff --git a/lib/presentation/recipe_detail/components/step_list_widget.dart b/lib/presentation/recipe_detail/components/step_list_widget.dart index c218b4c..f8ab6f6 100644 --- a/lib/presentation/recipe_detail/components/step_list_widget.dart +++ b/lib/presentation/recipe_detail/components/step_list_widget.dart @@ -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 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(), ); diff --git a/lib/presentation/recipe_detail/view/recipe_detail_view.dart b/lib/presentation/recipe_detail/view/recipe_detail_view.dart index 1c824b8..8d67a40 100644 --- a/lib/presentation/recipe_detail/view/recipe_detail_view.dart +++ b/lib/presentation/recipe_detail/view/recipe_detail_view.dart @@ -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 { @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 { 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, + ), ], ), ), diff --git a/lib/presentation/recipe_detail/view_model/recipe_detail_view_model.dart b/lib/presentation/recipe_detail/view_model/recipe_detail_view_model.dart index 5f7526a..2eef467 100644 --- a/lib/presentation/recipe_detail/view_model/recipe_detail_view_model.dart +++ b/lib/presentation/recipe_detail/view_model/recipe_detail_view_model.dart @@ -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 = Rxn(); @override - void onReady() { - super.onReady(); + void onInit() { + super.onInit(); + _fetchRecipeDetail(); + } + + Future _fetchRecipeDetail() async { + showLoadingContainer(); + var data = await _recipeUseCase.fetchDetailRecipe(cancelToken, recipeUuid); + data.fold((l){ + }, (result){ + hideLoadingContainer(); + recipe.value = result; + }); } @override diff --git a/lib/presentation/utensils/binding/utensil_binding.dart b/lib/presentation/utensils/binding/utensil_binding.dart new file mode 100644 index 0000000..143b08f --- /dev/null +++ b/lib/presentation/utensils/binding/utensil_binding.dart @@ -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(), + ); + } +} diff --git a/lib/presentation/utensils/view/utensil_view.dart b/lib/presentation/utensils/view/utensil_view.dart new file mode 100644 index 0000000..3b3e931 --- /dev/null +++ b/lib/presentation/utensils/view/utensil_view.dart @@ -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 { + 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, + ), + ], + ); + } +} diff --git a/lib/presentation/utensils/view_model/utensil_view_model.dart b/lib/presentation/utensils/view_model/utensil_view_model.dart new file mode 100644 index 0000000..3f51f1d --- /dev/null +++ b/lib/presentation/utensils/view_model/utensil_view_model.dart @@ -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 _startSplash() async { + await Future.delayed(const Duration(seconds: 2)); + Get.offNamed(MainRoute.home); + } +} diff --git a/lib/resources/constants/database_constant.dart b/lib/resources/constants/database_constant.dart new file mode 100644 index 0000000..a4a6546 --- /dev/null +++ b/lib/resources/constants/database_constant.dart @@ -0,0 +1,5 @@ +class DatabaseConstant { + static const String dbName = 'snapandcook_db.db'; + + static const String utensilsTable = 'utensil_tb'; +} \ No newline at end of file diff --git a/lib/utils/extension/database_extension.dart b/lib/utils/extension/database_extension.dart new file mode 100644 index 0000000..ad52582 --- /dev/null +++ b/lib/utils/extension/database_extension.dart @@ -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> chunk(Iterable 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 insertMultiple( + String table, + Iterable> 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 = []; + 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; + } +} \ No newline at end of file diff --git a/lib/utils/extension/dio_extension.dart b/lib/utils/extension/dio_extension.dart index 0e1b04c..767ad35 100644 --- a/lib/utils/extension/dio_extension.dart +++ b/lib/utils/extension/dio_extension.dart @@ -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; + } } diff --git a/lib/utils/extension/double_extension.dart b/lib/utils/extension/double_extension.dart new file mode 100644 index 0000000..65f8f8e --- /dev/null +++ b/lib/utils/extension/double_extension.dart @@ -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; + } +} \ No newline at end of file diff --git a/lib/utils/extension/list_extension.dart b/lib/utils/extension/list_extension.dart new file mode 100644 index 0000000..438e8f9 --- /dev/null +++ b/lib/utils/extension/list_extension.dart @@ -0,0 +1,10 @@ +extension ListKt on Iterable { + ///forEach with index + void forEachIndexed(void Function(int index, T element) action) { + int index = 0; + + for (T element in this) { + action(index++, element); + } + } +} \ No newline at end of file diff --git a/lib/utils/interceptor/platform_header_interceptor.dart b/lib/utils/interceptor/platform_header_interceptor.dart new file mode 100644 index 0000000..f29f825 --- /dev/null +++ b/lib/utils/interceptor/platform_header_interceptor.dart @@ -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); + } +} diff --git a/lib/utils/interceptor/pretty_dio_logger_interceptor.dart b/lib/utils/interceptor/pretty_dio_logger_interceptor.dart new file mode 100644 index 0000000..4f95ab7 --- /dev/null +++ b/lib/utils/interceptor/pretty_dio_logger_interceptor.dart @@ -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 = {}; + 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 = {} + ..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 = {}; + 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(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(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('╚'); + } +} diff --git a/pubspec.lock b/pubspec.lock index 911865f..7f5bf58 100644 --- a/pubspec.lock +++ b/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: diff --git a/pubspec.yaml b/pubspec.yaml index 3535616..2c5ba48 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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: