diff --git a/lib/pages/admin_page.dart b/lib/pages/admin_page.dart index f455184..823764f 100644 --- a/lib/pages/admin_page.dart +++ b/lib/pages/admin_page.dart @@ -1,4 +1,7 @@ +// Flutter imports: import 'package:flutter/material.dart'; + +// Project imports: import 'package:scouting_site/services/firebase/firebase_api.dart'; import 'package:scouting_site/services/formatters/text_formatter_builder.dart'; import 'package:scouting_site/services/scouting/form_page_data.dart'; diff --git a/lib/pages/login_page.dart b/lib/pages/login_page.dart index 9019032..11b9567 100644 --- a/lib/pages/login_page.dart +++ b/lib/pages/login_page.dart @@ -1,8 +1,8 @@ // Flutter imports: import 'package:flutter/material.dart'; -import 'package:scouting_site/pages/admin_page.dart'; // Project imports: +import 'package:scouting_site/pages/admin_page.dart'; import 'package:scouting_site/pages/home_page.dart'; import 'package:scouting_site/services/firebase/firebase_api.dart'; import 'package:scouting_site/services/localstorage.dart'; diff --git a/lib/pages/summation/team_overview.dart b/lib/pages/summation/team_overview.dart index d25ffeb..46497a4 100644 --- a/lib/pages/summation/team_overview.dart +++ b/lib/pages/summation/team_overview.dart @@ -1,8 +1,12 @@ +// Dart imports: +import 'dart:convert'; + +// Flutter imports: import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; // Package imports: import 'package:fl_chart/fl_chart.dart'; -import 'package:flutter/services.dart'; // Project imports: import 'package:scouting_site/services/cast.dart'; @@ -73,8 +77,7 @@ class _TeamOverviewPageState extends State { for (var page in form.pages) { for (var question in page.questions) { if (question.type == AnswerType.photo) { - Uint8List imageBytes = Uint8List.fromList( - List.from(question.answer.whereType())); + Uint8List imageBytes = base64Decode(question.answer as String); loadedImages.add(imageBytes); // Decode image to get dimensions @@ -93,8 +96,8 @@ class _TeamOverviewPageState extends State { // Update state with images and max dimensions setState(() { images = loadedImages; - maxImageWidth = maxWidth; - maxImageHeight = maxHeight; + maxImageWidth = 640; + maxImageHeight = 360; }); } @@ -499,10 +502,10 @@ class _TeamOverviewPageState extends State { textScaler: const TextScaler.linear(1.2), )); } else { - Uint8List imageBytes = Uint8List.fromList( - List.from(question.answer.whereType())); + Uint8List imageBytes = base64Decode(question.answer as String); answersWidgets.add(Text(question.questionText)); - answersWidgets.add(Image.memory(imageBytes)); + answersWidgets.add(SizedBox( + width: 300, height: 300, child: Image.memory(imageBytes))); } } answersWidgets.add(const Divider()); diff --git a/lib/services/scouting/form_page_data.dart b/lib/services/scouting/form_page_data.dart index 3618706..55af5cf 100644 --- a/lib/services/scouting/form_page_data.dart +++ b/lib/services/scouting/form_page_data.dart @@ -1,6 +1,6 @@ // Project imports: -import 'package:scouting_site/services/scouting/question.dart'; import 'package:scouting_site/services/cast.dart'; +import 'package:scouting_site/services/scouting/question.dart'; class FormPageData { String pageName; diff --git a/lib/services/scouting/question.dart b/lib/services/scouting/question.dart index afce00b..6ea92f8 100644 --- a/lib/services/scouting/question.dart +++ b/lib/services/scouting/question.dart @@ -1,4 +1,7 @@ +// Flutter imports: import 'package:flutter/widgets.dart'; + +// Project imports: import 'package:scouting_site/services/cast.dart'; class Question { @@ -50,15 +53,21 @@ class Question { Map toJson() { _validateAnswer(); + dynamic answerValue; if (type == AnswerType.checkbox && answer == null) { answer = false; } - + if (type == AnswerType.photo) { + print("Answer: $answer"); + answerValue = answer as String; + } else { + answerValue = answer[0]; + } return { 'type': type.name, 'questionText': questionText, - 'answer': answer[0], + 'answer': answerValue, 'options': options, 'evaluation': evaluation, 'score': evaluate(), @@ -123,7 +132,7 @@ class Question { return json['answer'] ?? (type == AnswerType.checkbox ? false : null); } else if (type == AnswerType.photo) { if (json['answer'] == null) { - return 0; + return ""; } return json['answer']; } else { diff --git a/lib/widgets/avgs_graph.dart b/lib/widgets/avgs_graph.dart index 7e8dce9..36b8a67 100644 --- a/lib/widgets/avgs_graph.dart +++ b/lib/widgets/avgs_graph.dart @@ -1,4 +1,7 @@ +// Flutter imports: import 'package:flutter/material.dart'; + +// Package imports: import 'package:syncfusion_flutter_charts/charts.dart'; class AvgsGraph extends StatelessWidget { diff --git a/lib/widgets/dialog_widgets/dialog_image_corousel.dart b/lib/widgets/dialog_widgets/dialog_image_corousel.dart index 86ca54f..db864f0 100644 --- a/lib/widgets/dialog_widgets/dialog_image_corousel.dart +++ b/lib/widgets/dialog_widgets/dialog_image_corousel.dart @@ -1,5 +1,10 @@ +// Dart imports: import 'dart:typed_data'; + +// Flutter imports: import 'package:flutter/material.dart'; + +// Package imports: import 'package:carousel_slider/carousel_slider.dart'; class ImageCarousel extends StatelessWidget { diff --git a/lib/widgets/questions_widgets/camera_widget.dart b/lib/widgets/questions_widgets/camera_widget.dart index 3de07bf..6ee9036 100644 --- a/lib/widgets/questions_widgets/camera_widget.dart +++ b/lib/widgets/questions_widgets/camera_widget.dart @@ -1,126 +1,112 @@ -import 'dart:developer'; +// Dart imports: +import 'dart:convert'; +import 'dart:typed_data'; +// Flutter imports: +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'dart:typed_data'; -import 'dart:convert'; -import 'package:universal_html/html.dart' as html; +// Package imports: +import 'package:camera/camera.dart'; class CameraCaptureWidget extends StatefulWidget { - final bool multiple; - final Function(List)? onImageListUpdated; - - const CameraCaptureWidget({ - super.key, - this.multiple = false, - this.onImageListUpdated, - }); - + const CameraCaptureWidget({super.key, this.onImageCaptured}); + final Function(Uint8List image)? onImageCaptured; @override State createState() => _CameraCaptureWidgetState(); } class _CameraCaptureWidgetState extends State { - List _images = []; - html.VideoElement? _videoElement; + CameraController? controller; + List? cameras; + bool isCameraInitialized = false; + bool _isRearCameraSelected = true; @override void initState() { super.initState(); - _initializeWebCamera(); + initializeCamera(); } - // Initialize the web camera - Future _initializeWebCamera() async { - try { - final html.VideoElement videoElement = html.VideoElement() - ..width = 640 - ..height = 480 - ..style.border = '1px solid black'; - - final html.MediaStream? stream = await html.window.navigator.mediaDevices - ?.getUserMedia({'video': true}); - videoElement.srcObject = stream; - html.document.body! - .append(videoElement); // Append to DOM to display video - videoElement.play(); - + Future initializeCamera() async { + cameras = await availableCameras(); + if (cameras != null && cameras!.isNotEmpty) { + controller = CameraController(cameras![0], ResolutionPreset.high); + await controller!.initialize(); setState(() { - _videoElement = videoElement; + isCameraInitialized = true; }); - } catch (e) { - log("Error accessing web camera: $e"); } } - // Capture an image from the video element - Future _captureImage() async { - if (_videoElement == null) { - log("Web camera is not initialized."); - return; + Future initCamera(CameraDescription cameraDescription) async { + controller = CameraController(cameraDescription, ResolutionPreset.high); + try { + await controller!.initialize(); + if (!mounted) return; + setState(() {}); + } on CameraException catch (e) { + debugPrint("camera error $e"); } + } + Future takePicture() async { + if (!controller!.value.isInitialized) return; + if (controller!.value.isTakingPicture) return; try { - final html.CanvasElement canvas = - html.CanvasElement(width: 640, height: 480); - final html.CanvasRenderingContext2D context = canvas.context2D; - context.drawImage(_videoElement!, 0, 0); - - // Capture the image from canvas and convert it to a Uint8List - final imageBytes = canvas.toDataUrl('image/png'); - final decodedBytes = base64Decode(imageBytes.split(',').last); - - setState(() { - if (widget.multiple) { - _images.add(Uint8List.fromList(decodedBytes)); - } else { - _images = [Uint8List.fromList(decodedBytes)]; - } - }); - - widget.onImageListUpdated?.call(_images); - } catch (e) { - log("Error capturing image: $e"); + XFile picture = await controller!.takePicture(); + widget.onImageCaptured!(await picture.readAsBytes()); + } on CameraException catch (e) { + debugPrint('Error occurred while taking picture: $e'); } } @override void dispose() { - _videoElement?.pause(); + controller?.dispose(); super.dispose(); } @override Widget build(BuildContext context) { + if (!isCameraInitialized) { + return const Center(child: CircularProgressIndicator()); + } return Column( children: [ - _videoElement != null - ? const AspectRatio( - aspectRatio: 640 / 480, - child: HtmlElementView(viewType: 'videoElement'), - ) - : const Text('Loading camera...'), - const SizedBox(height: 20), - ElevatedButton( - onPressed: _captureImage, - child: Text(widget.multiple ? 'Capture Images' : 'Capture Image'), + AspectRatio( + aspectRatio: controller!.value.aspectRatio, + child: CameraPreview(controller!), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + if (cameras!.length > 1) + IconButton( + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + iconSize: 30, + icon: Icon( + _isRearCameraSelected + ? CupertinoIcons.switch_camera + : CupertinoIcons.switch_camera_solid, + color: Colors.white, + ), + onPressed: () { + setState( + () => _isRearCameraSelected = !_isRearCameraSelected); + initCamera(cameras![_isRearCameraSelected ? 0 : 1]); + }, + ), + IconButton( + onPressed: takePicture, + iconSize: 50, + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + icon: const Icon(Icons.circle, color: Colors.white), + ), + ], ), - const SizedBox(height: 20), - _images.isNotEmpty - ? Wrap( - children: _images - .map((imageBytes) => Padding( - padding: const EdgeInsets.all(8.0), - child: Image.memory( - imageBytes, - width: 100, - height: 100, - fit: BoxFit.cover, - ), - )) - .toList(), - ) - : const Text('No images captured yet.'), ], ); } diff --git a/lib/widgets/questions_widgets/question_widget.dart b/lib/widgets/questions_widgets/question_widget.dart index d62d3f5..922a05f 100644 --- a/lib/widgets/questions_widgets/question_widget.dart +++ b/lib/widgets/questions_widgets/question_widget.dart @@ -1,3 +1,6 @@ +// Dart imports: +import 'dart:convert'; + // Flutter imports: import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -8,6 +11,7 @@ import 'package:scouting_site/services/formatters/text_formatter_builder.dart'; import 'package:scouting_site/services/scouting/question.dart'; import 'package:scouting_site/widgets/dialog_widgets/dialog_text_input.dart'; import 'package:scouting_site/widgets/dialog_widgets/dialog_toggle_switch.dart'; +import 'package:scouting_site/widgets/questions_widgets/camera_widget.dart'; import 'package:scouting_site/widgets/questions_widgets/counter_widget.dart'; import 'package:scouting_site/widgets/questions_widgets/multiplechoice_widget.dart'; @@ -85,13 +89,11 @@ class QuestionWidgetState extends State { } Widget generatePhotoWidget(Question question) { - // return CameraCaptureWidget( - // multiple: true, - // onImageListUpdated: (images) { - // question.answer = images; - // }, - // ); - return const Placeholder(); + return CameraCaptureWidget( + onImageCaptured: (Uint8List image) async { + question.answer = base64Encode(image); + }, + ); } Widget generateMultipleChoice(Question question) { diff --git a/lib/widgets/scout_app_bar.dart b/lib/widgets/scout_app_bar.dart index 87063f8..e64b9f0 100644 --- a/lib/widgets/scout_app_bar.dart +++ b/lib/widgets/scout_app_bar.dart @@ -1,4 +1,7 @@ +// Flutter imports: import 'package:flutter/material.dart'; + +// Project imports: import 'package:scouting_site/theme.dart'; AppBar getScoutAppBar(String title) {