diff --git a/build-app.sh b/build-app.sh new file mode 100644 index 0000000..424abcb --- /dev/null +++ b/build-app.sh @@ -0,0 +1,114 @@ +#!/bin/bash +# Example: bash build-app.sh -b=apk -e=staging/.env --add-args=--release --add-args=--target=lib/main_staging.dart +# Example prod: bash build-app.sh -b=apk -e=production/.env --add-args=--release --add-args=--target=lib/main_production.dart +# Example aab: bash build-app.sh -b=appbundle -e=production/.env --add-args=--release --add-args=--target=lib/main_production.dart + +# Example get Env : String.fromEnvironment, bool.fromEnvironment, int.fromEnvironment, and double.fromEnvironment + +# --build-number +# On Android it is used as "versionCode". +# On Xcode builds it is used as "CFBundleVersion". + +#--obfuscate --split-debug-info=./debug_symbols + +# --build-name +# On Android it is used as "versionName". +# On Xcode builds it is used as "CFBundleShortVersionString". + +# for window run on Git Bash/ Bash For Window + +# Read user input for a specific argument +read_argument() { + read -p "Enter $1: " input + echo "$input" +} + +# Parse command-line arguments +while [[ $# -gt 0 ]]; do + case "$1" in + + -e=*|--env=*) + env_file="${1#*=}" + shift + ;; + -b=*|--bundle=*) + bundle="${1#*=}" + shift + ;; + --add-args=*) + add_args+=" ${1#*=}" + shift + ;; + *) + echo "Invalid argument: $1" + exit 1 + ;; + esac +done + +# Validation Environment +if [[ -z "$env_file" ]]; then + env_file=$(read_argument "env file path") +fi + + +# Validation Bundle +if [[ -z "$bundle" ]]; then + bundle=$(read_argument "bundle (aar, apk, appbundle, ios, ipa)") +fi + +if [[ "$bundle" != "aar" && "$bundle" != "apk" && "$bundle" != "appbundle" && "$bundle" != "ios" && "$bundle" != "ipa" ]]; then + echo "Invalid bundle option: $bundle" + exit 1 +fi + +# Generate dart define +generate_dart_define_args() { + env_file="$1" + dart_define_args="" + while IFS= read -r line; do + # Ignore lines starting with '#' and lines with only whitespace + if [[ ! -z "$line" && "$line" != "#"* && ! "$line" =~ ^[[:space:]]*$ ]]; then + key=$(echo "$line" | cut -d '=' -f 1) + value=$(echo "$line" | cut -d '=' -f 2-) + dart_define_args+="--dart-define=$key=$value " + fi + done < "$env_file" + echo "$dart_define_args" +} + +dart_define_args=$(generate_dart_define_args "$env_file") +dart_define_args+="$add_args" + +echo "Arguments:" +echo "> Platform: $platform" +echo "> Env: $env_file" +echo "> Build: $build_type" +echo "" + +echo "Running flutter clean" + +flutter clean +echo "" + +# Remove pubspec.lock if it exists +if [ -f "pubspec.lock" ]; then + echo "Removing pubspec.lock" + echo "" + rm pubspec.lock +fi + +echo "Running flutter pub get" +flutter pub get +echo "" + +# Add obfuscation for APK and AAB +if [[ "$bundle" == "apk" || "$bundle" == "appbundle" ]]; then + dart_define_args+=" --obfuscate --split-debug-info=./debug_symbols " +fi + +echo "Running flutter build $bundle $dart_define_args" +echo "" +flutter build $bundle $dart_define_args + +echo "Flutter build $bundle finished" diff --git a/lib/data/remote/models/detection_model.g.dart b/lib/data/remote/models/detection_model.g.dart new file mode 100644 index 0000000..d3d552d --- /dev/null +++ b/lib/data/remote/models/detection_model.g.dart @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'detection_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +DetectionModel _$DetectionModelFromJson(Map json) => + DetectionModel( + finalClasses: (json['finalClasses'] as List) + .map((e) => (e as num).toInt()) + .toList(), + finalBboxes: (json['finalBboxes'] as List) + .map((e) => + (e as List).map((e) => (e as num).toDouble()).toList()) + .toList(), + finalScores: (json['finalScores'] as List) + .map((e) => (e as num).toDouble()) + .toList(), + ); + +Map _$DetectionModelToJson(DetectionModel instance) => + { + 'finalClasses': instance.finalClasses, + 'finalBboxes': instance.finalBboxes, + 'finalScores': instance.finalScores, + }; diff --git a/lib/presentation/recipe_detection/components/detection_result_widget.dart b/lib/presentation/recipe_detection/components/detection_result_widget.dart index fe219a2..fa45777 100644 --- a/lib/presentation/recipe_detection/components/detection_result_widget.dart +++ b/lib/presentation/recipe_detection/components/detection_result_widget.dart @@ -44,6 +44,10 @@ class DetectionResultWidget extends GetView { padding: const EdgeInsets.all(16.0), child: Text('Bahan yang terdeteksi:', style: TTCommonsTextStyles.textMd.textBold(),), ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Obx(() => Text(this.controller.detectionTime.value, style: TTCommonsTextStyles.textMd.textBold(),)), + ), Obx(() => ListView.builder( itemCount: this.controller.detectedIngredients.length, shrinkWrap: true, diff --git a/lib/presentation/recipe_detection/view/recipe_detection_view.dart b/lib/presentation/recipe_detection/view/recipe_detection_view.dart index 555e0ff..05a07a8 100644 --- a/lib/presentation/recipe_detection/view/recipe_detection_view.dart +++ b/lib/presentation/recipe_detection/view/recipe_detection_view.dart @@ -108,6 +108,7 @@ class RecipeDetectionView extends BaseView { 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, diff --git a/lib/presentation/recipe_detection/view_model/recipe_detection_view_model.dart b/lib/presentation/recipe_detection/view_model/recipe_detection_view_model.dart index 2a6784b..50b6c74 100644 --- a/lib/presentation/recipe_detection/view_model/recipe_detection_view_model.dart +++ b/lib/presentation/recipe_detection/view_model/recipe_detection_view_model.dart @@ -11,11 +11,15 @@ import 'package:snap_and_cook_mobile/routes/routes/main_route.dart'; import 'package:snap_and_cook_mobile/utils/detection/labels.dart'; import '../../../components/camera/custom_camera.dart'; +import '../../../domain/use_case/utensils/utensil_use_case.dart'; import '../../../utils/detection/yolo.dart'; import '../../base/base_view_model.dart'; class RecipeDetectionViewModel extends BaseViewModel { RxList> modelResults = RxList(); + final _utensilUseCase = UtensilUseCase(); + + Rxn imageFile = Rxn(); RxnInt imageHeight = RxnInt(); RxnInt imageWidth = RxnInt(); @@ -31,23 +35,21 @@ class RecipeDetectionViewModel extends BaseViewModel { double maxImageWidgetHeight = 400; - double confidenceThreshold = 0.20; - double iouThreshold = 0.1; + double confidenceThreshold = 0.25; + double iouThreshold = 0.40; RxList> inferenceOutput = RxList(); RxList classes = RxList(); RxList> bboxes = RxList(); RxList scores = RxList(); - - // int? imageWidth; - // int? imageHeight; + RxString detectionTime = RxString(""); Rxn imageBytes = Rxn(); DraggableScrollableController draggableScrollableController = DraggableScrollableController(); final YoloModel model = YoloModel( - 'assets/yolov8.tflite', + 'assets/yolov8s_snapcook.tflite', inModelWidth, inModelHeight, numClasses, @@ -71,6 +73,11 @@ class RecipeDetectionViewModel extends BaseViewModel { scores.clear(); } + Future _isUtensilEmpty() async { + var utensils = await _utensilUseCase.fetchSelectedUtensils(); + return utensils.isEmpty; + } + Future updatePostProcess() async { detectedIngredients.clear(); if (inferenceOutput.isEmpty) { @@ -128,6 +135,12 @@ class RecipeDetectionViewModel extends BaseViewModel { } Future pickImage() async { + var isEmpty = await _isUtensilEmpty(); + if (isEmpty){ + Get.snackbar("Peringatan", "Kamu belum memilih alat memasak"); + return; + } + File? data = await Navigator.of(Get.context!).push( MaterialPageRoute( builder: (BuildContext context) => @@ -153,7 +166,10 @@ class RecipeDetectionViewModel extends BaseViewModel { imageHeight.value = image.height; imageWidth.value = image.width; + int predictionTimeStart = DateTime.now().millisecondsSinceEpoch; + inferenceOutput.value = await model.inferenceImage(image); + detectionTime.value = "Prediction time: ${DateTime.now().millisecondsSinceEpoch - predictionTimeStart} ms"; closeLoadingDialog(); updatePostProcess(); @@ -227,6 +243,8 @@ class RecipeDetectionViewModel extends BaseViewModel { void removeIngredient(int index) { detectedIngredients.removeAt(index); detectedIngredients.refresh(); + + } // Future drawOnImage(List> modelResults) async { diff --git a/lib/utils/detection/nms.dart b/lib/utils/detection/nms.dart index 7c6b958..016eed8 100644 --- a/lib/utils/detection/nms.dart +++ b/lib/utils/detection/nms.dart @@ -1,7 +1,7 @@ import 'dart:math'; (List, List>, List) nms(List> rawOutput, - {double confidenceThreshold = 0.7, double iouThreshold = 0.4}) { + {double confidenceThreshold = 0.25, double iouThreshold = 0.4}) { List bestClasses = []; List bestScores = []; diff --git a/lib/utils/detection/yolo.dart b/lib/utils/detection/yolo.dart index 8b2234a..1d495cb 100644 --- a/lib/utils/detection/yolo.dart +++ b/lib/utils/detection/yolo.dart @@ -75,8 +75,8 @@ class YoloModel { List> unfilteredBboxes, int imageWidth, int imageHeight, { - double confidenceThreshold = 0.7, - double iouThreshold = 0.1, + double confidenceThreshold = 0.25, + double iouThreshold = 0.4, }) async { List classes; List> bboxes; @@ -97,17 +97,4 @@ class YoloModel { } return (classes, bboxes, scores); } - - // (List, List>, List) inferAndPostprocess( - // Image image, { - // double confidenceThreshold = 0.7, - // double iouThreshold = 0.1, - // }) => - // postprocess( - // infer(image), - // image.width, - // image.height, - // confidenceThreshold: confidenceThreshold, - // iouThreshold: iouThreshold, - // ); }