Finishing apps
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 5.6 KiB |
After Width: | Height: | Size: 7.9 KiB |
After Width: | Height: | Size: 6.6 KiB |
|
@ -1,4 +1,6 @@
|
|||
Telur
|
||||
Ayam
|
||||
Kentang
|
||||
Pisang
|
||||
Telur
|
||||
Tomat
|
||||
Wortel
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 99 KiB |
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 587 B |
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 282 B After Width: | Height: | Size: 856 B |
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 704 B After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 586 B After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 762 B After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 6.5 KiB |
|
@ -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';
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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] ?? '';
|
||||
}
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -5,6 +5,5 @@ abstract class UtensilInterface {
|
|||
|
||||
Future<List<String>> fetchSelectedUtensils();
|
||||
|
||||
|
||||
Future<void> updateUtensil(Utensil utensil);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
try{
|
||||
var response = await _utensilService.getAllUtensil(CancelToken());
|
||||
List<Utensil> utensils = await _dbContract.getUtensils();
|
||||
if (utensils.isEmpty){
|
||||
_dbContract.insertAllUtensil(utensilResource);
|
||||
return utensilResource;
|
||||
|
||||
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
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
|
|
212
lib/main.dart
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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,);
|
||||
},
|
||||
|
|
|
@ -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,10 +44,14 @@ class DetectionItemWidget extends GetView<RecipeDetectionViewModel> {
|
|||
},
|
||||
splashColor: Colors.transparent,
|
||||
icon: const Icon(Icons.add_circle_outline)),
|
||||
Text(
|
||||
'${ingredient.quantity}',
|
||||
SizedBox(
|
||||
width: 20.w,
|
||||
child: Text(
|
||||
'${ingredient.quantity?.toInt()}',
|
||||
textAlign: TextAlign.center,
|
||||
style: TTCommonsTextStyles.textMd.textMedium(),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
controller.decrementIngredientQuantity(index);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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';
|
||||
}
|
|
@ -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)}%'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
const List<String> labels = [
|
||||
'Ayam',
|
||||
'Kentang',
|
||||
'Pisang',
|
||||
'Telur',
|
||||
'Tomat',
|
||||
'Wortel'
|
||||
];
|
|
@ -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;
|
||||
}
|
|
@ -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,
|
||||
// );
|
||||
}
|
224
pubspec.lock
|
@ -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:
|
||||
|
|
14
pubspec.yaml
|
@ -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/
|
||||
|
|
|
@ -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);
|
||||
|
|