Apparence-io / CamerAwesome

📸 Embedding a camera experience within your own app shouldn't be that hard. A flutter plugin to integrate awesome Android / iOS camera experience.
https://ApparenceKit.dev
MIT License
906 stars 199 forks source link

How to take photo in built-in ui #437

Open zhaoxiaohai opened 5 months ago

zhaoxiaohai commented 5 months ago
import 'dart:io';

import 'package:camerawesome/camerawesome_plugin.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:cross_file/cross_file.dart';
import 'package:better_open_file/better_open_file.dart';
import '../utils/file_utils.dart';

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Custom CamerAwesome UI',
      home: CameraPage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CameraAwesomeBuilder.awesome(
        saveConfig: SaveConfig.photo(pathBuilder: (sensors) async {
          final Directory extDir = await getTemporaryDirectory();
          final testDir =
              await Directory('${extDir.path}/test').create(recursive: true);
          const String fileExtension ='jpg' ;
          final String filePath =
              '${testDir.path}/${DateTime.now().millisecondsSinceEpoch}.$fileExtension';
          return SingleCaptureRequest(filePath, sensors.first);
        }),
        sensorConfig: SensorConfig.single(
          sensor: Sensor.position(SensorPosition.back),
          aspectRatio: CameraAspectRatios.ratio_1_1,
        ),
        previewFit: CameraPreviewFit.contain,
        previewPadding: const EdgeInsets.only(left: 150, top: 100),
        previewAlignment: Alignment.topRight,
        // Buttons of CamerAwesome UI will use this theme
        theme: AwesomeTheme(
          bottomActionsBackgroundColor: Colors.cyan.withOpacity(0.5),
          buttonTheme: AwesomeButtonTheme(
            backgroundColor: Colors.cyan.withOpacity(0.5),
            iconSize: 20,
            foregroundColor: Colors.white,
            padding: const EdgeInsets.all(16),
            // Tap visual feedback (ripple, bounce...)
            buttonBuilder: (child, onTap) {
              return ClipOval(
                child: Material(
                  color: Colors.transparent,
                  shape: const CircleBorder(),
                  child: InkWell(
                    splashColor: Colors.cyan,
                    highlightColor: Colors.cyan.withOpacity(0.5),
                    onTap: onTap,
                    child: child,
                  ),
                ),
              );
            },
          ),
        ),
        topActionsBuilder: (state) => AwesomeTopActions(
          padding: EdgeInsets.zero,
          state: state,
          children: [
            Expanded(
              child: AwesomeLocationButton(
                state: state as PhotoCameraState,
              ),
            ),
          ],
        ),
        middleContentBuilder: (state) {
          return Column(
            children: [
              const Spacer(),
              Builder(builder: (context) {
                return Container(
                  color: AwesomeThemeProvider.of(context)
                      .theme
                      .bottomActionsBackgroundColor,
                  child: const Align(
                    alignment: Alignment.bottomCenter,
                    child: Padding(
                      padding: EdgeInsets.only(bottom: 10, top: 10),
                      child: Text(
                        "Take your best shot!",
                        style: TextStyle(
                          color: Colors.white,
                          fontWeight: FontWeight.bold,
                          fontStyle: FontStyle.italic,
                        ),
                      ),
                    ),
                  ),
                );
              }),
            ],
          );
        },
        bottomActionsBuilder: (state) => AwesomeBottomActions(
          state: state,
          left: AwesomeFlashButton(
            state: state,
          ),
          right: AwesomeCameraSwitchButton(
            state: state,
            scale: 1.0,
            onSwitchTap: (state) {
              state.switchCameraSensor(
                aspectRatio: state.sensorConfig.aspectRatio,
              );
            },
          ),
        ),
      ),
    );
  }
}

I don't know how to take photo path when capture button taped

g-apparence commented 5 months ago

Hi, could you detail more about what you are trying to achieve?

zhaoxiaohai commented 5 months ago

Hi, could you detail more about what you are trying to achieve?

I want to obtain the picture file after taking a photo, so that I can submit it to the server. However, in the current demo, I did not see any method to get the picture. My current usage is shown in the code above.

difafadhilj commented 4 months ago
lib/ui/views/camera/camera_view.dart:22:32: Error: The getter 'filePath' isn't defined for the class 'MediaCapture'.
 - 'MediaCapture' is from 'package:camerawesome/src/orchestrator/models/media_capture.dart' ('../../../../.pub-cache/hosted/pub.dev/camerawesome-2.0.0+1/lib/src/orchestrator/models/media_capture.dart').

Same here, the filePath getter isn't defined when I want to obtain the file path.

g-apparence commented 4 months ago

Here is an example on how to do this

The build method

CameraAwesomeBuilder.custom(
        builder: (state, preview) {
          return AwesomeCameraLayout(
            state: state,
            topActions: const SizedBox.shrink(),
            bottomActions: CameraBottomActions(
              state: state,
              onVideo: (video) => onVideo(ref, video),
              onVideoFailed: (videoError) {
                // TODO show error
              },
              onPhoto: (photo) => onPhoto(state, ref, photo),
            ),
            middleContent: const SizedBox.shrink(),
          );
        },
        saveConfig: SaveConfig.photoAndVideo(
          videoPathBuilder: _videoPathBuilder,
          photoPathBuilder: _imagePathBuilder,
          // ignore: avoid_redundant_argument_values
          initialCaptureMode: CaptureMode.video,
          videoOptions: VideoOptions(
            enableAudio: true,
            ios: CupertinoVideoOptions(
              fps: 30,
            ),
            android: AndroidVideoOptions(
              bitrate: 6000000,
              fallbackStrategy: QualityFallbackStrategy.lower,
            ),
          ),
        ),
        ....
      ),

The on video (This is where you can do this)

void onVideo(WidgetRef ref, CaptureRequest request) {
    final path = request.when(
      single: (single) => single.file!.path,
      multiple: (multiple) => multiple.fileBySensor.values.first!.path,
    );
   // Do what you need here <<<<<<<<
  }

this should help you doing what you want @zhaoxiaohai @difafadhilj

ps9310 commented 4 months ago

Here is an example on how to do this

The build method

CameraAwesomeBuilder.custom(
        builder: (state, preview) {
          return AwesomeCameraLayout(
            state: state,
            topActions: const SizedBox.shrink(),
            bottomActions: CameraBottomActions(
              state: state,
              onVideo: (video) => onVideo(ref, video),
              onVideoFailed: (videoError) {
                // TODO show error
              },
              onPhoto: (photo) => onPhoto(state, ref, photo),
            ),
            middleContent: const SizedBox.shrink(),
          );
        },
        saveConfig: SaveConfig.photoAndVideo(
          videoPathBuilder: _videoPathBuilder,
          photoPathBuilder: _imagePathBuilder,
          // ignore: avoid_redundant_argument_values
          initialCaptureMode: CaptureMode.video,
          videoOptions: VideoOptions(
            enableAudio: true,
            ios: CupertinoVideoOptions(
              fps: 30,
            ),
            android: AndroidVideoOptions(
              bitrate: 6000000,
              fallbackStrategy: QualityFallbackStrategy.lower,
            ),
          ),
        ),
        ....
      ),

The on video (This is where you can do this)

void onVideo(WidgetRef ref, CaptureRequest request) {
    final path = request.when(
      single: (single) => single.file!.path,
      multiple: (multiple) => multiple.fileBySensor.values.first!.path,
    );
   // Do what you need here <<<<<<<<
  }

this should help you doing what you want @zhaoxiaohai @difafadhilj

@g-apparence I can't find the CameraBottomActions widget in the master branch. However, I did notice AwesomeBottomActions and AwesomeCaptureButton, although neither includes the onPhoto or onVideo callbacks.

g-apparence commented 4 months ago

@ps9310 Sorry I wrote this too fast. This is a current wip in the main branch to make this more intuitive. I'm not completely satisfied with it yet.

Either you can do this with the old way

state.captureState$.listen(
            (event) {
              if (event?.status == MediaCaptureStatus.success) {
                event?.captureRequest.when(single: (single) {
                  yourImageFunctionToProcessTheFile(single.file!.path);
                });
              }
            },
          );

Where the state can be accessed within builder command in custom factory or any topActionsBuilder, bottomActionsBuilder... in awesome factory.

This is not very intuitive so I'm looking to improve it.

g-apparence commented 4 months ago

It's not published on pub.dev yet but here is the new callback event

onMediaCaptureEvent: (event) {
    switch ((event.status, event.isPicture, event.isVideo)) {
        case (MediaCaptureStatus.capturing, true, false):
            debugPrint('Capturing picture...');
        case (MediaCaptureStatus.success, true, false):
            event.captureRequest.when(
                single: (single) {
                debugPrint('Picture saved: ${single.file?.path}');
                },
                multiple: (multiple) {
                multiple.fileBySensor.forEach((key, value) {
                    debugPrint('multiple image taken: $key ${value?.path}');
                });
                },
            );
        case (MediaCaptureStatus.failure, true, false):
            debugPrint('Failed to capture picture: ${event.exception}');
        case (MediaCaptureStatus.capturing, false, true):
            debugPrint('Capturing video...');
        case (MediaCaptureStatus.success, false, true):
            event.captureRequest.when(
                single: (single) {
                    debugPrint('Video saved: ${single.file?.path}');
                },
                multiple: (multiple) {
                    multiple.fileBySensor.forEach((key, value) {
                        debugPrint('multiple video taken: $key ${value?.path}');
                    });
                },
            );
        case (MediaCaptureStatus.failure, false, true):
            debugPrint('Failed to capture video: ${event.exception}');
        default:
            debugPrint('Unknown event: $event');
    }
},

Should invest in rewriting the MediaCapture with Dart3 sealed class later

Honelign commented 1 month ago

@g-apparence onmedia capture event is not defined onMediaCaptureEvent:(event) { switch ((event.status, event.isPicture, event.isVideo)) { case (MediaCaptureStatus.capturing, true, false): debugPrint('Capturing picture...'); case (MediaCaptureStatus.success, true, false): event.captureRequest.when( single: (single) { debugPrint('Picture saved: ${single.file?.path}'); }, multiple: (multiple) { multiple.fileBySensor.forEach((key, value) { debugPrint('multiple image taken: $key ${value?.path}'); }); }, ); case (MediaCaptureStatus.failure, true, false): debugPrint('Failed to capture picture: ${event.exception}'); case (MediaCaptureStatus.capturing, false, true): debugPrint('Capturing video...'); case (MediaCaptureStatus.success, false, true): event.captureRequest.when( single: (single) { debugPrint('Video saved: ${single.file?.path}'); }, multiple: (multiple) { multiple.fileBySensor.forEach((key, value) { debugPrint('multiple video taken: $key ${value?.path}'); }); }, ); case (MediaCaptureStatus.failure, false, true): debugPrint('Failed to capture video: ${event.exception}'); default: debugPrint('Unknown event: $event'); } },