MEnambahkan fitur tutorial

This commit is contained in:
ibnubatutah 2024-06-11 21:27:26 +07:00
parent 1a5cc31678
commit e81d566892
23 changed files with 543 additions and 54 deletions

View File

@ -67,7 +67,7 @@ class RecipeFullItem extends StatelessWidget {
const Icon(Icons.timer, size: 16),
const SizedBox(width: 4),
Text(
'${recipe.cookTime ?? 0} menit',
'${recipe.prepTime ?? 0} menit',
style: TTCommonsTextStyles.textSm.textRegular(),
maxLines: 1,
overflow: TextOverflow.ellipsis,

View File

@ -12,6 +12,8 @@ class RecipeModel {
String? title;
String? description;
String? image;
String? difficulity;
double? confidence;
List<Ingredient>? ingredients;
List<String>? instructions;
int? prepTime;
@ -49,6 +51,8 @@ class RecipeModel {
cookTime: cookTime,
servings: servings,
utensils: utensils,
difficulity: difficulity,
confidence: confidence
);
}
}

View File

@ -23,7 +23,9 @@ RecipeModel _$RecipeModelFromJson(Map<String, dynamic> json) => RecipeModel(
utensils: (json['utensils'] as List<dynamic>?)
?.map((e) => e as String)
.toList(),
);
)
..difficulity = json['difficulity'] as String?
..confidence = (json['confidence'] as num?)?.toDouble();
Map<String, dynamic> _$RecipeModelToJson(RecipeModel instance) =>
<String, dynamic>{
@ -31,6 +33,8 @@ Map<String, dynamic> _$RecipeModelToJson(RecipeModel instance) =>
'title': instance.title,
'description': instance.description,
'image': instance.image,
'difficulity': instance.difficulity,
'confidence': instance.confidence,
'ingredients': instance.ingredients,
'instructions': instance.instructions,
'prepTime': instance.prepTime,

View File

@ -8,6 +8,8 @@ class Recipe {
String? title;
String? description;
String? image;
String? difficulity;
double? confidence;
List<Ingredient>? ingredients;
List<String>? instructions;
int? prepTime;
@ -26,5 +28,7 @@ class Recipe {
this.cookTime,
this.servings,
this.utensils,
this.difficulity,
this.confidence
});
}

View File

@ -0,0 +1,87 @@
import 'package:flutter/material.dart';
import 'package:tutorial_coach_mark/tutorial_coach_mark.dart';
List<TargetFocus> createTargets({
required GlobalKey keyBottomNavigation1,
required GlobalKey keyBottomNavigation2,
required GlobalKey keyBottomNavigation3,
}) {
List<TargetFocus> targets = [];
targets.add(
TargetFocus(
identify: "keyBottomNavigation1",
keyTarget: keyBottomNavigation1,
color: Colors.black38,
alignSkip: Alignment.topRight,
contents: [
TargetContent(
align: ContentAlign.top,
builder: (context, controller) {
return const Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Deteksi Resep",
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold
),
),
SizedBox(height: 8,),
Text(
"Tekan tombol berikut untuk mengambil foto bahan makanan dari kamera ataupun galeri",
style: TextStyle(
color: Colors.white,
fontSize: 18,
),
),
],
);
},
),
],
),
);
targets.add(
TargetFocus(
identify: "keyBottomNavigation3",
keyTarget: keyBottomNavigation3,
color: Colors.black38,
alignSkip: Alignment.topLeft,
contents: [
TargetContent(
align: ContentAlign.bottom,
builder: (context, controller) {
return const Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Pengaturan Alat Memasak Yang Dimiliki",
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold
),
),
SizedBox(height: 8,),
Text(
"Tekan tombol berikut untuk mengatur dan menambahkan alat-alat memasak yang kamu miliki di dapurmu",
style: TextStyle(
color: Colors.white,
fontSize: 18,
),
),
],
);
},
),
],
),
);
return targets;
}

View File

@ -18,6 +18,7 @@ class HomeView extends BaseView<HomeViewModel> {
@override
Widget? floatingActionButton() {
return FloatingActionButton(
key: controller.cameraKey,
onPressed: () {
controller.navigateToRecipeDetection();
},
@ -33,6 +34,11 @@ class HomeView extends BaseView<HomeViewModel> {
@override
Widget body(BuildContext context) {
controller.getPageContext(context);
return _buildBody();
}
Widget _buildBody(){
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -43,7 +49,7 @@ class HomeView extends BaseView<HomeViewModel> {
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
children: [
SizedBox(
const SizedBox(
height: 16,
),
Row(
@ -51,11 +57,12 @@ class HomeView extends BaseView<HomeViewModel> {
Text(
'Selamat Datang',
style: TTCommonsTextStyles.textLg.textRegular().copyWith(
color: AppColors.heroWhite,
),
color: AppColors.heroWhite,
),
),
const Spacer(),
IconButton(
key: controller.settingKey,
onPressed: controller.navigateToUtensilPage,
icon: const Icon(
Icons.settings,
@ -64,6 +71,7 @@ class HomeView extends BaseView<HomeViewModel> {
],
),
SearchTextField(
key: controller.searchKey,
controller: controller.searchController,
hintText: 'Cari Resep..',
inputType: TextInputType.text,
@ -89,8 +97,8 @@ class HomeView extends BaseView<HomeViewModel> {
],
),
),
RecipeRecommendationWidget(),
SizedBox(height: 64,)
const RecipeRecommendationWidget(),
const SizedBox(height: 64,)
],
),
);
@ -128,7 +136,7 @@ class HomeView extends BaseView<HomeViewModel> {
const SizedBox(
height: 8,
),
Text('Data 1'),
const Text('Data 1'),
],
),
);

View File

@ -1,17 +1,30 @@
import 'dart:ui';
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 'package:snap_and_cook_mobile/resources/constants/session_constants.dart';
import 'package:tutorial_coach_mark/tutorial_coach_mark.dart';
import '../../../domain/entities/recipe.dart';
import '../../../resources/arguments/argument_constants.dart';
import '../../../routes/routes/main_route.dart';
import '../../../utils/session/session.dart';
import '../../base/base_view_model.dart';
import '../components/tutorial_home_items.dart';
class HomeViewModel extends BaseViewModel {
TextEditingController searchController = TextEditingController();
final RecipeUseCase _recipeUseCase = RecipeUseCase();
final RxList<Recipe> recipes = RxList<Recipe>();
late TutorialCoachMark tutorialCoachMark;
BuildContext? pageContext;
final cameraKey = GlobalKey();
final searchKey = GlobalKey();
final settingKey = GlobalKey();
void onSearchSubmitted(String value) {
if (value.isEmpty) {
@ -25,6 +38,51 @@ class HomeViewModel extends BaseViewModel {
void onInit() {
super.onInit();
_fetchAllRecipes();
createTutorial();
showTutorial();
}
void getPageContext(BuildContext context){
pageContext = context;
}
void createTutorial() {
tutorialCoachMark = TutorialCoachMark(
targets: createTargets(
keyBottomNavigation1: cameraKey,
keyBottomNavigation2: searchKey,
keyBottomNavigation3: settingKey),
colorShadow: Colors.black38,
textSkip: "SKIP",
paddingFocus: 10,
opacityShadow: 0.5,
imageFilter: ImageFilter.blur(sigmaX: 8, sigmaY: 8),
onFinish: () {
print("finish");
Session.set(SessionConstants.isAlreadyOnBoardingHome, "yes");
},
onClickTarget: (target) {
},
onClickTargetWithTapPosition: (target, tapDetails) {},
onClickOverlay: (target) {},
onSkip: () {
Session.set(SessionConstants.isAlreadyOnBoardingHome, "yes");
return true;
},
);
}
Future<void> showTutorial() async {
Future.delayed(const Duration(seconds: 1));
String? isOnBoarded = await Session.get(SessionConstants.isAlreadyOnBoardingHome);
if (isOnBoarded != null) {
return;
}
if (pageContext?.mounted ?? false){
tutorialCoachMark.show(context: pageContext!);
}
}
Future<void> _fetchAllRecipes() async {

View File

@ -4,18 +4,37 @@ import 'package:snap_and_cook_mobile/presentation/recipe_detail/components/ingre
class IngredientListWidget extends StatelessWidget {
final List<Ingredient> ingredients;
final List<String> selectedIngredient;
const IngredientListWidget({super.key, required this.ingredients});
const IngredientListWidget(
{super.key, required this.ingredients, required this.selectedIngredient});
@override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) {
return IngredientItem(ingredient: ingredients[index]);
return IngredientItem(
ingredient: ingredients[index],
isSelected: isSelectedIngredient(index),
);
},
itemCount: ingredients.length,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
);
}
String _toChecker(String data){
if (data == "daging ayam") return 'ayam';
if (data == "sayap ayam") return 'ayam';
if (data == "paha ayam") return 'ayam';
if (data == "dada ayam") return 'ayam';
return data;
}
bool isSelectedIngredient(int index){
return selectedIngredient.contains(_toChecker(ingredients[index].name?.toLowerCase() ?? ''));
}
}

View File

@ -7,8 +7,9 @@ import '../../../styles/colors.dart';
class IngredientItem extends StatelessWidget {
final Ingredient ingredient;
final bool? isSelected;
const IngredientItem({super.key, required this.ingredient});
const IngredientItem({super.key, required this.ingredient, this.isSelected});
@override
Widget build(BuildContext context) {
@ -58,12 +59,14 @@ class IngredientItem extends StatelessWidget {
// }
Widget _circleWidget() {
print("IS SLECTED = $isSelected");
return Container(
width: 8.0,
height: 8.0,
decoration: const BoxDecoration(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColors.copper,
color: (isSelected ?? false) ? Colors.green : AppColors.copper,
),
);
}

View File

@ -1,10 +1,14 @@
import 'package:flutter/material.dart';
import 'package:snap_and_cook_mobile/components/recipe/utensils.dart';
import '../../../data/remote/models/utensil_model.dart';
class UtensilsListWidget extends StatelessWidget {
final List<String> utensils;
final List<String> selectedUtensil;
const UtensilsListWidget({super.key, required this.utensils});
const UtensilsListWidget(
{super.key, required this.utensils, required this.selectedUtensil});
@override
Widget build(BuildContext context) {
@ -13,7 +17,10 @@ class UtensilsListWidget extends StatelessWidget {
child: ListView.builder(
clipBehavior: Clip.none,
itemBuilder: (context, index) {
return UtensilItem(name: utensils[index], isSelected: false,);
return UtensilItem(
name: utensils[index],
isSelected: selectedUtensil.contains(utensils[index]),
);
},
itemCount: utensils.length,
scrollDirection: Axis.horizontal,

View File

@ -75,13 +75,16 @@ class RecipeDetailView extends BaseView<RecipeDetailViewModel> {
),
Obx(
() => IngredientListWidget(
selectedIngredient: controller.selectedIngredientName,
ingredients: controller.recipe.value?.ingredients ?? [],
),
),
const RecipeDetailDividerWidget(
title: 'Alat Memasak',
),
UtensilsListWidget(utensils: controller.recipe.value?.utensils ?? [],),
UtensilsListWidget(
selectedUtensil: controller.selectedUtensil,
utensils: controller.recipe.value?.utensils ?? [],),
const RecipeDetailDividerWidget(
title: 'Langkah-langkah',
),
@ -89,13 +92,6 @@ class RecipeDetailView extends BaseView<RecipeDetailViewModel> {
const SizedBox(
height: 24,
),
BasicButton(
onPress: () {},
bgColor: AppColors.copper,
text: 'Mulai Memasak'),
const SizedBox(
height: 24,
),
],
),
),

View File

@ -1,5 +1,6 @@
import 'package:get/get.dart';
import '../../../data/remote/models/ingredient_model.dart';
import '../../../domain/entities/recipe.dart';
import '../../../domain/use_case/general/recipe_use_case.dart';
import '../../../resources/arguments/argument_constants.dart';
@ -8,7 +9,10 @@ import '../../base/base_view_model.dart';
class RecipeDetailViewModel extends BaseViewModel {
final _arguments = Get.arguments;
String get recipeUuid => _arguments[ArgumentConstants.recipeUuid];
List<String> get selectedUtensil => _arguments[ArgumentConstants.selectedUtensil];
List<Ingredient> get selectedIngredient => _arguments[ArgumentConstants.selectedIngredient];
List<String> selectedIngredientName = [];
final RecipeUseCase _recipeUseCase = RecipeUseCase();
final Rxn<Recipe> recipe = Rxn<Recipe>();
@ -16,6 +20,9 @@ class RecipeDetailViewModel extends BaseViewModel {
void onInit() {
super.onInit();
_fetchRecipeDetail();
selectedIngredient.forEach((element) {
selectedIngredientName.add((element.name ?? '').toLowerCase());
});
}
Future<void> _fetchRecipeDetail() async {

View File

@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import 'package:tutorial_coach_mark/tutorial_coach_mark.dart';
List<TargetFocus> createDetectionIdleTutorialTargets({
required GlobalKey keyBottomNavigation1,
}) {
List<TargetFocus> targets = [];
targets.add(
TargetFocus(
identify: "keyBottomNavigation1",
keyTarget: keyBottomNavigation1,
color: Colors.black38,
alignSkip: Alignment.topRight,
contents: [
TargetContent(
align: ContentAlign.custom,
customPosition: CustomTargetContentPosition(
top: 56,
left: 0,
right: 0,
),
builder: (context, controller) {
return const Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Ambil Gambar",
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold
),
),
SizedBox(height: 8,),
Text(
"Tekan tombol berikut untuk mengambil foto bahan makanan dari kamera ataupun galeri",
style: TextStyle(
color: Colors.white,
fontSize: 18,
),
),
],
);
},
),
],
),
);
return targets;
}

View File

@ -37,6 +37,7 @@ class RecipeDetectionView extends BaseView<RecipeDetectionViewModel> {
@override
Widget body(BuildContext context) {
controller.getPageContext(context);
return Obx(() {
if (controller.isShowDetectionResult.value) {
return _detection(context);
@ -69,7 +70,10 @@ class RecipeDetectionView extends BaseView<RecipeDetectionViewModel> {
),
const SizedBox(height: 16),
BasicButton(
onPress: controller.pickImage, height: 42, text: "Ambil Gambar")
key: controller.buttonKey,
onPress: controller.pickImage,
height: 42,
text: "Ambil Gambar")
],
),
));
@ -86,11 +90,14 @@ class RecipeDetectionView extends BaseView<RecipeDetectionViewModel> {
Widget _detection(BuildContext context) {
return Obx(() {
final bboxesColors = List<Color>.generate(
6,
(_) =>
Color((Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0),
);
final bboxesColors = [
Colors.green,
Colors.blue,
Colors.redAccent,
Colors.purpleAccent,
Colors.amberAccent,
Colors.tealAccent
];
final double displayWidth = MediaQuery.of(context).size.width;
@ -108,7 +115,6 @@ class RecipeDetectionView extends BaseView<RecipeDetectionViewModel> {
for (int i = 0; i < controller.bboxes.length; i++) {
final box = controller.bboxes[i];
final boxClass = controller.classes[i];
print("boxClass ${boxClass}");
bboxesWidgets.add(
Bbox(
box[0] * resizeFactor,
@ -127,7 +133,8 @@ class RecipeDetectionView extends BaseView<RecipeDetectionViewModel> {
child: Center(
child: Stack(
children: [
if (controller.imageFile.value != null) Image.file(controller.imageFile.value!),
if (controller.imageFile.value != null)
Image.file(controller.imageFile.value!),
...bboxesWidgets,
],
),

View File

@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@ -9,15 +10,20 @@ import 'package:snap_and_cook_mobile/data/remote/models/ingredient_model.dart';
import 'package:snap_and_cook_mobile/resources/arguments/argument_constants.dart';
import 'package:snap_and_cook_mobile/routes/routes/main_route.dart';
import 'package:snap_and_cook_mobile/utils/detection/labels.dart';
import 'package:tutorial_coach_mark/tutorial_coach_mark.dart';
import '../../../components/camera/custom_camera.dart';
import '../../../domain/use_case/utensils/utensil_use_case.dart';
import '../../../resources/constants/session_constants.dart';
import '../../../utils/detection/yolo.dart';
import '../../../utils/session/session.dart';
import '../../base/base_view_model.dart';
import '../components/tutorial_detection_idle_items.dart';
class RecipeDetectionViewModel extends BaseViewModel {
RxList<Map<String, dynamic>> modelResults = RxList();
final _utensilUseCase = UtensilUseCase();
final buttonKey = GlobalKey();
Rxn<File> imageFile = Rxn<File>();
@ -35,8 +41,8 @@ class RecipeDetectionViewModel extends BaseViewModel {
double maxImageWidgetHeight = 400;
double confidenceThreshold = 0.25;
double iouThreshold = 0.40;
double confidenceThreshold = 0.3;
double iouThreshold = 0.4;
RxList<List<double>> inferenceOutput = RxList();
RxList<int> classes = RxList();
@ -49,16 +55,62 @@ class RecipeDetectionViewModel extends BaseViewModel {
DraggableScrollableController();
final YoloModel model = YoloModel(
'assets/yolov8s_snapcook.tflite',
'assets/yolov8m_snapcook.tflite',
inModelWidth,
inModelHeight,
numClasses,
);
late TutorialCoachMark tutorialCoachMark;
BuildContext? pageContext;
@override
void onInit() {
super.onInit();
_loadMachineLearningModel();
createTutorial();
showTutorial();
}
void getPageContext(BuildContext context){
pageContext = context;
}
void createTutorial() {
tutorialCoachMark = TutorialCoachMark(
targets: createDetectionIdleTutorialTargets(
keyBottomNavigation1: buttonKey,),
colorShadow: Colors.black38,
textSkip: "SKIP",
paddingFocus: 10,
opacityShadow: 0.5,
imageFilter: ImageFilter.blur(sigmaX: 8, sigmaY: 8),
onFinish: () {
print("finish");
Session.set(SessionConstants.isAlreadyOnBoardingDetectIngredient, "yes");
},
onClickTarget: (target) {
},
onClickTargetWithTapPosition: (target, tapDetails) {},
onClickOverlay: (target) {},
onSkip: () {
Session.set(SessionConstants.isAlreadyOnBoardingDetectIngredient, "yes");
return true;
},
);
}
Future<void> showTutorial() async {
Future.delayed(const Duration(seconds: 1));
String? isOnBoarded = await Session.get(SessionConstants.isAlreadyOnBoardingDetectIngredient);
if (isOnBoarded != null) {
return;
}
if (pageContext?.mounted ?? false){
tutorialCoachMark.show(context: pageContext!);
}
}
Future<void> _loadMachineLearningModel() async {

View File

@ -48,7 +48,11 @@ class RecipeDetectionResultViewModel extends BaseViewModel {
void navigateToRecipeDetail(String uuid) {
Get.toNamed(MainRoute.detail,
arguments: {ArgumentConstants.recipeUuid: uuid});
arguments: {
ArgumentConstants.recipeUuid: uuid,
ArgumentConstants.selectedIngredient: ingredients,
ArgumentConstants.selectedUtensil: selectedUtensil.value
});
}
@override

View File

@ -0,0 +1,94 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:snap_and_cook_mobile/styles/colors.dart';
import 'package:tutorial_coach_mark/tutorial_coach_mark.dart';
List<TargetFocus> createUtensilTutorialTargets({
required GlobalKey keyBottomNavigation1,
}) {
List<TargetFocus> targets = [];
targets.add(
TargetFocus(
identify: "keyBottomNavigation1",
keyTarget: keyBottomNavigation1,
color: Colors.black38,
alignSkip: Alignment.topRight,
contents: [
TargetContent(
align: ContentAlign.custom,
customPosition: CustomTargetContentPosition(
top: Get.height * 0.25,
left: 0,
right: 0,
),
builder: (context, controller) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Pilih Alat Memasak",
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold
),
),
const SizedBox(height: 8,),
const Text(
"Tekan tombol alat memasak yang kamu miliki, dan tekan lagi untuk membatalkan pilihan",
style: TextStyle(
color: Colors.white,
fontSize: 18,
),
),
const SizedBox(height: 16,),
Row(
children: [
Container(
color: AppColors.copper,
height: 22,
width: 22,
),
const SizedBox(width: 8,),
const Expanded(
child: Text(
"Warna oranye berarti kamu telah memilih dan miliki",
style: TextStyle(
color: Colors.white,
fontSize: 18,
),
),
),
],
),
const SizedBox(height: 8,),
Row(
children: [
Container(
height: 22,
width: 22,
color: AppColors.heroWhite,
),
const SizedBox(width: 8,),
const Expanded(
child: Text(
"Warna putih berarti tidak kamu pilih",
style: TextStyle(
color: Colors.white,
fontSize: 18,
),
),
),
],
),
],
);
},
),
],
),
);
return targets;
}

View File

@ -21,18 +21,31 @@ class UtensilView extends BaseView<UtensilViewModel> {
@override
Widget body(BuildContext context) {
controller.getPageContext(context);
return Obx(
() => Wrap(
() => Stack(
children: [
for (int i = 0; i < controller.utensils.length; i++)
GestureDetector(
onTap: () {
controller.onSelectUtensil(controller.utensils[i], i);
},
child: UtensilItem(
name: controller.utensils[i].name ?? '',
isSelected: controller.utensils[i].isSelected == 1),
)
Wrap(
children: [
for (int i = 0; i < controller.utensils.length; i++)
GestureDetector(
onTap: () {
controller.onSelectUtensil(controller.utensils[i], i);
},
child: UtensilItem(
name: controller.utensils[i].name ?? '',
isSelected: controller.utensils[i].isSelected == 1),
)
],
),
Padding(
padding: const EdgeInsets.all(16.0),
child: SizedBox(
key: controller.buttonKey,
height: 20,
width: 60,
),
)
],
),
);

View File

@ -1,20 +1,75 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:snap_and_cook_mobile/domain/use_case/utensils/utensil_use_case.dart';
import 'package:tutorial_coach_mark/tutorial_coach_mark.dart';
import '../../../data/remote/models/utensil_model.dart';
import '../../../resources/constants/session_constants.dart';
import '../../../utils/session/session.dart';
import '../../base/base_view_model.dart';
import '../components/tutorial_utensils_items.dart';
class UtensilViewModel extends BaseViewModel {
final _useCase = UtensilUseCase();
RxList<Utensil> utensils = RxList();
final buttonKey = GlobalKey();
late TutorialCoachMark tutorialCoachMark;
BuildContext? pageContext;
@override
void onInit() {
super.onInit();
_fetchUtensils();
createTutorial();
showTutorial();
}
void getPageContext(BuildContext context){
pageContext = context;
}
void createTutorial() {
tutorialCoachMark = TutorialCoachMark(
targets: createUtensilTutorialTargets(
keyBottomNavigation1: buttonKey,),
colorShadow: Colors.black38,
textSkip: "SKIP",
paddingFocus: 10,
opacityShadow: 0.5,
imageFilter: ImageFilter.blur(sigmaX: 8, sigmaY: 8),
onFinish: () {
print("finish");
Session.set(SessionConstants.isAlreadyOnBoardingUtensil, "yes");
},
onClickTarget: (target) {
},
onClickTargetWithTapPosition: (target, tapDetails) {},
onClickOverlay: (target) {},
onSkip: () {
Session.set(SessionConstants.isAlreadyOnBoardingUtensil, "yes");
return true;
},
);
}
Future<void> showTutorial() async {
Future.delayed(const Duration(seconds: 1));
String? isOnBoarded = await Session.get(SessionConstants.isAlreadyOnBoardingUtensil);
if (isOnBoarded != null) {
return;
}
if (pageContext?.mounted ?? false){
tutorialCoachMark.show(context: pageContext!);
}
}
Future<void> _fetchUtensils() async {
showLoadingContainer();
utensils.value = await _useCase.fetchUtensils();

View File

@ -2,6 +2,8 @@ class ArgumentConstants {
static const String receivedFile = "received_file_args";
static const String ingredients = "ingredients_args";
static const String recipeUuid = "recipe_uuid_args";
static const String selectedUtensil = "selected_utensil_args";
static const String selectedIngredient = "selected_ingredient_args";
static const String search = "search_args";
}

View File

@ -1,4 +1,8 @@
class SessionConstants {
static const String token = "session_token";
static const String isAlreadyOnBoarding = "session_is_already_on_boarding";
static const String isAlreadyOnBoardingHome =
"session_is_already_on_boarding_home";
static const String isAlreadyOnBoardingDetectIngredient =
"session_is_already_on_boarding_detect_ingredient";
static const String isAlreadyOnBoardingUtensil =
"session_is_already_on_boarding_utensil";
}

View File

@ -21,10 +21,10 @@ packages:
dependency: transitive
description:
name: archive
sha256: ecf4273855368121b1caed0d10d4513c7241dfc813f7d3c8933b36622ae9b265
sha256: "6bd38d335f0954f5fad9c79e614604fbf03a0e5b975923dd001b6ea965ef5b4b"
url: "https://pub.dev"
source: hosted
version: "3.5.1"
version: "3.6.0"
args:
dependency: transitive
description:
@ -415,10 +415,10 @@ packages:
dependency: "direct main"
description:
name: flutter_screenutil
sha256: "8cf100b8e4973dc570b6415a2090b0bfaa8756ad333db46939efc3e774ee100d"
sha256: b372c35a772a1dc84142a3b9c5ee89a390834bd258e5e6a450d9b975b985d1c9
url: "https://pub.dev"
source: hosted
version: "5.9.0"
version: "5.9.1"
flutter_svg:
dependency: "direct main"
description:
@ -505,10 +505,10 @@ packages:
dependency: transitive
description:
name: image
sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e"
sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8"
url: "https://pub.dev"
source: hosted
version: "4.1.7"
version: "4.2.0"
image_picker:
dependency: "direct main"
description:
@ -1146,6 +1146,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.2"
tutorial_coach_mark:
dependency: "direct main"
description:
name: tutorial_coach_mark
sha256: "1f1fd234790afb929dec7391a4d90aa54ffe8c8e4d278d9283df8e3f5ac5d63e"
url: "https://pub.dev"
source: hosted
version: "1.2.11"
typed_data:
dependency: transitive
description:

View File

@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+5
version: 1.0.0+6
environment:
sdk: '>=3.0.2 <4.0.0'
@ -55,6 +55,7 @@ dependencies:
chucker_flutter:
sqflite: ^2.2.8+4
tflite_flutter: ^0.10.4
tutorial_coach_mark: ^1.2.11
dev_dependencies:
flutter_test: