Finishing apps

This commit is contained in:
ibnubatutah 2024-05-13 12:40:17 +07:00
parent 14110d1a55
commit ca9f74290c
58 changed files with 794 additions and 465 deletions

View File

@ -25,9 +25,16 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
namespace "com.ibnu.snap_and_cook_mobile"
compileSdkVersion flutter.compileSdkVersion
compileSdkVersion 34
ndkVersion flutter.ndkVersion
compileOptions {
@ -53,17 +60,23 @@ android {
applicationId "com.ibnu.snap_and_cook_mobile"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion 23
targetSdkVersion 33
minSdkVersion 26
targetSdkVersion 34
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
signingConfig signingConfigs.release
}
}
}

View File

@ -1,8 +1,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:label="snap_and_cook_mobile"
android:label="SnapAndCook"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
android:icon="@mipmap/launcher_icon">
<activity
android:name=".MainActivity"
android:exported="true"

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

0
assets/.gitkeep Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -1,4 +1,6 @@
Telur
Ayam
Kentang
Pisang
Telur
Tomat
Wortel

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 587 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

After

Width:  |  Height:  |  Size: 856 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '../../main.dart';
import '../../routes/routes.dart';
import '../../routes/routes/main_route.dart';
import '../../styles/themes.dart';

View File

@ -11,8 +11,6 @@ class UtensilItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: 32,
width: 108,
margin: const EdgeInsets.all(8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(26),
@ -22,7 +20,8 @@ class UtensilItem extends StatelessWidget {
),
color: isSelected ? AppColors.copper : Colors.white
),
child: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
child: Text(
name,
style: TTCommonsTextStyles.textMd

View File

@ -1,24 +1,18 @@
import 'package:flutter_dotenv/flutter_dotenv.dart';
import '../data/enums/environment_enum.dart';
import '../resources/constants/environtment_constant.dart';
import 'app_build_config.dart';
class AppEnvironment {
static load() async {
if (AppBuildConfig.instance.config == BuildConfigEnum.production) {
await dotenv.load(fileName: "production/.env");
} else {
await dotenv.load(fileName: "staging/.env");
}
}
static Map<String, String> get env => dotenv.env;
static String get apiUrl => dotenv.env[EnvironmentConstant.baseUrl] ?? '';
static String get imageUrl => dotenv.env[EnvironmentConstant.imageUrl] ?? '';
static Map<String, String> get env => <String, String>{
EnvironmentConstant.baseUrl: const String.fromEnvironment(
EnvironmentConstant.baseUrl,
defaultValue: ""),
EnvironmentConstant.imageUrl: const String.fromEnvironment(
EnvironmentConstant.imageUrl,
defaultValue: ""),
};
static String get apiUrl => env[EnvironmentConstant.baseUrl] ?? '';
static String get imageUrl => env[EnvironmentConstant.imageUrl] ?? '';
}

View File

@ -22,6 +22,12 @@ class UtensilContract {
print('All Data saved!');
}
Future<void> deleteAllUtensil() async {
final Database db = await _databaseHelper.database;
await db.delete(DatabaseConstant.utensilsTable);
print('All Data deleted!');
}
Future<void> updateUtensil(Utensil utensil) async {
final Database db = await _databaseHelper.database;
await db.update(DatabaseConstant.utensilsTable, utensil.toJson(),

View File

@ -0,0 +1,18 @@
import 'package:dio/dio.dart' hide Headers;
import 'package:retrofit/retrofit.dart';
import 'package:snap_and_cook_mobile/data/remote/responses/base_response.dart';
import '../../../resources/services/recipe_service_constant.dart';
import '../models/utensil_model.dart';
part 'utensil_service.g.dart';
@RestApi()
abstract class UtensilServices {
factory UtensilServices(Dio dio) = _UtensilServices;
@GET(RecipeServiceConstants.utensils)
Future<BaseResponse<List<Utensil>>> getAllUtensil(
@CancelRequest() CancelToken cancelToken,
);
}

View File

@ -0,0 +1,62 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'utensil_service.dart';
// **************************************************************************
// RetrofitGenerator
// **************************************************************************
// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers
class _UtensilServices implements UtensilServices {
_UtensilServices(
this._dio, {
this.baseUrl,
});
final Dio _dio;
String? baseUrl;
@override
Future<BaseResponse<List<Utensil>>> getAllUtensil(cancelToken) async {
const _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final _data = <String, dynamic>{};
final _result = await _dio.fetch<Map<String, dynamic>>(
_setStreamType<BaseResponse<List<Utensil>>>(Options(
method: 'GET',
headers: _headers,
extra: _extra,
)
.compose(
_dio.options,
'utensils',
queryParameters: queryParameters,
data: _data,
cancelToken: cancelToken,
)
.copyWith(baseUrl: baseUrl ?? _dio.options.baseUrl)));
final value = BaseResponse<List<Utensil>>.fromJson(
_result.data!,
(json) => (json as List<dynamic>)
.map<Utensil>((i) => Utensil.fromJson(i as Map<String, dynamic>))
.toList(),
);
return value;
}
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
if (T != dynamic &&
!(requestOptions.responseType == ResponseType.bytes ||
requestOptions.responseType == ResponseType.stream)) {
if (T == String) {
requestOptions.responseType = ResponseType.plain;
} else {
requestOptions.responseType = ResponseType.json;
}
}
return requestOptions;
}
}

View File

@ -5,6 +5,5 @@ abstract class UtensilInterface {
Future<List<String>> fetchSelectedUtensils();
Future<void> updateUtensil(Utensil utensil);
}

View File

@ -1,4 +1,7 @@
import 'package:dio/dio.dart';
import 'package:get/get.dart';
import 'package:snap_and_cook_mobile/data/local/utensils_contract.dart';
import 'package:snap_and_cook_mobile/data/remote/services/utensil_service.dart';
import 'package:snap_and_cook_mobile/domain/use_case/utensils/utensil_resource.dart';
import '../../../data/remote/models/utensil_model.dart';
@ -6,16 +9,25 @@ import 'utensil_interface.dart';
class UtensilUseCase implements UtensilInterface {
final _dbContract = UtensilContract();
final _utensilService = Get.find<UtensilServices>();
@override
Future<List<Utensil>> fetchUtensils() async {
List<Utensil> utensils = await _dbContract.getUtensils();
if (utensils.isEmpty){
_dbContract.insertAllUtensil(utensilResource);
return utensilResource;
}
try{
var response = await _utensilService.getAllUtensil(CancelToken());
List<Utensil> utensils = await _dbContract.getUtensils();
return utensils;
if (utensils.length == (response.data?.length ?? 0)){
return utensils;
}
await _dbContract.deleteAllUtensil();
await _dbContract.insertAllUtensil(response.data ?? []);
utensils = await _dbContract.getUtensils();
return utensils;
} catch (e){
return [];
}
}
@override

View File

@ -2,6 +2,7 @@ import 'package:dio/dio.dart';
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/data/remote/services/utensil_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';
@ -13,7 +14,7 @@ import 'utils/session/session.dart';
Future<void> init() async {
WidgetsFlutterBinding.ensureInitialized();
await AppEnvironment.load();
AppEnvironment();
await Get.putAsync<Dio>(
() async => Dio()
@ -29,5 +30,6 @@ Future<void> init() async {
await Get.putAsync(() async => Session());
await Get.putAsync(() async => RecipeServices(Get.find<Dio>()));
await Get.putAsync(() async => UtensilServices(Get.find<Dio>()));
runApp(const RootRestorationScope(restorationId: 'root', child: App()));
}

View File

@ -1,212 +0,0 @@
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter_vision/flutter_vision.dart';
import 'package:image_picker/image_picker.dart';
enum Options { none, imagev8 }
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MaterialApp(
home: MyApp(),
debugShowCheckedModeBanner: false,
));
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late FlutterVision vision;
Options option = Options.none;
@override
void initState() {
super.initState();
vision = FlutterVision();
}
@override
void dispose() async {
super.dispose();
await vision.closeYoloModel();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: task(option),
);
}
Widget task(Options option) {
return YoloImageV8(vision: vision);
}
}
class YoloImageV8 extends StatefulWidget {
final FlutterVision vision;
const YoloImageV8({Key? key, required this.vision}) : super(key: key);
@override
State<YoloImageV8> createState() => _YoloImageV8State();
}
class _YoloImageV8State extends State<YoloImageV8> {
late List<Map<String, dynamic>> yoloResults;
File? imageFile;
int imageHeight = 1;
int imageWidth = 1;
bool isLoaded = false;
@override
void initState() {
super.initState();
loadYoloModel().then((value) {
setState(() {
yoloResults = [];
isLoaded = true;
});
});
}
@override
void dispose() async {
super.dispose();
}
@override
Widget build(BuildContext context) {
final Size size = MediaQuery.of(context).size;
if (!isLoaded) {
return const Scaffold(
body: Center(
child: Text("Model not loaded, waiting for it"),
),
);
}
return Scaffold(
appBar: AppBar(
title: const Text("Yolo V8"),
),
body: Column(
children: [
Expanded(child: imageFile != null ? Image.file(imageFile!) : const SizedBox()),
Stack(
fit: StackFit.expand,
children: [
Align(
alignment: Alignment.bottomCenter,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
onPressed: pickImage,
child: const Text("ambil gmbr serah"),
),
// ElevatedButton(
// onPressed: yoloOnImage,
// child: const Text("Ya test"),
// )
],
),
),
],
),
...displayBoxesAroundRecognizedObjects(size),
],
),
);
}
Future<void> loadYoloModel() async {
await widget.vision.loadYoloModel(
labels: 'assets/labels.txt',
modelPath: 'assets/yolov8m_float16.tflite',
modelVersion: "yolov8",
quantization: false,
numThreads: 2,
useGpu: true,
);
setState(() {
isLoaded = true;
});
}
Future<void> pickImage() async {
final ImagePicker picker = ImagePicker();
final XFile? photo = await picker.pickImage(source: ImageSource.gallery);
if (photo != null) {
setState(() {
imageFile = File(photo.path);
yoloOnImage();
});
}
}
yoloOnImage() async {
yoloResults.clear();
Uint8List byte = await imageFile!.readAsBytes();
final image = await decodeImageFromList(byte);
imageHeight = image.height;
imageWidth = image.width;
final result = await widget.vision.yoloOnImage(
bytesList: byte,
imageHeight: image.height,
imageWidth: image.width,
iouThreshold: 0.8,
confThreshold: 0.2,
classThreshold: 0.3,
);
if (result.isNotEmpty) {
setState(() {
yoloResults = result;
});
}
}
List<Widget> displayBoxesAroundRecognizedObjects(Size screen) {
if (yoloResults.isEmpty) return [];
double factorX = screen.width / (imageWidth);
double imgRatio = imageWidth / imageHeight;
double newWidth = imageWidth * factorX;
double newHeight = newWidth / imgRatio;
double factorY = newHeight / (imageHeight);
double pady = (screen.height - newHeight) / 2;
Color colorPick = const Color.fromARGB(255, 50, 233, 30);
return yoloResults.map((result) {
return Positioned(
left: result["box"][0] * factorX,
top: result["box"][1] * factorY + pady,
width: (result["box"][2] - result["box"][0]) * factorX,
height: (result["box"][3] - result["box"][1]) * factorY,
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(10.0)),
border: Border.all(color: Colors.pink, width: 2.0),
),
child: Text(
"${result['tag']} ${(result['box'][4] * 100).toStringAsFixed(0)}%",
style: TextStyle(
background: Paint()..color = colorPick,
color: Colors.white,
fontSize: 12.0,
),
),
),
);
}).toList();
}
}

View File

@ -1,8 +1,12 @@
import 'dart:io';
import 'configuration/app_build_config.dart';
import 'data/enums/environment_enum.dart';
import 'init.dart';
import 'main_staging.dart';
void main() async{
HttpOverrides.global = MyHttpOverrides();
AppBuildConfig.instantiate(config: BuildConfigEnum.production);
await init();
}

View File

@ -1,8 +1,19 @@
import 'dart:io';
import 'configuration/app_build_config.dart';
import 'data/enums/environment_enum.dart';
import 'init.dart';
class MyHttpOverrides extends HttpOverrides{
@override
HttpClient createHttpClient(SecurityContext? context){
return super.createHttpClient(context)
..badCertificateCallback = (X509Certificate cert, String host, int port)=> true;
}
}
void main() async{
HttpOverrides.global = MyHttpOverrides();
AppBuildConfig.instantiate(config: BuildConfigEnum.staging);
await init();
}

View File

@ -9,8 +9,9 @@ class UtensilsListWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SizedBox(
height: 50,
height: 55,
child: ListView.builder(
clipBehavior: Clip.none,
itemBuilder: (context, index) {
return UtensilItem(name: utensils[index], isSelected: false,);
},

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:snap_and_cook_mobile/data/remote/models/ingredient_model.dart';
import 'package:snap_and_cook_mobile/presentation/recipe_detection/view_model/recipe_detection_view_model.dart';
@ -43,9 +44,13 @@ class DetectionItemWidget extends GetView<RecipeDetectionViewModel> {
},
splashColor: Colors.transparent,
icon: const Icon(Icons.add_circle_outline)),
Text(
'${ingredient.quantity}',
style: TTCommonsTextStyles.textMd.textMedium(),
SizedBox(
width: 20.w,
child: Text(
'${ingredient.quantity?.toInt()}',
textAlign: TextAlign.center,
style: TTCommonsTextStyles.textMd.textMedium(),
),
),
IconButton(
onPressed: () {

View File

@ -67,7 +67,7 @@ class DetectionResultWidget extends GetView<RecipeDetectionViewModel> {
isLeading: true,
),
),
SizedBox(width: 8),
const SizedBox(width: 8),
Expanded(
child: BasicButton(
onPress: this.controller.navigateToRecipeDetectionResult,

View File

@ -1,3 +1,5 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:snap_and_cook_mobile/components/basic_button.dart';
@ -5,6 +7,8 @@ import 'package:snap_and_cook_mobile/styles/colors.dart';
import 'package:snap_and_cook_mobile/styles/text_styles/tt_commons_text_styles.dart';
import '../../../components/appbar/basic_appbar.dart';
import '../../../utils/detection/bbox.dart';
import '../../../utils/detection/labels.dart';
import '../../base/base_view.dart';
import '../components/detection_result_widget.dart';
import '../view_model/recipe_detection_view_model.dart';
@ -82,12 +86,50 @@ class RecipeDetectionView extends BaseView<RecipeDetectionViewModel> {
Widget _detection(BuildContext context) {
return Obx(() {
if (controller.imageBytes.value != null) {
final bboxesColors = List<Color>.generate(
6,
(_) =>
Color((Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0),
);
final double displayWidth = MediaQuery.of(context).size.width;
double resizeFactor = 1;
if (controller.imageWidth.value != null &&
controller.imageHeight.value != null) {
double k1 = displayWidth / controller.imageWidth.value!;
double k2 =
controller.maxImageWidgetHeight / controller.imageHeight.value!;
resizeFactor = min(k1, k2);
}
List<Bbox> bboxesWidgets = [];
for (int i = 0; i < controller.bboxes.length; i++) {
final box = controller.bboxes[i];
final boxClass = controller.classes[i];
bboxesWidgets.add(
Bbox(
box[0] * resizeFactor,
box[1] * resizeFactor,
box[2] * resizeFactor,
box[3] * resizeFactor,
labels[boxClass],
controller.scores[i],
bboxesColors[boxClass]),
);
}
if (controller.imageFile.value != null) {
return SizedBox(
width: Get.width,
child: Image.memory(
controller.imageBytes.value!,
fit: BoxFit.contain,
height: controller.maxImageWidgetHeight,
child: Center(
child: Stack(
children: [
if (controller.imageFile.value != null) Image.file(controller.imageFile.value!),
...bboxesWidgets,
],
),
),
);
} else {

View File

@ -1,36 +1,58 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_vision/flutter_vision.dart';
import 'package:get/get.dart';
import 'package:image/image.dart' as img;
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 '../../../components/camera/custom_camera.dart';
import '../../../utils/helper/detection_helper.dart';
import '../../../utils/detection/yolo.dart';
import '../../base/base_view_model.dart';
class RecipeDetectionViewModel extends BaseViewModel {
final FlutterVision _vision = FlutterVision();
RxList<Map<String, dynamic>> modelResults = RxList();
Rxn<File> imageFile = Rxn<File>();
RxInt imageHeight = RxInt(1);
RxInt imageWidth = RxInt(1);
RxnInt imageHeight = RxnInt();
RxnInt imageWidth = RxnInt();
RxBool isLoadingModel = RxBool(false);
RxBool isProcessingModel = RxBool(false);
RxBool isShowDetectionResult = RxBool(false);
RxList<Ingredient> detectedIngredients = RxList();
final Stopwatch _stopwatch = Stopwatch();
Timer? _timer;
static const inModelWidth = 640;
static const inModelHeight = 640;
static const numClasses = 6;
double maxImageWidgetHeight = 400;
double confidenceThreshold = 0.20;
double iouThreshold = 0.1;
RxList<List<double>> inferenceOutput = RxList();
RxList<int> classes = RxList();
RxList<List<double>> bboxes = RxList();
RxList<double> scores = RxList();
// int? imageWidth;
// int? imageHeight;
Rxn<Uint8List> imageBytes = Rxn<Uint8List>();
DraggableScrollableController draggableScrollableController =
DraggableScrollableController();
final YoloModel model = YoloModel(
'assets/yolov8.tflite',
inModelWidth,
inModelHeight,
numClasses,
);
@override
void onInit() {
super.onInit();
@ -39,18 +61,70 @@ class RecipeDetectionViewModel extends BaseViewModel {
Future<void> _loadMachineLearningModel() async {
showLoadingContainer();
await _vision.loadYoloModel(
// labels: 'assets/labels.txt',
modelPath: 'assets/yolov8m_float16.tflite',
labels: 'assets/labels.txt',
modelVersion: "yolov8",
quantization: false,
numThreads: 3,
useGpu: true,
);
await model.init();
hideLoadingContainer();
}
Future<void> updatePostProcess() async {
detectedIngredients.clear();
print("MASUK 1");
if (inferenceOutput.isEmpty) {
return;
}
List<int> newClasses = [];
List<List<double>> newBboxes = [];
List<double> newScores = [];
print("MASUK 2");
/// Wait this process with loading
(newClasses, newBboxes, newScores) = await model.postprocess(
inferenceOutput,
imageWidth.value ?? 0,
imageHeight.value ?? 0,
confidenceThreshold: confidenceThreshold,
iouThreshold: iouThreshold,
);
print("MASUK 3");
debugPrint('Detected ${newClasses} classes');
debugPrint('Detected ${newBboxes.length} boxed');
debugPrint('Detected ${newScores.length} scores');
classes.value = newClasses;
bboxes.value = newBboxes;
scores.value = newScores;
hideLoadingContainer();
for (var element in newClasses) {
if (detectedIngredients.isEmpty){
detectedIngredients.add(Ingredient(
name: labels[element],
quantity: 1,
));
} else {
bool isExist = false;
for (int i = 0; i < detectedIngredients.length; i++) {
if (detectedIngredients[i].name == labels[element]) {
detectedIngredients[i].quantity = detectedIngredients[i].quantity! + 1;
isExist = true;
break;
}
}
if (!isExist) {
detectedIngredients.add(Ingredient(
name: labels[element],
quantity: 1,
));
}
}
}
// closeLoadingDialog();
_showDraggableBottomSheet();
}
Future<void> pickImage() async {
File? data = await Navigator.of(Get.context!).push(
MaterialPageRoute<File>(
@ -58,67 +132,72 @@ class RecipeDetectionViewModel extends BaseViewModel {
const CustomCameraWidget(compressionQuality: 80),
),
);
print("MASUK 9");
if (data != null) {
print("MASUK 10");
imageFile.value = data;
_startTimer();
// _startTimer();
await Future.delayed(const Duration(milliseconds: 500));
print("MASUK 11");
_detectIngredients();
}
}
void _startTimer() {
_stopwatch.start();
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
print("${_stopwatch.elapsed.inSeconds} seconds");
});
}
void _detectIngredients() async {
modelResults.clear();
print("MASUK SINI 1");
// showLoadingDialog();
final image = img.decodeImage(await imageFile.value!.readAsBytes())!;
print("MASUK SINI 2");
Uint8List byte = await imageFile.value!.readAsBytes();
final image = await decodeImageFromList(byte);
imageHeight.value = image.height;
imageWidth.value = image.width;
inferenceOutput.value = model.infer(image);
showLoadingDialog();
final result = await _vision.yoloOnImage(
bytesList: byte,
imageHeight: image.height,
imageWidth: image.width,
iouThreshold: 0.2,
confThreshold: 0.2,
classThreshold: 0.2,
);
print("MASUK SINI 3");
updatePostProcess();
print("DATA IS ${result.length}");
if (result.isNotEmpty) {
modelResults.value = result;
imageBytes.value = await drawOnImage(modelResults);
closeLoadingDialog();
_showDraggableBottomSheet();
_timer?.cancel();
_stopwatch.stop();
_stopwatch.reset();
} else {
_timer?.cancel();
_stopwatch.stop();
_stopwatch.reset();
closeLoadingDialog();
showGeneralDialog(context: Get.context!, pageBuilder: (context, anim1, anim2) {
return AlertDialog(
title: const Text("Tidak ada bahan yang terdeteksi"),
content: const Text("Silahkan coba lagi"),
actions: [
TextButton(onPressed: () {
Get.back();
}, child: const Text("OK"))
],
);
});
}
// final result = await _vision.yoloOnImage(
// bytesList: byte,
// imageHeight: image.height,
// imageWidth: image.width,
// iouThreshold: 0.2,
// confThreshold: 0.2,
// classThreshold: 0.2,
// );
// print("DATA IS ${result.length}");
// if (result.isNotEmpty) {
// } else {
// _timer?.cancel();
// _stopwatch.stop();
// _stopwatch.reset();
// closeLoadingDialog();
// showGeneralDialog(context: Get.context!, pageBuilder: (context, anim1, anim2) {
// return AlertDialog(
// title: const Text("Tidak ada bahan yang terdeteksi"),
// content: const Text("Silahkan coba lagi"),
// actions: [
// TextButton(onPressed: () {
// Get.back();
// }, child: const Text("OK"))
// ],
// );
// });
// }
}
final translationDict = {
'carrot': 'Wortel',
};
@ -158,38 +237,38 @@ class RecipeDetectionViewModel extends BaseViewModel {
detectedIngredients.refresh();
}
Future<Uint8List> drawOnImage(List<Map<String, dynamic>> modelResults) async {
final image = imageFile.value;
if (image == null) {
return Uint8List(0);
}
final imgBytes = image.readAsBytesSync();
final img = await decodeImageFromList(Uint8List.fromList(imgBytes));
final recorder = PictureRecorder();
final canvas = Canvas(recorder);
canvas.drawImage(img, Offset.zero, Paint());
List<Ingredient> detectedObject =
drawBoxesOnCanvasAndReturnDetectedIngredient(
canvas: canvas,
screen: Size(img.width.toDouble(), img.height.toDouble()),
modelResults: modelResults,
imageHeight: imageHeight.value,
imageWidth: imageWidth.value,
);
detectedIngredients.value = translateIngredients(detectedObject, translationDict);
// detectedIngredients.value = detectedObject;
final picture = recorder.endRecording();
final imgWithBoxes = await picture.toImage(img.width, img.height);
final ByteData? byteData =
await imgWithBoxes.toByteData(format: ImageByteFormat.png);
return byteData!.buffer.asUint8List();
}
// Future<Uint8List> drawOnImage(List<Map<String, dynamic>> modelResults) async {
// final image = imageFile.value;
// if (image == null) {
// return Uint8List(0);
// }
//
// final imgBytes = image.readAsBytesSync();
// final img = await decodeImageFromList(Uint8List.fromList(imgBytes));
//
// final recorder = PictureRecorder();
// final canvas = Canvas(recorder);
// canvas.drawImage(img, Offset.zero, Paint());
// List<Ingredient> detectedObject =
// drawBoxesOnCanvasAndReturnDetectedIngredient(
// canvas: canvas,
// screen: Size(img.width.toDouble(), img.height.toDouble()),
// modelResults: modelResults,
// imageHeight: imageHeight.value,
// imageWidth: imageWidth.value,
// );
//
// detectedIngredients.value = translateIngredients(detectedObject, translationDict);
//
// // detectedIngredients.value = detectedObject;
//
// final picture = recorder.endRecording();
// final imgWithBoxes = await picture.toImage(img.width, img.height);
// final ByteData? byteData =
// await imgWithBoxes.toByteData(format: ImageByteFormat.png);
//
// return byteData!.buffer.asUint8List();
// }
void _showDraggableBottomSheet() {
isShowDetectionResult.value = true;
@ -209,7 +288,8 @@ class RecipeDetectionViewModel extends BaseViewModel {
@override
void onClose() {
_vision.closeYoloModel();
super.onClose();
}
}

View File

@ -19,7 +19,7 @@ class DetectedIngredientItem extends StatelessWidget {
color: AppColors.copper),
),
child: Text(
'${ingredient.name} ${ingredient.quantity}',
'${ingredient.name}',
style: TTCommonsTextStyles.textMd.textMedium().copyWith(
color: AppColors.copper,
),

View File

@ -22,12 +22,13 @@ class UtensilViewModel extends BaseViewModel {
}
void onSelectUtensil(Utensil utensil, int index){
if (utensil.isSelected == 0){
if (utensil.isSelected == null || utensil.isSelected == 0){
utensil.isSelected = 1;
} else{
utensil.isSelected = 0;
}
utensils[index] = utensil;
utensils.refresh();
_useCase.updateUtensil(utensil);
}

View File

@ -2,4 +2,5 @@ class RecipeServiceConstants {
static const String listRecipe = 'recipes';
static const String detailRecipe = 'recipe/{uuid}';
static const String recipeRecommendation = 'recipe/recommendation';
static const String utensils = 'utensils';
}

View File

@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
class Bbox extends StatelessWidget {
final double x;
final double y;
final double width;
final double height;
final String label;
final double score;
final Color color;
const Bbox(
this.x,
this.y,
this.width,
this.height,
this.label,
this.score,
this.color, {
super.key,
});
@override
Widget build(BuildContext context) {
return Positioned(
top: y - height / 2,
left: x - width / 2,
width: width,
height: height,
child: Container(
decoration: BoxDecoration(
border: Border.all(color: color, width: 3),
borderRadius: const BorderRadius.all(Radius.circular(4)),
),
child: Align(
alignment: Alignment.topLeft,
child: FittedBox(
child: Container(
color: color,
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(label),
Text(' ${(score * 100).toStringAsFixed(0)}%'),
],
),
),
),
),
),
);
}
}

View File

@ -0,0 +1,8 @@
const List<String> labels = [
'Ayam',
'Kentang',
'Pisang',
'Telur',
'Tomat',
'Wortel'
];

View File

@ -0,0 +1,115 @@
import 'dart:math';
(List<int>, List<List<double>>, List<double>) nms(List<List<double>> rawOutput,
{double confidenceThreshold = 0.7, double iouThreshold = 0.4}) {
List<int> bestClasses = [];
List<double> bestScores = [];
List<int> boxesToSave = [];
// Take the argmax to the determine the best classes and scores
for (int i = 0; i < 8400; i++) {
double bestScore = 0;
int bestCls = -1;
for (int j = 4; j < 10; j++) {
double clsScore = rawOutput[j][i];
if (clsScore > bestScore) {
bestScore = clsScore;
bestCls = j - 4;
}
}
if (bestScore > confidenceThreshold) {
bestClasses.add(bestCls);
bestScores.add(bestScore);
boxesToSave.add(i);
}
}
// Get rid of boxes below confidence threshold
List<List<double>> candidateBoxes = [];
for (var index in boxesToSave) {
List<double> savedBox = [];
for (int i = 0; i < 4; i++) {
savedBox.add(rawOutput[i][index]);
}
candidateBoxes.add(savedBox);
}
var sortedBestScores = List.from(bestScores);
sortedBestScores.sort((a, b) => -a.compareTo(b));
List<int> argSortList =
sortedBestScores.map((e) => bestScores.indexOf(e)).toList();
List<int> sortedBestClasses = [];
List<List<double>> sortedCandidateBoxes = [];
for (var index in argSortList) {
sortedBestClasses.add(bestClasses[index]);
sortedCandidateBoxes.add(candidateBoxes[index]);
}
List<List<double>> finalBboxes = [];
List<double> finalScores = [];
List<int> finalClasses = [];
while (sortedCandidateBoxes.isNotEmpty) {
var bbox1xywh = sortedCandidateBoxes.removeAt(0);
finalBboxes.add(bbox1xywh);
var bbox1xyxy = xywh2xyxy(bbox1xywh);
finalScores.add(sortedBestScores.removeAt(0));
var class1 = sortedBestClasses.removeAt(0);
finalClasses.add(class1);
List<int> indexesToRemove = [];
for (int i = 0; i < sortedCandidateBoxes.length; i++) {
if (class1 == sortedBestClasses[i]) {
if (computeIou(bbox1xyxy, xywh2xyxy(sortedCandidateBoxes[i])) >
iouThreshold) {
indexesToRemove.add(i);
}
}
}
for (var index in indexesToRemove.reversed) {
sortedCandidateBoxes.removeAt(index);
sortedBestClasses.removeAt(index);
sortedBestScores.removeAt(index);
}
}
return (finalClasses, finalBboxes, finalScores);
}
List<double> xywh2xyxy(List<double> bbox) {
double halfWidth = bbox[2] / 2;
double halfHeight = bbox[3] / 2;
return [
bbox[0] - halfWidth,
bbox[1] - halfHeight,
bbox[0] + halfWidth,
bbox[1] + halfHeight,
];
}
/// Computes the intersection over union between two bounding boxes encoded with
/// the xyxy format.
double computeIou(List<double> bbox1, List<double> bbox2) {
assert(bbox1[0] < bbox1[2]);
assert(bbox1[1] < bbox1[3]);
assert(bbox2[0] < bbox2[2]);
assert(bbox2[1] < bbox2[3]);
// Determine the coordinate of the intersection rectangle
double xLeft = max(bbox1[0], bbox2[0]);
double yTop = max(bbox1[1], bbox2[1]);
double xRight = min(bbox1[2], bbox2[2]);
double yBottom = min(bbox1[3], bbox2[3]);
if (xRight < xLeft || yBottom < yTop) {
return 0;
}
double intersectionArea = (xRight - xLeft) * (yBottom - yTop);
double bbox1Area = (bbox1[2] - bbox1[0]) * (bbox1[3] - bbox1[1]);
double bbox2Area = (bbox2[2] - bbox2[0]) * (bbox2[3] - bbox2[1]);
double iou = intersectionArea / (bbox1Area + bbox2Area - intersectionArea);
assert(iou >= 0 && iou <= 1);
return iou;
}

View File

@ -0,0 +1,93 @@
import 'package:flutter/foundation.dart';
import 'package:tflite_flutter/tflite_flutter.dart';
import 'package:image/image.dart';
import 'nms.dart';
class YoloModel {
final String modelPath;
final int inWidth;
final int inHeight;
final int numClasses;
Interpreter? _interpreter;
YoloModel(
this.modelPath,
this.inWidth,
this.inHeight,
this.numClasses,
);
Future<void> init() async {
_interpreter = await Interpreter.fromAsset(modelPath);
}
List<List<double>> infer(Image image) {
assert(_interpreter != null, 'The model must be initialized');
final imgResized = copyResize(image, width: inWidth, height: inHeight);
final imgNormalized = List.generate(
inHeight,
(y) => List.generate(
inWidth,
(x) {
final pixel = imgResized.getPixel(x, y);
return [pixel.rNormalized, pixel.gNormalized, pixel.bNormalized];
},
),
);
// output shape:
// 1 : batch size
// 4 + 6: left, top, right, bottom and probabilities for each class
// 8400: num predictions
final output = [
List<List<double>>.filled(4 + numClasses, List<double>.filled(8400, 0))
];
int predictionTimeStart = DateTime.now().millisecondsSinceEpoch;
_interpreter!.run([imgNormalized], output);
debugPrint(
'Prediction time: ${DateTime.now().millisecondsSinceEpoch - predictionTimeStart} ms');
return output[0];
}
Future<(List<int>, List<List<double>>, List<double>)> postprocess(
List<List<double>> unfilteredBboxes,
int imageWidth,
int imageHeight, {
double confidenceThreshold = 0.7,
double iouThreshold = 0.1,
}) async {
List<int> classes;
List<List<double>> bboxes;
List<double> scores;
int nmsTimeStart = DateTime.now().millisecondsSinceEpoch;
(classes, bboxes, scores) = nms(
unfilteredBboxes,
confidenceThreshold: confidenceThreshold,
iouThreshold: iouThreshold,
);
debugPrint(
'NMS time: ${DateTime.now().millisecondsSinceEpoch - nmsTimeStart} ms');
for (var bbox in bboxes) {
bbox[0] *= imageWidth;
bbox[1] *= imageHeight;
bbox[2] *= imageWidth;
bbox[3] *= imageHeight;
}
return (classes, bboxes, scores);
}
// (List<int>, List<List<double>>, List<double>) inferAndPostprocess(
// Image image, {
// double confidenceThreshold = 0.7,
// double iouThreshold = 0.1,
// }) =>
// postprocess(
// infer(image),
// image.width,
// image.height,
// confidenceThreshold: confidenceThreshold,
// iouThreshold: iouThreshold,
// );
}

View File

@ -21,18 +21,18 @@ packages:
dependency: transitive
description:
name: archive
sha256: "7b875fd4a20b165a3084bd2d210439b22ebc653f21cea4842729c0c30c82596b"
sha256: ecf4273855368121b1caed0d10d4513c7241dfc813f7d3c8933b36622ae9b265
url: "https://pub.dev"
source: hosted
version: "3.4.9"
version: "3.5.1"
args:
dependency: transitive
description:
name: args
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
url: "https://pub.dev"
source: hosted
version: "2.4.2"
version: "2.5.0"
async:
dependency: transitive
description:
@ -77,26 +77,26 @@ packages:
dependency: transitive
description:
name: build_resolvers
sha256: "64e12b0521812d1684b1917bc80945625391cb9bdd4312536b1d69dcb6133ed8"
sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
version: "2.4.2"
build_runner:
dependency: "direct dev"
description:
name: build_runner
sha256: "67d591d602906ef9201caf93452495ad1812bea2074f04e25dbd7c133785821b"
sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
url: "https://pub.dev"
source: hosted
version: "2.4.7"
version: "2.4.9"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
sha256: c9e32d21dd6626b5c163d48b037ce906bbe428bc23ab77bcd77bb21e593b6185
sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799"
url: "https://pub.dev"
source: hosted
version: "7.2.11"
version: "7.3.0"
built_collection:
dependency: transitive
description:
@ -109,66 +109,66 @@ packages:
dependency: transitive
description:
name: built_value
sha256: "69acb7007eb2a31dc901512bfe0f7b767168be34cb734835d54c070bfa74c1b2"
sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb
url: "https://pub.dev"
source: hosted
version: "8.8.0"
version: "8.9.2"
cached_network_image:
dependency: "direct main"
description:
name: cached_network_image
sha256: f98972704692ba679db144261172a8e20feb145636c617af0eb4022132a6797f
sha256: "28ea9690a8207179c319965c13cd8df184d5ee721ae2ce60f398ced1219cea1f"
url: "https://pub.dev"
source: hosted
version: "3.3.0"
version: "3.3.1"
cached_network_image_platform_interface:
dependency: transitive
description:
name: cached_network_image_platform_interface
sha256: "56aa42a7a01e3c9db8456d9f3f999931f1e05535b5a424271e9a38cabf066613"
sha256: "9e90e78ae72caa874a323d78fa6301b3fb8fa7ea76a8f96dc5b5bf79f283bf2f"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
version: "4.0.0"
cached_network_image_web:
dependency: transitive
description:
name: cached_network_image_web
sha256: "759b9a9f8f6ccbb66c185df805fac107f05730b1dab9c64626d1008cca532257"
sha256: "205d6a9f1862de34b93184f22b9d2d94586b2f05c581d546695e3d8f6a805cd7"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
version: "1.2.0"
camera:
dependency: "direct main"
description:
name: camera
sha256: "71fb0e03618a8629a912bbf49bb664869bd16830c1d363b1096df99727b0bebb"
sha256: "9499cbc2e51d8eb0beadc158b288380037618ce4e30c9acbc4fae1ac3ecb5797"
url: "https://pub.dev"
source: hosted
version: "0.10.5+6"
version: "0.10.5+9"
camera_android:
dependency: transitive
description:
name: camera_android
sha256: b63304d553dde613ca68fb40aaa76bbbcdbd051683bdbf2019671ec8d9645ce7
sha256: "351429510121d179b9aac5a2e8cb525c3cd6c39f4d709c5f72dfb21726e52371"
url: "https://pub.dev"
source: hosted
version: "0.10.8+14"
version: "0.10.8+16"
camera_avfoundation:
dependency: transitive
description:
name: camera_avfoundation
sha256: "3b6d9f550cfd658c71f34a99509528501e5e5d4fa79f11e3a4d6ef380d8e0254"
sha256: "608b56b0880722f703871329c4d7d4c2f379c8e2936940851df7fc041abc6f51"
url: "https://pub.dev"
source: hosted
version: "0.9.13+7"
version: "0.9.13+10"
camera_platform_interface:
dependency: transitive
description:
name: camera_platform_interface
sha256: "86fd4fc597c6e455265ddb5884feb352d0171ad14b9cdf3aba30da59b25738c4"
sha256: fceb2c36038b6392317b1d5790c6ba9e6ca9f1da3031181b8bea03882bf9387a
url: "https://pub.dev"
source: hosted
version: "2.6.0"
version: "2.7.3"
camera_web:
dependency: transitive
description:
@ -201,6 +201,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.0+1"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19
url: "https://pub.dev"
source: hosted
version: "0.4.1"
clock:
dependency: transitive
description:
@ -213,10 +221,10 @@ packages:
dependency: transitive
description:
name: code_builder
sha256: b2151ce26a06171005b379ecff6e08d34c470180ffe16b8e14b6d52be292b55f
sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37
url: "https://pub.dev"
source: hosted
version: "4.8.0"
version: "4.10.0"
collection:
dependency: transitive
description:
@ -325,10 +333,10 @@ packages:
dependency: transitive
description:
name: file_selector_platform_interface
sha256: "0aa47a725c346825a2bd396343ce63ac00bda6eff2fbc43eabe99737dede8262"
sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b
url: "https://pub.dev"
source: hosted
version: "2.6.1"
version: "2.6.2"
file_selector_windows:
dependency: transitive
description:
@ -358,14 +366,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.3.1"
flutter_dotenv:
dependency: "direct main"
description:
name: flutter_dotenv
sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77"
url: "https://pub.dev"
source: hosted
version: "5.1.0"
flutter_image_compress:
dependency: "direct main"
description:
@ -382,6 +382,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.1"
flutter_launcher_icons:
dependency: "direct dev"
description:
name: flutter_launcher_icons
sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea"
url: "https://pub.dev"
source: hosted
version: "0.13.1"
flutter_lints:
dependency: "direct dev"
description:
@ -415,23 +423,15 @@ packages:
dependency: "direct main"
description:
name: flutter_svg
sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c
sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2"
url: "https://pub.dev"
source: hosted
version: "2.0.9"
version: "2.0.10+1"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_vision:
dependency: "direct main"
description:
name: flutter_vision
sha256: ef1765d992ce39998c25a9d4e2eae7c8e5c4d3fc1f31742a829476fd6758ba6f
url: "https://pub.dev"
source: hosted
version: "1.1.4"
flutter_web_plugins:
dependency: transitive
description: flutter
@ -441,18 +441,18 @@ packages:
dependency: "direct main"
description:
name: fluttertoast
sha256: dfdde255317af381bfc1c486ed968d5a43a2ded9c931e87cbecd88767d6a71c1
sha256: "81b68579e23fcbcada2db3d50302813d2371664afe6165bc78148050ab94bf66"
url: "https://pub.dev"
source: hosted
version: "8.2.4"
version: "8.2.5"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612"
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
url: "https://pub.dev"
source: hosted
version: "3.2.0"
version: "4.0.0"
get:
dependency: "direct main"
description:
@ -501,6 +501,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.2"
image:
dependency: transitive
description:
name: image
sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e"
url: "https://pub.dev"
source: hosted
version: "4.1.7"
image_picker:
dependency: "direct main"
description:
@ -513,10 +521,10 @@ packages:
dependency: transitive
description:
name: image_picker_android
sha256: d6a6e78821086b0b737009b09363018309bbc6de3fd88cc5c26bc2bb44a4957f
sha256: "39f2bfe497e495450c81abcd44b62f56c2a36a37a175da7d137b4454977b51b1"
url: "https://pub.dev"
source: hosted
version: "0.8.8+2"
version: "0.8.9+3"
image_picker_for_web:
dependency: transitive
description:
@ -529,10 +537,10 @@ packages:
dependency: transitive
description:
name: image_picker_ios
sha256: "76ec722aeea419d03aa915c2c96bf5b47214b053899088c9abb4086ceecf97a7"
sha256: fadafce49e8569257a0cad56d24438a6fa1f0cbd7ee0af9b631f7492818a4ca3
url: "https://pub.dev"
source: hosted
version: "0.8.8+4"
version: "0.8.9+1"
image_picker_linux:
dependency: transitive
description:
@ -553,10 +561,10 @@ packages:
dependency: transitive
description:
name: image_picker_platform_interface
sha256: ed9b00e63977c93b0d2d2b343685bed9c324534ba5abafbb3dfbd6a780b1b514
sha256: fa4e815e6fcada50e35718727d83ba1c92f1edf95c0b4436554cec301b56233b
url: "https://pub.dev"
source: hosted
version: "2.9.1"
version: "2.9.3"
image_picker_windows:
dependency: transitive
description:
@ -593,18 +601,18 @@ packages:
dependency: "direct dev"
description:
name: json_annotation
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev"
source: hosted
version: "4.8.1"
version: "4.9.0"
json_serializable:
dependency: "direct dev"
description:
name: json_serializable
sha256: aa1f5a8912615733e0fdc7a02af03308933c93235bdc8d50d0b0c8a8ccb0b969
sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b
url: "https://pub.dev"
source: hosted
version: "6.7.1"
version: "6.8.0"
lints:
dependency: transitive
description:
@ -697,26 +705,26 @@ packages:
dependency: transitive
description:
name: path_provider
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
url: "https://pub.dev"
source: hosted
version: "2.2.1"
version: "2.2.2"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d"
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
version: "2.3.2"
path_provider_linux:
dependency: transitive
description:
@ -729,10 +737,10 @@ packages:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c"
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
@ -801,26 +809,18 @@ packages:
dependency: transitive
description:
name: platform
sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59"
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
url: "https://pub.dev"
source: hosted
version: "3.1.3"
version: "3.1.4"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.7"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
url: "https://pub.dev"
source: hosted
version: "3.7.3"
version: "2.1.8"
pool:
dependency: transitive
description:
@ -853,6 +853,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.0"
quiver:
dependency: transitive
description:
name: quiver
sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47
url: "https://pub.dev"
source: hosted
version: "3.2.1"
retrofit:
dependency: "direct main"
description:
@ -905,10 +913,10 @@ packages:
dependency: transitive
description:
name: share_plus_platform_interface
sha256: df08bc3a07d01f5ea47b45d03ffcba1fa9cd5370fb44b3f38c70e42cced0f956
sha256: "251eb156a8b5fa9ce033747d73535bf53911071f8d3b6f4f0b578505ce0d4496"
url: "https://pub.dev"
source: hosted
version: "3.3.1"
version: "3.4.0"
share_plus_web:
dependency: transitive
description:
@ -945,10 +953,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7"
sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
url: "https://pub.dev"
source: hosted
version: "2.3.4"
version: "2.3.5"
shared_preferences_linux:
dependency: transitive
description:
@ -961,10 +969,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a
sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
version: "2.3.2"
shared_preferences_web:
dependency: transitive
description:
@ -1014,10 +1022,10 @@ packages:
dependency: transitive
description:
name: source_gen
sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16
sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
version: "1.5.0"
source_helper:
dependency: transitive
description:
@ -1046,18 +1054,18 @@ packages:
dependency: "direct main"
description:
name: sqflite
sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a"
sha256: a9016f495c927cb90557c909ff26a6d92d9bd54fc42ba92e19d4e79d61e798c6
url: "https://pub.dev"
source: hosted
version: "2.3.0"
version: "2.3.2"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
sha256: bb4738f15b23352822f4c42a531677e5c6f522e079461fd240ead29d8d8a54a6
sha256: "28d8c66baee4968519fb8bd6cdbedad982d6e53359091f0b74544a9f32ec72d5"
url: "https://pub.dev"
source: hosted
version: "2.5.0+2"
version: "2.5.3"
stack_trace:
dependency: transitive
description:
@ -1114,6 +1122,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.5.1"
tflite_flutter:
dependency: "direct main"
description:
name: tflite_flutter
sha256: ffb8651fdb116ab0131d6dc47ff73883e0f634ad1ab12bb2852eef1bbeab4a6a
url: "https://pub.dev"
source: hosted
version: "0.10.4"
timing:
dependency: transitive
description:
@ -1150,10 +1166,10 @@ packages:
dependency: transitive
description:
name: url_launcher_android
sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f"
sha256: d4ed0711849dd8e33eb2dd69c25db0d0d3fdc37e0a62e629fe32f57a22db2745
url: "https://pub.dev"
source: hosted
version: "6.2.2"
version: "6.3.0"
url_launcher_ios:
dependency: transitive
description:
@ -1214,26 +1230,26 @@ packages:
dependency: transitive
description:
name: vector_graphics
sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43"
sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3"
url: "https://pub.dev"
source: hosted
version: "1.1.9+1"
version: "1.1.11+1"
vector_graphics_codec:
dependency: transitive
description:
name: vector_graphics_codec
sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7"
sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da
url: "https://pub.dev"
source: hosted
version: "1.1.9+1"
version: "1.1.11+1"
vector_graphics_compiler:
dependency: transitive
description:
name: vector_graphics_compiler
sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26
sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81"
url: "https://pub.dev"
source: hosted
version: "1.1.9+1"
version: "1.1.11+1"
vector_math:
dependency: transitive
description:
@ -1270,10 +1286,10 @@ packages:
dependency: transitive
description:
name: xdg_directories
sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2"
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
url: "https://pub.dev"
source: hosted
version: "1.0.3"
version: "1.0.4"
xml:
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+1
version: 1.0.0+5
environment:
sdk: '>=3.0.2 <4.0.0'
@ -35,7 +35,6 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
flutter_vision: ^1.1.4
image_picker: ^0.8.6+3
dio: ^4.0.4
flutter_svg: ^2.0.2
@ -53,9 +52,9 @@ dependencies:
lottie: 2.3.1
cached_network_image: ^3.2.3
shimmer:
flutter_dotenv: ^5.0.2
chucker_flutter:
sqflite: ^2.2.8+4
tflite_flutter: ^0.10.4
dev_dependencies:
flutter_test:
@ -72,6 +71,13 @@ dev_dependencies:
dartz: ^0.10.1
json_serializable: ^6.0.0
json_annotation: ^4.8.0
flutter_launcher_icons: ^0.13.1
flutter_launcher_icons:
android: "launcher_icon"
ios: true
image_path: "assets/images/ic_launcher.png"
min_sdk_android: 21
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
@ -89,8 +95,6 @@ flutter:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
assets:
- production/
- staging/
- assets/
- assets/images/
- assets/fonts/

View File

@ -8,13 +8,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:snap_and_cook_mobile/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);