diff --git a/android/app/build.gradle b/android/app/build.gradle index c722841e51aa1f2271d161773cbaf991b5a80222..c6b5d48e2e161a833f97e478f8cef140438e1aa1 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -44,7 +44,7 @@ android { defaultConfig { applicationId "org.benoitharrault.random" - minSdkVersion 19 + minSdkVersion 21 targetSdkVersion 30 versionCode appVersionCode.toInteger() versionName appVersionName diff --git a/android/gradle.properties b/android/gradle.properties index cfe1f46b5f8d528a00df0b970d82b0a0f7297079..e7bbd478806d6d3d5c8d27157b30df069180a8b6 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true -app.versionName=1.0.43 -app.versionCode=44 +app.versionName=1.0.44 +app.versionCode=45 diff --git a/assets/translations/en.json b/assets/translations/en.json index 6b6d02750729a4be993e296a06bcc5e0f63d4fe2..7280a8a3e1e7d0316800f5a1005c0893419ca4cd 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -2,6 +2,7 @@ "app_name": "Sandbox App", "bottom_nav_sample": "Sample", + "bottom_nav_camera": "Camera", "bottom_nav_api": "API", "bottom_nav_chart": "Graph", "bottom_nav_game": "Game", diff --git a/assets/translations/fr.json b/assets/translations/fr.json index 72682066e0c519f9bdd04d866ef5fa0644db798f..5ef35e50be00a17d1c45e5deca1517d327ef6341 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -2,6 +2,7 @@ "app_name": "App de test", "bottom_nav_sample": "Démo", + "bottom_nav_camera": "Caméra", "bottom_nav_api": "API", "bottom_nav_chart": "Graph", "bottom_nav_game": "Jeu", diff --git a/lib/cubit/bottom_nav_cubit.dart b/lib/cubit/bottom_nav_cubit.dart index 98ff898e6e0e4bc0e5f7784b40462ccb1cf1e8d3..a02cc94d81d823edb09522d02406763341dc12f2 100644 --- a/lib/cubit/bottom_nav_cubit.dart +++ b/lib/cubit/bottom_nav_cubit.dart @@ -3,7 +3,7 @@ import 'package:hydrated_bloc/hydrated_bloc.dart'; class BottomNavCubit extends HydratedCubit<int> { BottomNavCubit() : super(0); - int pagesCount = 5; + int pagesCount = 6; void updateIndex(int index) { if (isIndexAllowed(index)) { diff --git a/lib/ui/screens/camera_page.dart b/lib/ui/screens/camera_page.dart new file mode 100644 index 0000000000000000000000000000000000000000..98c22824edbc072dd7bc89334b91fcfdaa8eb683 --- /dev/null +++ b/lib/ui/screens/camera_page.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:unicons/unicons.dart'; + +import 'package:random/ui/widgets/take_picture_widget.dart'; + +class CameraPage extends StatelessWidget { + const CameraPage({super.key}); + + static Icon navBarIcon = const Icon(UniconsLine.camera); + static String navBarText = 'bottom_nav_camera'; + + @override + Widget build(BuildContext context) { + return Material( + color: Theme.of(context).colorScheme.background, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: <Widget>[ + const SizedBox(height: 8), + const TakePictureWidget(), + ], + ), + ); + } +} diff --git a/lib/ui/skeleton.dart b/lib/ui/skeleton.dart index 275a44618b4c0780dd33157aa5d81f492c09219e..044b573f6823cfb4bb0f8adb1edc342349a4c134 100644 --- a/lib/ui/skeleton.dart +++ b/lib/ui/skeleton.dart @@ -5,6 +5,7 @@ import 'package:flutter_swipe/flutter_swipe.dart'; import 'package:random/cubit/bottom_nav_cubit.dart'; import 'package:random/ui/screens/about_page.dart'; import 'package:random/ui/screens/api_page.dart'; +import 'package:random/ui/screens/camera_page.dart'; import 'package:random/ui/screens/demo_page.dart'; import 'package:random/ui/screens/game_page.dart'; import 'package:random/ui/screens/graph_page.dart'; @@ -25,6 +26,7 @@ class _SkeletonScreenState extends State<SkeletonScreen> { const List<Widget> pageNavigation = <Widget>[ DemoPage(), ApiPage(), + CameraPage(), GraphPage(), GamePage(), SettingsPage(), diff --git a/lib/ui/widgets/bottom_nav_bar.dart b/lib/ui/widgets/bottom_nav_bar.dart index 6d6f83d1e47a4c73acaca1aca929a59224dc43ec..2013e651e39935e8c9b6c3197eb18773d1d3d6f1 100644 --- a/lib/ui/widgets/bottom_nav_bar.dart +++ b/lib/ui/widgets/bottom_nav_bar.dart @@ -50,6 +50,10 @@ class BottomNavBar extends StatelessWidget { icon: const Icon(UniconsLine.globe), label: tr('bottom_nav_api'), ), + BottomNavigationBarItem( + icon: const Icon(UniconsLine.camera), + label: tr('bottom_nav_camera'), + ), BottomNavigationBarItem( icon: const Icon(UniconsLine.pen), label: tr('bottom_nav_chart'), diff --git a/lib/ui/widgets/take_picture_widget.dart b/lib/ui/widgets/take_picture_widget.dart new file mode 100644 index 0000000000000000000000000000000000000000..7a73090629bb996bdb2af68bd4ba45c97b35a3b4 --- /dev/null +++ b/lib/ui/widgets/take_picture_widget.dart @@ -0,0 +1,121 @@ +import 'dart:io'; + +import 'package:camera/camera.dart'; +import 'package:flutter/material.dart'; +import 'package:unicons/unicons.dart'; + +import 'package:random/utils/picture_storage.dart'; + +class TakePictureWidget extends StatefulWidget { + const TakePictureWidget({super.key}); + + @override + TakePictureWidgetState createState() => TakePictureWidgetState(); +} + +class TakePictureWidgetState extends State<TakePictureWidget> { + CameraController? controller; + PictureStorage? storage; + + List<String> previousImages = []; + List<String> debug = []; + + @override + void initState() { + loadCamera(); + storage = PictureStorage(); + super.initState(); + } + + loadCamera() async { + final List<CameraDescription>? cameras = await availableCameras(); + if (cameras != null) { + controller = CameraController( + cameras.first, + ResolutionPreset.max, + enableAudio: false, + ); + + controller!.initialize().then((_) { + if (!mounted) { + return; + } + setState(() {}); + }); + } else { + print("No camera found."); + } + } + + @override + void dispose() { + controller!.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + height: 400, + child: controller == null + ? Center(child: Text("Loading camera...")) + : !controller!.value.isInitialized + ? Center(child: CircularProgressIndicator()) + : CameraPreview(controller!), + ), + ElevatedButton.icon( + label: Text("Take picture"), + icon: Icon(UniconsLine.camera), + onPressed: () async { + try { + if ((controller != null) && (controller!.value.isInitialized)) { + final XFile image = await controller!.takePicture(); + print('image.path: ' + image.path); + debug.add('image.path: ' + image.path); + + File savedFile = await storage!.writeCounter(File(image.path)); + debug.add('image.path: ' + image.path); + + String imagePath = savedFile.path; + print('imagePath: ' + imagePath); + debug.add('imagePath: ' + imagePath); + + previousImages.add(imagePath); + setState(() {}); + } + } catch (e) { + debug.add('error: ' + e.toString()); + setState(() {}); + + print(e); + } + }, + ), + Text('debug: '), + Column( + children: debug.map((String line) { + return Text(line); + }).toList(), + ), + previousImages.length == 0 + ? Text('no previous images') + : Column( + children: previousImages.map((String imagePath) { + return Row( + children: [ + // Image.file(File(imagePath)), + Text(imagePath), + ], + ); + }).toList(), + ), + ], + ), + ); + } +} diff --git a/lib/utils/picture_storage.dart b/lib/utils/picture_storage.dart new file mode 100644 index 0000000000000000000000000000000000000000..72e2278f677d39ef318ab24a57dd4a7e8b9ae950 --- /dev/null +++ b/lib/utils/picture_storage.dart @@ -0,0 +1,35 @@ +import 'dart:io'; + +import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; + +class PictureStorage { + Future<String> get _localPath async { + final directory = await getApplicationDocumentsDirectory(); + + return directory.path; + } + + Future<String> _localFilePath(String name) async { + final path = await _localPath; + + return path + '/' + name; + } + + Future<File> moveFile(File sourceFile, String newPath) async { + try { + return await sourceFile.rename(newPath); + } on FileSystemException catch (e) { + print('Found exception while moving file: ' + e.toString()); + final newFile = await sourceFile.copy(newPath); + await sourceFile.delete(); + return newFile; + } + } + + Future<File> writeCounter(File sourceFile) async { + final targetFile = await _localFilePath(basename(sourceFile.path)); + + return moveFile(sourceFile, targetFile); + } +} diff --git a/pubspec.lock b/pubspec.lock index 1505f2f3bab4e02e4b77d1b5d90e487a9eb593e5..b3dd33c8a58e6b44e1eff2ec494b36c08e757a6d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -25,6 +25,46 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.2" + camera: + dependency: "direct main" + description: + name: camera + sha256: "9499cbc2e51d8eb0beadc158b288380037618ce4e30c9acbc4fae1ac3ecb5797" + url: "https://pub.dev" + source: hosted + version: "0.10.5+9" + camera_android: + dependency: transitive + description: + name: camera_android + sha256: "351429510121d179b9aac5a2e8cb525c3cd6c39f4d709c5f72dfb21726e52371" + url: "https://pub.dev" + source: hosted + version: "0.10.8+16" + camera_avfoundation: + dependency: transitive + description: + name: camera_avfoundation + sha256: "7d0763dfcbf060f56aa254a68c103210280bee9e97bbe4fdef23e257a4f70ab9" + url: "https://pub.dev" + source: hosted + version: "0.9.14" + camera_platform_interface: + dependency: transitive + description: + name: camera_platform_interface + sha256: e971ebca970f7cfee396f76ef02070b5e441b4aa04942da9c108d725f57bbd32 + url: "https://pub.dev" + source: hosted + version: "2.7.2" + camera_web: + dependency: transitive + description: + name: camera_web + sha256: f18ccfb33b2a7c49a52ad5aa3f07330b7422faaecbdfd9b9fe8e51182f6ad67d + url: "https://pub.dev" + source: hosted + version: "0.3.2+4" characters: dependency: transitive description: @@ -49,6 +89,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e + url: "https://pub.dev" + source: hosted + version: "0.3.3+8" crypto: dependency: transitive description: @@ -123,6 +171,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da + url: "https://pub.dev" + source: hosted + version: "2.0.17" flutter_swipe: dependency: "direct main" description: @@ -365,6 +421,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" string_scanner: dependency: transitive description: @@ -438,5 +502,5 @@ packages: source: hosted version: "1.0.4" sdks: - dart: ">=3.2.0 <4.0.0" - flutter: ">=3.16.0" + dart: ">=3.2.3 <4.0.0" + flutter: ">=3.16.6" diff --git a/pubspec.yaml b/pubspec.yaml index 499f579c251cc60572d357eda98f5d4fdccd65e3..2e0ad1ddb9fb0b379c371fd73328014ec8aad36b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: A random application, for testing purpose only. publish_to: 'none' -version: 1.0.43+44 +version: 1.0.44+45 environment: sdk: '^3.0.0' @@ -12,6 +12,7 @@ dependencies: flutter: sdk: flutter + camera: ^0.10.5+8 easy_localization: ^3.0.1 equatable: ^2.0.5 flutter_bloc: ^8.1.1