inspection_camera 0.1.0 copy "inspection_camera: ^0.1.0" to clipboard
inspection_camera: ^0.1.0 copied to clipboard

A new Flutter RzCamera Package.

example/lib/main.dart

import 'package:file_saver/file_saver.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:inspection_camera/inspection_camera.dart';

void main() {
  try {
    runApp(const MyApp());
  } catch (e) {
    print(e);
  }
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Camera POC'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final List<InspectionMedia?> images = [];

  final TextEditingController textEditingController =
      TextEditingController(text: "3");
  bool loading = false;

  Future<T> loadingWrapper<T>(Future<T> Function() callback) async {
    setState(() {
      loading = true;
    });
    try {
      return await callback.call();
    } finally {
      setState(() {
        loading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: ListView(
        children: [
          if (!loading) ...[
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: FilledButton(
                child: const Text('Take single picture'),
                onPressed: () async {
                  loadingWrapper(() async {
                    final image = await CameraModule(
                        navigationHandler:
                            NavigationHandler.defaultNavigator(context),
                        defaultCaptureConfig: ImageCaptureConfig(
                          cameraModuleCallbacks: ImageCaptureCallbacks(
                              onCameraLoaded: () {},
                              onRotationPrompt: (a) {},
                              onFlashToggle: (a) {},
                              onCapture: () {},
                              onPreviewLoad: () {},
                              onPreviewAccepted: () {},
                              onPreviewRejected: () {},
                              onRetakeClick: () async {
                                bool valueToReturn = false;
                                await showDialog(
                                    context: context,
                                    builder: (context) {
                                      return AlertDialog(
                                        title: const Text(
                                          'Current image will be discarded.',
                                        ),
                                        actions: [
                                          InkWell(
                                              onTap: () {
                                                Navigator.pop(context);
                                                valueToReturn = true;
                                              },
                                              child: const Text('ok')),
                                          InkWell(
                                              onTap: () {
                                                Navigator.pop(context);
                                                valueToReturn = false;
                                              },
                                              child: const Text('cancel')),
                                        ],
                                      );
                                    });
                                return valueToReturn;
                              }),
                        )).capture(
                      captureConfig: (defaultConfig) {
                        return defaultConfig;
                      },
                    );
                    if (image != null) {
                      setState(() {
                        images.insert(0, image);
                      });
                    }
                  });
                },
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: FilledButton(
                child: const Text('Take single video'),
                onPressed: () async {
                  loadingWrapper(() async {
                    final image = await CameraModule(
                      navigationHandler:
                          NavigationHandler.defaultNavigator(context),
                    ).capture(
                      captureConfig: (defaultConfig) => VideoCaptureConfig(),
                    );
                    if (image != null) {
                      setState(() {
                        images.insert(0, image);
                      });
                    }
                  });
                },
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Row(
                children: [
                  Flexible(
                    child: TextFormField(
                      controller: textEditingController,
                      onChanged: (value) {
                        if (int.tryParse(value) == null && value.isNotEmpty) {
                          textEditingController.text = "1";
                        }
                        setState(() {});
                      },
                    ),
                  ),
                  Expanded(
                    child: FilledButton(
                      child: Text('Take ${int.tryParse(
                            textEditingController.text,
                          ) ?? 1} pictures'),
                      onPressed: () async {
                        loadingWrapper(() async {
                          await CameraModule(
                            navigationHandler:
                                NavigationHandler.defaultNavigator(context),
                            defaultCaptureConfig: ImageCaptureConfig(
                                captureWidgetBuilders: CaptureWidgetBuilders(
                              shutterIconBuilder: (context) =>
                                  const Placeholder(),
                            )),
                          ).captureMultiple(
                            captureConfigs: (defaultConfig) {
                              final List<MediaCaptureConfig> configs = [];
                              for (var i = 0;
                                  i < int.parse(textEditingController.text);
                                  i++) {
                                configs.add(defaultConfig);
                                /*if (i % 2 != 0) {
                                  configs Fs.add(VideoCaptureConfig());
                                } else {
                                  configs.add(defaultConfig);
                                }*/
                              }
                              return configs;
                            },
                            onPictureTaken: (index, captureConfig, data) {
                              images.insert(0, data);
                              setState(() {});
                            },
                          );
                        });
                      },
                    ),
                  ),
                ],
              ),
            ),
          ] else
            const Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Padding(
                  padding: EdgeInsets.all(8.0),
                  child: CircularProgressIndicator(),
                ),
              ],
            ),
          const Divider(),
          ...images.map(
            (e) => e == null
                ? Container()
                : switch (e) {
                    InspectionImage() => buildImage(context, e),
                    InspectionVideo() => Card(
                        child: Padding(
                          padding: const EdgeInsets.all(16.0),
                          child: buildStackVid(context, e.toWidget(), e.data),
                        ),
                      ),
                  },
          ),
        ],
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

  SizedBox buildImage(BuildContext context, InspectionImage e) {
    return SizedBox(
      width: MediaQuery.of(context).size.width,
      child: Card(
        child: Column(
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                FutureBuilder(
                  future: e.data.readAsBytes(),
                  builder: (context, snapshot) {
                    if (snapshot.hasData) {
                      return buildStack(
                        context,
                        e.toWidget(),
                        snapshot.data!,
                      );
                    } else {
                      return Container();
                    }
                  },
                ),
                FutureBuilder(
                  future: e.data.readAsBytes(),
                  builder: (context, snapshot) {
                    if (snapshot.hasData) {
                      return buildStack(
                        context,
                        e.toWidget(),
                        snapshot.data!,
                      );
                    } else {
                      return Container();
                    }
                  },
                ),
              ],
            ),
            FilledButton(
              onPressed: () async {
                if (kIsWeb) {
                  await FileSaver.instance.saveFile(
                    name: 'originalImage.png',
                    filePath: e.data.path,
                  );
                  await Future.delayed(
                    const Duration(seconds: 1),
                  );
                  await FileSaver.instance.saveFile(
                    name: 'editedImage.png',
                    bytes: await e.data.readAsBytes(),
                  );
                } else {
                  await ImageGallerySaver.saveFile(
                    e.data.path,
                  );
                  await ImageGallerySaver.saveFile(e.data.path);
                  if (context.mounted) {
                    showDialog(
                      context: context,
                      builder: (context) {
                        return AlertDialog(
                          content: const Text('Saved to gallery!'),
                          actions: [
                            FilledButton(
                              onPressed: () => Navigator.pop(context),
                              child: const Text('Close'),
                            ),
                          ],
                        );
                      },
                    );
                  }
                }
              },
              child: const Text(kIsWeb ? 'Download' : 'Save to gallery'),
            )
          ],
        ),
      ),
    );
  }

  Stack buildStack(BuildContext context, Widget? e, Uint8List data) {
    return Stack(
      children: [
        SizedBox(
          width: MediaQuery.of(context).size.width * 0.45,
          child: e,
        ),
        Align(
          alignment: Alignment.topRight,
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: FloatingActionButton(
              child: const Icon(
                Icons.compress_rounded,
              ),
              onPressed: () {
                showDialog(
                  context: context,
                  builder: (context) => CompressionDialog(data: data),
                );
              },
            ),
          ),
        )
      ],
    );
  }

  Stack buildStackVid(BuildContext context, Widget? e, XFile data) {
    return Stack(
      children: [
        SizedBox(
          width: MediaQuery.of(context).size.width * 0.45,
          child: e,
        ),
        Align(
          alignment: Alignment.topRight,
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: FloatingActionButton(
              child: const Icon(
                Icons.compress_rounded,
              ),
              onPressed: () {
                showDialog(
                  context: context,
                  builder: (context) => CompressionDialogVideo(data: data),
                );
              },
            ),
          ),
        )
      ],
    );
  }
}

class CompressionDialog extends StatefulWidget {
  const CompressionDialog({super.key, required this.data});

  final Uint8List data;

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

class CompressionDialogState extends State<CompressionDialog> {
  int minWidth = 1920;
  int minHeight = 1080;
  int quality = 95;
  int rotate = 0;
  int inSampleSize = 1;
  bool autoCorrectionAngle = true;
  bool keepExif = false;
  CompressFormat format = CompressFormat.jpeg;

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: const Text('Set Parameters'),
      content: SingleChildScrollView(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _buildSlider(
              label: 'Min Width',
              value: minWidth,
              min: 0,
              max: 5000,
              divisions: 100,
              onChanged: (value) {
                setState(() {
                  minWidth = value.round();
                });
              },
            ),
            _buildSlider(
              label: 'Min Height',
              value: minHeight,
              min: 0,
              max: 5000,
              divisions: 100,
              onChanged: (value) {
                setState(() {
                  minHeight = value.round();
                });
              },
            ),
            _buildSlider(
              label: 'Quality',
              value: quality,
              min: 0,
              max: 100,
              divisions: 100,
              onChanged: (value) {
                setState(() {
                  quality = value.round();
                });
              },
            ),
            _buildSlider(
              label: 'Rotate',
              value: rotate,
              min: -180,
              max: 180,
              divisions: 360,
              onChanged: (value) {
                setState(() {
                  rotate = value.round();
                });
              },
            ),
            _buildSlider(
              label: 'In Sample Size',
              value: inSampleSize,
              min: 1,
              max: 10,
              divisions: 9,
              onChanged: (value) {
                setState(() {
                  inSampleSize = value.round();
                });
              },
            ),
            const Text('Format:'),
            DropdownButton<CompressFormat>(
              value: format,
              onChanged: (CompressFormat? newValue) {
                setState(() {
                  format = newValue!;
                });
              },
              items: CompressFormat.values.map((CompressFormat value) {
                return DropdownMenuItem<CompressFormat>(
                  value: value,
                  child: Text(value.toString().split('.').last),
                );
              }).toList(),
            ),
            Row(
              children: [
                const Text('Auto Correction Angle:'),
                Checkbox(
                  value: autoCorrectionAngle,
                  onChanged: (value) {
                    setState(() {
                      autoCorrectionAngle = value!;
                    });
                  },
                ),
              ],
            ),
            Row(
              children: [
                const Text('Keep Exif:'),
                Checkbox(
                  value: keepExif,
                  onChanged: (value) {
                    setState(() {
                      keepExif = value!;
                    });
                  },
                ),
              ],
            ),
          ],
        ),
      ),
      actions: [
        TextButton(
          onPressed: () async {
            try {
              await widget.data
                  .compressed(
                    autoCorrectionAngle: autoCorrectionAngle,
                    format: format,
                    inSampleSize: inSampleSize,
                    keepExif: keepExif,
                    minHeight: minHeight,
                    minWidth: minWidth,
                    quality: quality,
                    rotate: rotate,
                  )
                  .then(
                    (value) => Navigator.push(
                      context,
                      MaterialPageRoute(
                        builder: (context) => ImageComparisonScreen(
                          originalImage: widget.data,
                          compressedImage: value,
                          minWidth: minWidth,
                          minHeight: minHeight,
                          quality: quality,
                          rotate: rotate,
                          inSampleSize: inSampleSize,
                          autoCorrectionAngle: autoCorrectionAngle,
                          format: format,
                          keepExif: keepExif,
                        ),
                      ),
                    ),
                  );
            } catch (e) {
              print(e);
              if (context.mounted) {
                showDialog(
                  context: context,
                  builder: (context) => ErrorDialog(
                    errorMessage: e.toString(),
                  ),
                );
              }
            }
            if (context.mounted) {
              Navigator.of(context).pop();
            }
          },
          child: const Text('Save'),
        ),
        TextButton(
          onPressed: () {
            Navigator.of(context).pop();
          },
          child: const Text('Cancel'),
        ),
      ],
    );
  }

  Widget _buildSlider({
    required String label,
    required int value,
    int? min,
    required int max,
    required int divisions,
    required ValueChanged<double> onChanged,
  }) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('$label $value'),
        Slider(
          value: value.toDouble(),
          min: min?.toDouble() ?? 0,
          max: max.toDouble(),
          divisions: divisions,
          label: value.round().toString(),
          onChanged: onChanged,
        ),
      ],
    );
  }
}

class ImageComparisonScreen extends StatelessWidget {
  final Uint8List originalImage;
  final Uint8List compressedImage;
  final int minWidth;
  final int minHeight;
  final int quality;
  final int rotate;
  final int inSampleSize;
  final bool autoCorrectionAngle;
  final CompressFormat format;
  final bool keepExif;

  const ImageComparisonScreen({
    super.key,
    required this.originalImage,
    required this.compressedImage,
    required this.minWidth,
    required this.minHeight,
    required this.quality,
    required this.rotate,
    required this.inSampleSize,
    required this.autoCorrectionAngle,
    required this.format,
    required this.keepExif,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Compression Overview'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(10.0),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: [
            Expanded(
              child: Column(
                children: [
                  Image.memory(
                    originalImage,
                    fit: BoxFit.contain,
                  ),
                  const SizedBox(height: 10),
                  Text('Size: ${originalImage.lengthInBytes} bytes'),
                ],
              ),
            ),
            const SizedBox(width: 10.0),
            Expanded(
              child: Column(
                children: [
                  Image.memory(
                    compressedImage,
                    fit: BoxFit.contain,
                  ),
                  const SizedBox(height: 10),
                  Text('Size: ${compressedImage.lengthInBytes} bytes'),
                  const SizedBox(height: 10),
                  _buildCompressionInfo(),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildCompressionInfo() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text('Compression Parameters:',
            style: TextStyle(fontWeight: FontWeight.bold)),
        Text('Min Width: $minWidth'),
        Text('Min Height: $minHeight'),
        Text('Quality: $quality'),
        Text('Rotate: $rotate'),
        Text('In Sample Size: $inSampleSize'),
        Text('Auto Correction Angle: $autoCorrectionAngle'),
        Text('Format: $format'),
        Text('Keep Exif: $keepExif'),
      ],
    );
  }
}

class ErrorDialog extends StatelessWidget {
  final String errorMessage;

  const ErrorDialog({super.key, required this.errorMessage});

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: const Text('Error'),
      content: SingleChildScrollView(
        child: Text(errorMessage),
      ),
      actions: <Widget>[
        TextButton(
          onPressed: () {
            Navigator.of(context).pop();
          },
          child: const Text('OK'),
        ),
      ],
    );
  }
}

class CompressionDialogVideo extends StatefulWidget {
  const CompressionDialogVideo({Key? key, required this.data});

  final XFile data;

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

class CompressionDialogVideoState extends State<CompressionDialogVideo> {
  VideoQuality quality = VideoQuality.DefaultQuality;
  bool deleteOrigin = false;
  int? startTime;
  int? duration;
  bool? includeAudio;
  int frameRate = 30;

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: const Text('Set Parameters'),
      content: SingleChildScrollView(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _buildQualityDropdown(),
            Row(
              children: [
                const Text('Delete Origin:'),
                Checkbox(
                  value: deleteOrigin,
                  onChanged: (value) {
                    setState(() {
                      deleteOrigin = value!;
                    });
                  },
                ),
              ],
            ),
            _buildSlider(
              label: 'Start Time',
              value: startTime ?? 0,
              min: 0,
              max: 100,
              divisions: 100,
              onChanged: (value) {
                setState(() {
                  startTime = value.round();
                });
              },
            ),
            _buildSlider(
              label: 'Duration',
              value: duration ?? 0,
              min: 0,
              max: 100,
              divisions: 100,
              onChanged: (value) {
                setState(() {
                  duration = value.round();
                });
              },
            ),
            Row(
              children: [
                const Text('Include Audio:'),
                Checkbox(
                  value: includeAudio ?? false,
                  onChanged: (value) {
                    setState(() {
                      includeAudio = value!;
                    });
                  },
                ),
              ],
            ),
            _buildSlider(
              label: 'Frame Rate',
              value: frameRate,
              min: 1,
              max: 120,
              divisions: 119,
              onChanged: (value) {
                setState(() {
                  frameRate = value.round();
                });
              },
            ),
          ],
        ),
      ),
      actions: [
        TextButton(
          onPressed: () async {
            try {
              await VideoCompress.compressVideo(
                widget.data.path,
                startTime: startTime,
                includeAudio: includeAudio,
                frameRate: frameRate,
                duration: duration,
                deleteOrigin: deleteOrigin,
              ).then(
                (value) => Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => VideoComparisonScreen(
                      originalVideo: widget.data,
                      compressedVideo: value!,
                      quality: quality,
                      deleteOrigin: deleteOrigin,
                      startTime: startTime,
                      duration: duration,
                      includeAudio: includeAudio,
                      frameRate: frameRate,
                    ),
                  ),
                ),
              );
            } catch (e) {
              print(e);
              if (context.mounted) {
                showDialog(
                  context: context,
                  builder: (context) => ErrorDialog(
                    errorMessage: e.toString(),
                  ),
                );
              }
            }
            if (context.mounted) {
              Navigator.of(context).pop();
            }
          },
          child: const Text('Save'),
        ),
        TextButton(
          onPressed: () {
            Navigator.of(context).pop();
          },
          child: const Text('Cancel'),
        ),
      ],
    );
  }

  Widget _buildSlider({
    required String label,
    required int value,
    int? min,
    required int max,
    required int divisions,
    required ValueChanged<double> onChanged,
  }) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('$label $value'),
        Slider(
          value: value.toDouble(),
          min: min?.toDouble() ?? 0,
          max: max.toDouble(),
          divisions: divisions,
          label: value.round().toString(),
          onChanged: onChanged,
        ),
      ],
    );
  }

  Widget _buildQualityDropdown() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text('Quality:'),
        DropdownButton<VideoQuality>(
          value: quality,
          onChanged: (VideoQuality? newValue) {
            setState(() {
              quality = newValue!;
            });
          },
          items: VideoQuality.values.map((VideoQuality value) {
            return DropdownMenuItem<VideoQuality>(
              value: value,
              child: Text(value.toString().split('.').last),
            );
          }).toList(),
        ),
      ],
    );
  }
}

class VideoComparisonScreen extends StatelessWidget {
  final XFile originalVideo;
  final MediaInfo compressedVideo;

  final VideoQuality quality;
  final bool deleteOrigin;
  final int? startTime;
  final int? duration;
  final bool? includeAudio;
  final int frameRate;

  const VideoComparisonScreen({
    Key? key,
    required this.originalVideo,
    required this.compressedVideo,
    required this.quality,
    required this.deleteOrigin,
    required this.startTime,
    required this.duration,
    required this.includeAudio,
    required this.frameRate,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Compression Overview'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(10.0),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: [
            Expanded(
              child: Column(
                children: [
                  VideoPreviewWidget(path: originalVideo.path, autoplay: false),
                  const SizedBox(height: 10),
                  const Text(
                    'Original Video',
                    style: TextStyle(fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 10),
                  // Display original video
                ],
              ),
            ),
            const SizedBox(width: 10.0),
            Expanded(
              child: Column(
                children: [
                  VideoPreviewWidget(path: compressedVideo.file!.path),

                  const Text(
                    'Compressed Video',
                    style: TextStyle(fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 10),
                  Text('Quality: $quality'),
                  Text('Delete Origin: $deleteOrigin'),
                  Text('Start Time: ${startTime ?? "Not Set"}'),
                  Text('Duration: ${duration ?? "Not Set"}'),
                  Text('Include Audio: ${includeAudio ?? "Not Set"}'),
                  Text('Frame Rate: $frameRate'),
                  const SizedBox(height: 10),
                  // Display compressed video
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}