Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/pages/admin_page.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
2 changes: 1 addition & 1 deletion lib/pages/login_page.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
19 changes: 11 additions & 8 deletions lib/pages/summation/team_overview.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -73,8 +77,7 @@ class _TeamOverviewPageState extends State<TeamOverviewPage> {
for (var page in form.pages) {
for (var question in page.questions) {
if (question.type == AnswerType.photo) {
Uint8List imageBytes = Uint8List.fromList(
List<int>.from(question.answer.whereType<int>()));
Uint8List imageBytes = base64Decode(question.answer as String);
loadedImages.add(imageBytes);

// Decode image to get dimensions
Expand All @@ -93,8 +96,8 @@ class _TeamOverviewPageState extends State<TeamOverviewPage> {
// Update state with images and max dimensions
setState(() {
images = loadedImages;
maxImageWidth = maxWidth;
maxImageHeight = maxHeight;
maxImageWidth = 640;
maxImageHeight = 360;
});
}

Expand Down Expand Up @@ -499,10 +502,10 @@ class _TeamOverviewPageState extends State<TeamOverviewPage> {
textScaler: const TextScaler.linear(1.2),
));
} else {
Uint8List imageBytes = Uint8List.fromList(
List<int>.from(question.answer.whereType<int>()));
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());
Expand Down
2 changes: 1 addition & 1 deletion lib/services/scouting/form_page_data.dart
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
15 changes: 12 additions & 3 deletions lib/services/scouting/question.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
// Flutter imports:
import 'package:flutter/widgets.dart';

// Project imports:
import 'package:scouting_site/services/cast.dart';

class Question {
Expand Down Expand Up @@ -50,15 +53,21 @@ class Question {

Map<String, dynamic> 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(),
Expand Down Expand Up @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions lib/widgets/avgs_graph.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
// Flutter imports:
import 'package:flutter/material.dart';

// Package imports:
import 'package:syncfusion_flutter_charts/charts.dart';

class AvgsGraph extends StatelessWidget {
Expand Down
5 changes: 5 additions & 0 deletions lib/widgets/dialog_widgets/dialog_image_corousel.dart
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
158 changes: 72 additions & 86 deletions lib/widgets/questions_widgets/camera_widget.dart
Original file line number Diff line number Diff line change
@@ -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<Uint8List>)? onImageListUpdated;

const CameraCaptureWidget({
super.key,
this.multiple = false,
this.onImageListUpdated,
});

const CameraCaptureWidget({super.key, this.onImageCaptured});
final Function(Uint8List image)? onImageCaptured;
@override
State<CameraCaptureWidget> createState() => _CameraCaptureWidgetState();
}

class _CameraCaptureWidgetState extends State<CameraCaptureWidget> {
List<Uint8List> _images = [];
html.VideoElement? _videoElement;
CameraController? controller;
List<CameraDescription>? cameras;
bool isCameraInitialized = false;
bool _isRearCameraSelected = true;

@override
void initState() {
super.initState();
_initializeWebCamera();
initializeCamera();
}

// Initialize the web camera
Future<void> _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<void> 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<void> _captureImage() async {
if (_videoElement == null) {
log("Web camera is not initialized.");
return;
Future<void> 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<void> 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.'),
],
);
}
Expand Down
16 changes: 9 additions & 7 deletions lib/widgets/questions_widgets/question_widget.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Dart imports:
import 'dart:convert';

// Flutter imports:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
Expand All @@ -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';

Expand Down Expand Up @@ -85,13 +89,11 @@ class QuestionWidgetState extends State<QuestionWidget> {
}

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) {
Expand Down
3 changes: 3 additions & 0 deletions lib/widgets/scout_app_bar.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
// Flutter imports:
import 'package:flutter/material.dart';

// Project imports:
import 'package:scouting_site/theme.dart';

AppBar getScoutAppBar(String title) {
Expand Down
Loading