Skip to content
Draft
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
99 changes: 99 additions & 0 deletions lib/components/toolbar/editor_camera.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:saber/i18n/strings.g.dart';


/// class used to take photo by camera
///
class TakePictureScreen extends StatefulWidget {
TakePictureScreen({
super.key,
required this.camera, // which camera to use
required this.onFileNameChanged, // function called with photo filename when photo is taken
});

final log = Logger('Camera');

final CameraDescription camera; // camera
final ValueChanged<String> onFileNameChanged; // function obtaining photo name

@override
TakePictureScreenState createState() => TakePictureScreenState();
}

class TakePictureScreenState extends State<TakePictureScreen> {
late CameraController _controller;
late Future<void> _initializeControllerFuture;

@override
void initState() {
super.initState();
// To display the current output from the Camera,
// create a CameraController.
_controller = CameraController(
// Get a specific camera from the list of available cameras.
widget.camera,
// Define the resolution to use.
ResolutionPreset.medium,
);

// Next, initialize the controller. This returns a Future.
_initializeControllerFuture = _controller.initialize();
}

@override
void dispose() {
// Dispose of the controller when the widget is disposed.
_controller.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(t.editor.camera.takePhoto)),
// You must wait until the controller is initialized before displaying the
// camera preview. Use a FutureBuilder to display a loading spinner until the
// controller has finished initializing.
body: FutureBuilder<void>(
future: _initializeControllerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// If the Future is complete, display the preview.
return CameraPreview(_controller);
} else {
// Otherwise, display a loading indicator.
return const Center(child: CircularProgressIndicator());
}
},
),
floatingActionButton: FloatingActionButton(
// Provide an onPressed callback.
onPressed: () async {
// Take the Picture in a try / catch block. If anything goes wrong,
// catch the error.
try {
// Ensure that the camera is initialized.
await _initializeControllerFuture;

// Attempt to take a picture and get the file `image`
// where it was saved.
final image = await _controller.takePicture();

if (!context.mounted) return;
widget.onFileNameChanged(image.path); // call callback with image path
} catch (e) {
// If an error occurs, log the error to the console.
widget.log.warning('Error taking photo ${e.toString()}');
}

},
child: const Icon(Icons.camera_alt),
),
);
}
}



12 changes: 12 additions & 0 deletions lib/components/toolbar/toolbar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class Toolbar extends StatefulWidget {
required this.isRedoPossible,
required this.toggleFingerDrawing,
required this.pickPhoto,
required this.takePhoto,
required this.paste,
required this.duplicateSelection,
required this.deleteSelection,
Expand All @@ -66,6 +67,7 @@ class Toolbar extends StatefulWidget {
final VoidCallback toggleFingerDrawing;

final VoidCallback pickPhoto;
final VoidCallback takePhoto;

final VoidCallback paste;

Expand Down Expand Up @@ -454,6 +456,16 @@ class _ToolbarState extends State<Toolbar> {
cupertinoIcon: CupertinoIcons.photo,
),
),
ToolbarIconButton(
tooltip: t.editor.toolbar.camera,
enabled: !widget.readOnly,
onPressed: widget.takePhoto,
padding: buttonPadding,
child: const AdaptiveIcon(
icon: Icons.camera_alt,
cupertinoIcon: CupertinoIcons.camera,
),
),
ToolbarIconButton(
tooltip: t.editor.toolbar.text,
selected: widget.textEditing,
Expand Down
12 changes: 12 additions & 0 deletions lib/i18n/strings.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ class _StringsEditorEn {
late final _StringsEditorToolbarEn toolbar = _StringsEditorToolbarEn._(_root);
late final _StringsEditorPensEn pens = _StringsEditorPensEn._(_root);
late final _StringsEditorPenOptionsEn penOptions = _StringsEditorPenOptionsEn._(_root);
late final _StringsEditorCameraEn camera = _StringsEditorCameraEn._(_root);
late final _StringsEditorColorsEn colors = _StringsEditorColorsEn._(_root);
late final _StringsEditorImageOptionsEn imageOptions = _StringsEditorImageOptionsEn._(_root);
late final _StringsEditorSelectionBarEn selectionBar = _StringsEditorSelectionBarEn._(_root);
Expand Down Expand Up @@ -713,6 +714,7 @@ class _StringsEditorToolbarEn {
String get select => 'Select';
String get toggleEraser => 'Toggle eraser (Ctrl E)';
String get photo => 'Images';
String get camera => 'Take photo';
String get text => 'Text';
String get toggleFingerDrawing => 'Toggle finger drawing (Ctrl F)';
String get undo => 'Undo';
Expand Down Expand Up @@ -747,6 +749,16 @@ class _StringsEditorPenOptionsEn {
String get size => 'Size';
}

// Path: editor.campea
class _StringsEditorCameraEn {
_StringsEditorCameraEn._(this._root);

final Translations _root; // ignore: unused_field

// Translations
String get takePhoto => 'Take photo';
}

// Path: editor.colors
class _StringsEditorColorsEn {
_StringsEditorColorsEn._(this._root);
Expand Down
95 changes: 95 additions & 0 deletions lib/pages/editor/editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:camera/camera.dart';
import 'package:collapsible/collapsible.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/cupertino.dart';
Expand All @@ -11,6 +12,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_quill/flutter_quill.dart' as flutter_quill;
import 'package:keybinder/keybinder.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;
import 'package:printing/printing.dart';
import 'package:saber/components/canvas/_asset_cache.dart';
import 'package:saber/components/canvas/_stroke.dart';
Expand All @@ -26,6 +28,7 @@ import 'package:saber/components/theming/adaptive_icon.dart';
import 'package:saber/components/theming/dynamic_material_app.dart';
import 'package:saber/components/toolbar/color_bar.dart';
import 'package:saber/components/toolbar/editor_bottom_sheet.dart';
import 'package:saber/components/toolbar/editor_camera.dart';
import 'package:saber/components/toolbar/editor_page_manager.dart';
import 'package:saber/components/toolbar/toolbar.dart';
import 'package:saber/data/editor/_color_change.dart';
Expand Down Expand Up @@ -1052,6 +1055,95 @@ class EditorState extends State<Editor> {
return images.length;
}



// functions taking photos

/// function called when photo is taken by camera
void parsePhotoName(
String photoName // name of photo created by camera
) async{
// use the Select tool so that the user can move the new image
currentTool = Select.currentSelect;


final jpgFile = File(photoName);
final Uint8List jpgBytes;
try {
jpgBytes = await jpgFile.readAsBytes();
} catch (e) {
log.severe('Failed to read file when importing $photoName: $e', e);
return;
}
List<EditorImage> images = [
PngEditorImage(
id: coreInfo.nextImageId++,
extension: path.extension(photoName),
imageProvider: MemoryImage(jpgBytes),
pageIndex: currentPageIndex,
pageSize: coreInfo.pages[currentPageIndex].size,
onMoveImage: onMoveImage,
onDeleteImage: onDeleteImage,
onMiscChange: autosaveAfterDelay,
onLoad: () => setState(() {}),
assetCache: coreInfo.assetCache,
),
];

history.recordChange(EditorHistoryItem(
type: EditorHistoryItemType.draw,
pageIndex: currentPageIndex,
strokes: [],
images: images,
));
createPage(currentPageIndex);
coreInfo.pages[currentPageIndex].images.addAll(images);
autosaveAfterDelay();
// return images.length;
}

void _takePhoto() async {
/// take photo by camera
if (coreInfo.readOnly) return;

WidgetsFlutterBinding.ensureInitialized();
// Obtain a list of the available cameras on the device.

try {
final cameras = await availableCameras();
// Get a specific camera from the list of available cameras.
final CameraDescription camera= cameras.first;

// show camera dialog and wait until it ends
await showDialog(
context: context,
builder: (context) { return AlertDialog(
title: Text(t.editor.camera.takePhoto),
content: takePhoto(context,
camera,
),
);
}
);
return;
} catch (e) {
// If an error occurs, log the error to the console.
log.warning(e.toString());
return; // no image taken
}
}

/// widget calling camera
Widget takePhoto(BuildContext context,
CameraDescription camera,
){
return TakePictureScreen(
camera: camera,
onFileNameChanged: parsePhotoName,
);
}


Future<List<_PhotoInfo>> _pickPhotosWithFilePicker() async {
final FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom,
Expand Down Expand Up @@ -1513,6 +1605,7 @@ class EditorState extends State<Editor> {
});
},
pickPhoto: _pickPhotos,
takePhoto: _takePhoto,
paste: paste,
exportAsSba: exportAsSba,
exportAsPdf: exportAsPdf,
Expand Down Expand Up @@ -1675,6 +1768,7 @@ class EditorState extends State<Editor> {
));
}


Widget bottomSheet(BuildContext context) {
final Brightness brightness = Theme.of(context).brightness;
final bool invert =
Expand Down Expand Up @@ -1777,6 +1871,7 @@ class EditorState extends State<Editor> {
);
}


Widget pageManager(BuildContext context) {
return EditorPageManager(
coreInfo: coreInfo,
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ dependencies:
mutex: ^3.1.0

collection: ^1.0.0
camera: ^0.10.5+9

dev_dependencies:
flutter_test:
Expand Down