mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 13:08:42 +00:00
splitting the logic for #327
This commit is contained in:
parent
3265b6259c
commit
b3ec419411
6 changed files with 266 additions and 357 deletions
|
|
@ -1,23 +1,21 @@
|
||||||
import 'package:camera/camera.dart';
|
import 'package:camera/camera.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:screenshot/screenshot.dart';
|
import 'package:screenshot/screenshot.dart';
|
||||||
|
import 'package:twonly/src/views/camera/camera_preview_components/main_camera_controller.dart';
|
||||||
import 'package:twonly/src/views/components/media_view_sizing.dart';
|
import 'package:twonly/src/views/components/media_view_sizing.dart';
|
||||||
|
|
||||||
class HomeViewCameraPreview extends StatelessWidget {
|
class MainCameraPreview extends StatelessWidget {
|
||||||
const HomeViewCameraPreview({
|
const MainCameraPreview({
|
||||||
required this.controller,
|
required this.mainCameraController,
|
||||||
required this.screenshotController,
|
|
||||||
required this.customPaint,
|
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final CameraController? controller;
|
final MainCameraController mainCameraController;
|
||||||
final CustomPaint? customPaint;
|
|
||||||
final ScreenshotController screenshotController;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (controller == null || !controller!.value.isInitialized) {
|
if (mainCameraController.cameraController == null ||
|
||||||
|
!mainCameraController.cameraController!.value.isInitialized) {
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
return Positioned.fill(
|
return Positioned.fill(
|
||||||
|
|
@ -26,58 +24,21 @@ class HomeViewCameraPreview extends StatelessWidget {
|
||||||
additionalPadding: 59,
|
additionalPadding: 59,
|
||||||
bottomNavigation: Container(),
|
bottomNavigation: Container(),
|
||||||
child: Screenshot(
|
child: Screenshot(
|
||||||
controller: screenshotController,
|
controller: mainCameraController.screenshotController,
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: 9 / 16,
|
aspectRatio: 9 / 16,
|
||||||
child: ClipRect(
|
child: ClipRect(
|
||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: controller!.value.previewSize!.height,
|
width: mainCameraController
|
||||||
height: controller!.value.previewSize!.width,
|
.cameraController!.value.previewSize!.height,
|
||||||
child: CameraPreview(controller!, child: customPaint),
|
height: mainCameraController
|
||||||
),
|
.cameraController!.value.previewSize!.width,
|
||||||
),
|
child: CameraPreview(
|
||||||
),
|
mainCameraController.cameraController!,
|
||||||
),
|
child: mainCameraController.customPaint,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SendToCameraPreview extends StatelessWidget {
|
|
||||||
const SendToCameraPreview({
|
|
||||||
required this.cameraController,
|
|
||||||
required this.screenshotController,
|
|
||||||
required this.customPaint,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
final CameraController? cameraController;
|
|
||||||
final ScreenshotController screenshotController;
|
|
||||||
final CustomPaint? customPaint;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if (cameraController == null || !cameraController!.value.isInitialized) {
|
|
||||||
return Container();
|
|
||||||
}
|
|
||||||
return Positioned.fill(
|
|
||||||
child: MediaViewSizing(
|
|
||||||
requiredHeight: 0,
|
|
||||||
additionalPadding: 59,
|
|
||||||
child: Screenshot(
|
|
||||||
controller: screenshotController,
|
|
||||||
child: AspectRatio(
|
|
||||||
aspectRatio: 9 / 16,
|
|
||||||
child: ClipRect(
|
|
||||||
child: FittedBox(
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
child: SizedBox(
|
|
||||||
width: cameraController!.value.previewSize!.height,
|
|
||||||
height: cameraController!.value.previewSize!.width,
|
|
||||||
child: CameraPreview(cameraController!, child: customPaint),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:camera/camera.dart';
|
import 'package:camera/camera.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
@ -9,7 +10,6 @@ import 'package:flutter_volume_controller/flutter_volume_controller.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:screenshot/screenshot.dart';
|
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
|
@ -22,6 +22,7 @@ import 'package:twonly/src/views/camera/camera_preview_components/send_to.dart';
|
||||||
import 'package:twonly/src/views/camera/camera_preview_components/video_recording_time.dart';
|
import 'package:twonly/src/views/camera/camera_preview_components/video_recording_time.dart';
|
||||||
import 'package:twonly/src/views/camera/camera_preview_components/zoom_selector.dart';
|
import 'package:twonly/src/views/camera/camera_preview_components/zoom_selector.dart';
|
||||||
import 'package:twonly/src/views/camera/image_editor/action_button.dart';
|
import 'package:twonly/src/views/camera/image_editor/action_button.dart';
|
||||||
|
import 'package:twonly/src/views/camera/camera_preview_components/main_camera_controller.dart';
|
||||||
import 'package:twonly/src/views/camera/share_image_editor_view.dart';
|
import 'package:twonly/src/views/camera/share_image_editor_view.dart';
|
||||||
import 'package:twonly/src/views/components/media_view_sizing.dart';
|
import 'package:twonly/src/views/components/media_view_sizing.dart';
|
||||||
import 'package:twonly/src/views/home.view.dart';
|
import 'package:twonly/src/views/home.view.dart';
|
||||||
|
|
@ -89,20 +90,14 @@ class SelectedCameraDetails {
|
||||||
|
|
||||||
class CameraPreviewControllerView extends StatelessWidget {
|
class CameraPreviewControllerView extends StatelessWidget {
|
||||||
const CameraPreviewControllerView({
|
const CameraPreviewControllerView({
|
||||||
required this.cameraController,
|
required this.mainController,
|
||||||
required this.selectCamera,
|
|
||||||
required this.selectedCameraDetails,
|
|
||||||
required this.screenshotController,
|
|
||||||
required this.isVisible,
|
required this.isVisible,
|
||||||
super.key,
|
super.key,
|
||||||
this.sendToGroup,
|
this.sendToGroup,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final MainCameraController mainController;
|
||||||
final Group? sendToGroup;
|
final Group? sendToGroup;
|
||||||
final Future<CameraController?> Function(int sCameraId, bool init)
|
|
||||||
selectCamera;
|
|
||||||
final CameraController? cameraController;
|
|
||||||
final SelectedCameraDetails selectedCameraDetails;
|
|
||||||
final ScreenshotController screenshotController;
|
|
||||||
final bool isVisible;
|
final bool isVisible;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -114,16 +109,13 @@ class CameraPreviewControllerView extends StatelessWidget {
|
||||||
if (snap.data!) {
|
if (snap.data!) {
|
||||||
return CameraPreviewView(
|
return CameraPreviewView(
|
||||||
sendToGroup: sendToGroup,
|
sendToGroup: sendToGroup,
|
||||||
selectCamera: selectCamera,
|
mainCameraController: mainController,
|
||||||
cameraController: cameraController,
|
|
||||||
selectedCameraDetails: selectedCameraDetails,
|
|
||||||
screenshotController: screenshotController,
|
|
||||||
isVisible: isVisible,
|
isVisible: isVisible,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return PermissionHandlerView(
|
return PermissionHandlerView(
|
||||||
onSuccess: () {
|
onSuccess: () {
|
||||||
selectCamera(0, true);
|
mainController.selectCamera(0, true);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -137,22 +129,14 @@ class CameraPreviewControllerView extends StatelessWidget {
|
||||||
|
|
||||||
class CameraPreviewView extends StatefulWidget {
|
class CameraPreviewView extends StatefulWidget {
|
||||||
const CameraPreviewView({
|
const CameraPreviewView({
|
||||||
required this.selectCamera,
|
required this.mainCameraController,
|
||||||
required this.cameraController,
|
|
||||||
required this.selectedCameraDetails,
|
|
||||||
required this.screenshotController,
|
|
||||||
required this.isVisible,
|
required this.isVisible,
|
||||||
super.key,
|
super.key,
|
||||||
this.sendToGroup,
|
this.sendToGroup,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final MainCameraController mainCameraController;
|
||||||
final Group? sendToGroup;
|
final Group? sendToGroup;
|
||||||
final Future<CameraController?> Function(
|
|
||||||
int sCameraId,
|
|
||||||
bool init,
|
|
||||||
) selectCamera;
|
|
||||||
final CameraController? cameraController;
|
|
||||||
final SelectedCameraDetails selectedCameraDetails;
|
|
||||||
final ScreenshotController screenshotController;
|
|
||||||
final bool isVisible;
|
final bool isVisible;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -174,6 +158,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
final GlobalKey keyTriggerButton = GlobalKey();
|
final GlobalKey keyTriggerButton = GlobalKey();
|
||||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
|
MainCameraController get mc => widget.mainCameraController;
|
||||||
|
|
||||||
StreamSubscription<HardwareButton>? androidVolumeDownSub;
|
StreamSubscription<HardwareButton>? androidVolumeDownSub;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -282,18 +268,18 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateScaleFactor(double newScale) async {
|
Future<void> updateScaleFactor(double newScale) async {
|
||||||
if (widget.selectedCameraDetails.scaleFactor == newScale ||
|
if (mc.selectedCameraDetails.scaleFactor == newScale ||
|
||||||
widget.cameraController == null) {
|
mc.cameraController == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await widget.cameraController?.setZoomLevel(
|
await mc.cameraController?.setZoomLevel(
|
||||||
newScale.clamp(
|
newScale.clamp(
|
||||||
widget.selectedCameraDetails.minAvailableZoom,
|
mc.selectedCameraDetails.minAvailableZoom,
|
||||||
widget.selectedCameraDetails.maxAvailableZoom,
|
mc.selectedCameraDetails.maxAvailableZoom,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
setState(() {
|
setState(() {
|
||||||
widget.selectedCameraDetails.scaleFactor = newScale;
|
mc.selectedCameraDetails.scaleFactor = newScale;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -325,31 +311,31 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
setState(() {
|
setState(() {
|
||||||
_sharePreviewIsShown = true;
|
_sharePreviewIsShown = true;
|
||||||
});
|
});
|
||||||
if (widget.selectedCameraDetails.isFlashOn) {
|
if (mc.selectedCameraDetails.isFlashOn) {
|
||||||
if (isFront) {
|
if (isFront) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_showSelfieFlash = true;
|
_showSelfieFlash = true;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await widget.cameraController?.setFlashMode(FlashMode.torch);
|
await mc.cameraController?.setFlashMode(FlashMode.torch);
|
||||||
}
|
}
|
||||||
await Future.delayed(const Duration(milliseconds: 1000));
|
await Future.delayed(const Duration(milliseconds: 1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
await widget.cameraController?.pausePreview();
|
await mc.cameraController?.pausePreview();
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Platform.isIOS) {
|
if (Platform.isIOS) {
|
||||||
// android has a problem with this. Flash is turned off in the pausePreview function.
|
// android has a problem with this. Flash is turned off in the pausePreview function.
|
||||||
await widget.cameraController?.setFlashMode(FlashMode.off);
|
await mc.cameraController?.setFlashMode(FlashMode.off);
|
||||||
}
|
}
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
imageBytes = widget.screenshotController
|
imageBytes = mc.screenshotController
|
||||||
.capture(pixelRatio: MediaQuery.of(context).devicePixelRatio);
|
.capture(pixelRatio: MediaQuery.of(context).devicePixelRatio);
|
||||||
|
|
||||||
if (await pushMediaEditor(imageBytes, null)) {
|
if (await pushMediaEditor(imageBytes, null)) {
|
||||||
|
|
@ -426,33 +412,33 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
await widget.selectCamera(
|
await mc.selectCamera(
|
||||||
widget.selectedCameraDetails.cameraId,
|
mc.selectedCameraDetails.cameraId,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get isFront =>
|
bool get isFront =>
|
||||||
widget.cameraController?.description.lensDirection ==
|
mc.cameraController?.description.lensDirection ==
|
||||||
CameraLensDirection.front;
|
CameraLensDirection.front;
|
||||||
|
|
||||||
Future<void> onPanUpdate(dynamic details) async {
|
Future<void> onPanUpdate(dynamic details) async {
|
||||||
if (isFront || details == null) {
|
if (isFront || details == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (widget.cameraController == null ||
|
if (mc.cameraController == null ||
|
||||||
!widget.cameraController!.value.isInitialized) {
|
!mc.cameraController!.value.isInitialized) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
widget.selectedCameraDetails.scaleFactor = (_baseScaleFactor +
|
mc.selectedCameraDetails.scaleFactor = (_baseScaleFactor +
|
||||||
// ignore: avoid_dynamic_calls
|
// ignore: avoid_dynamic_calls
|
||||||
(_basePanY - (details.localPosition.dy as double)) / 30)
|
(_basePanY - (details.localPosition.dy as double)) / 30)
|
||||||
.clamp(1, widget.selectedCameraDetails.maxAvailableZoom);
|
.clamp(1, mc.selectedCameraDetails.maxAvailableZoom);
|
||||||
|
|
||||||
await widget.cameraController!
|
await mc.cameraController!
|
||||||
.setZoomLevel(widget.selectedCameraDetails.scaleFactor);
|
.setZoomLevel(mc.selectedCameraDetails.scaleFactor);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
@ -509,8 +495,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> startVideoRecording() async {
|
Future<void> startVideoRecording() async {
|
||||||
if (widget.cameraController != null &&
|
if (mc.cameraController != null &&
|
||||||
widget.cameraController!.value.isRecordingVideo) {
|
mc.cameraController!.value.isRecordingVideo) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -518,7 +504,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await widget.cameraController?.startVideoRecording();
|
await mc.cameraController?.startVideoRecording();
|
||||||
_videoRecordingTimer =
|
_videoRecordingTimer =
|
||||||
Timer.periodic(const Duration(milliseconds: 15), (timer) {
|
Timer.periodic(const Duration(milliseconds: 15), (timer) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -556,8 +542,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
_isVideoRecording = false;
|
_isVideoRecording = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (widget.cameraController == null ||
|
if (mc.cameraController == null ||
|
||||||
!widget.cameraController!.value.isRecordingVideo) {
|
!mc.cameraController!.value.isRecordingVideo) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -566,9 +552,9 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final videoPath = await widget.cameraController?.stopVideoRecording();
|
final videoPath = await mc.cameraController?.stopVideoRecording();
|
||||||
if (videoPath == null) return;
|
if (videoPath == null) return;
|
||||||
await widget.cameraController?.pausePreview();
|
await mc.cameraController?.pausePreview();
|
||||||
if (await pushMediaEditor(null, File(videoPath.path))) {
|
if (await pushMediaEditor(null, File(videoPath.path))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -595,8 +581,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (widget.selectedCameraDetails.cameraId >= gCameras.length ||
|
if (mc.selectedCameraDetails.cameraId >= gCameras.length ||
|
||||||
widget.cameraController == null) {
|
mc.cameraController == null) {
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
return MediaViewSizing(
|
return MediaViewSizing(
|
||||||
|
|
@ -610,14 +596,14 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
_basePanY = details.localPosition.dy;
|
_basePanY = details.localPosition.dy;
|
||||||
_baseScaleFactor = widget.selectedCameraDetails.scaleFactor;
|
_baseScaleFactor = mc.selectedCameraDetails.scaleFactor;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onLongPressMoveUpdate: onPanUpdate,
|
onLongPressMoveUpdate: onPanUpdate,
|
||||||
onLongPressStart: (details) {
|
onLongPressStart: (details) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_basePanY = details.localPosition.dy;
|
_basePanY = details.localPosition.dy;
|
||||||
_baseScaleFactor = widget.selectedCameraDetails.scaleFactor;
|
_baseScaleFactor = mc.selectedCameraDetails.scaleFactor;
|
||||||
});
|
});
|
||||||
// Get the position of the pointer
|
// Get the position of the pointer
|
||||||
final renderBox =
|
final renderBox =
|
||||||
|
|
@ -670,29 +656,29 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
Icons.repeat_rounded,
|
Icons.repeat_rounded,
|
||||||
tooltipText: context.lang.switchFrontAndBackCamera,
|
tooltipText: context.lang.switchFrontAndBackCamera,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await widget.selectCamera(
|
await mc.selectCamera(
|
||||||
(widget.selectedCameraDetails.cameraId + 1) % 2,
|
(mc.selectedCameraDetails.cameraId + 1) % 2,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ActionButton(
|
ActionButton(
|
||||||
widget.selectedCameraDetails.isFlashOn
|
mc.selectedCameraDetails.isFlashOn
|
||||||
? Icons.flash_on_rounded
|
? Icons.flash_on_rounded
|
||||||
: Icons.flash_off_rounded,
|
: Icons.flash_off_rounded,
|
||||||
tooltipText: context.lang.toggleFlashLight,
|
tooltipText: context.lang.toggleFlashLight,
|
||||||
color: widget.selectedCameraDetails.isFlashOn
|
color: mc.selectedCameraDetails.isFlashOn
|
||||||
? Colors.white
|
? Colors.white
|
||||||
: Colors.white.withAlpha(160),
|
: Colors.white.withAlpha(160),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (widget.selectedCameraDetails.isFlashOn) {
|
if (mc.selectedCameraDetails.isFlashOn) {
|
||||||
await widget.cameraController
|
await mc.cameraController
|
||||||
?.setFlashMode(FlashMode.off);
|
?.setFlashMode(FlashMode.off);
|
||||||
widget.selectedCameraDetails.isFlashOn = false;
|
mc.selectedCameraDetails.isFlashOn = false;
|
||||||
} else {
|
} else {
|
||||||
await widget.cameraController
|
await mc.cameraController
|
||||||
?.setFlashMode(FlashMode.always);
|
?.setFlashMode(FlashMode.always);
|
||||||
widget.selectedCameraDetails.isFlashOn = true;
|
mc.selectedCameraDetails.isFlashOn = true;
|
||||||
}
|
}
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
|
|
@ -719,20 +705,19 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
if (widget.cameraController!.value.isInitialized &&
|
if (mc.cameraController!.value.isInitialized &&
|
||||||
widget.selectedCameraDetails.isZoomAble &&
|
mc.selectedCameraDetails.isZoomAble &&
|
||||||
!isFront &&
|
!isFront &&
|
||||||
!_isVideoRecording)
|
!_isVideoRecording)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 120,
|
width: 120,
|
||||||
child: CameraZoomButtons(
|
child: CameraZoomButtons(
|
||||||
key: widget.key,
|
key: widget.key,
|
||||||
scaleFactor:
|
scaleFactor: mc.selectedCameraDetails.scaleFactor,
|
||||||
widget.selectedCameraDetails.scaleFactor,
|
|
||||||
updateScaleFactor: updateScaleFactor,
|
updateScaleFactor: updateScaleFactor,
|
||||||
selectCamera: widget.selectCamera,
|
selectCamera: mc.selectCamera,
|
||||||
selectedCameraDetails: widget.selectedCameraDetails,
|
selectedCameraDetails: mc.selectedCameraDetails,
|
||||||
controller: widget.cameraController!,
|
controller: mc.cameraController!,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:camera/camera.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:google_mlkit_barcode_scanning/google_mlkit_barcode_scanning.dart';
|
||||||
|
import 'package:screenshot/screenshot.dart';
|
||||||
|
import 'package:twonly/src/views/camera/camera_preview_components/camera_preview_controller_view.dart';
|
||||||
|
import 'package:twonly/src/views/camera/painters/barcode_detector_painter.dart';
|
||||||
|
|
||||||
|
class MainCameraController {
|
||||||
|
late void Function() setState;
|
||||||
|
CameraController? cameraController;
|
||||||
|
ScreenshotController screenshotController = ScreenshotController();
|
||||||
|
SelectedCameraDetails selectedCameraDetails = SelectedCameraDetails();
|
||||||
|
bool initCameraStarted = true;
|
||||||
|
|
||||||
|
Future<void> closeCamera() async {
|
||||||
|
await cameraController?.stopImageStream();
|
||||||
|
await cameraController?.dispose();
|
||||||
|
cameraController = null;
|
||||||
|
initCameraStarted = false;
|
||||||
|
selectedCameraDetails = SelectedCameraDetails();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<CameraController?> selectCamera(int sCameraId, bool init) async {
|
||||||
|
initCameraStarted = true;
|
||||||
|
final opts = await initializeCameraController(
|
||||||
|
selectedCameraDetails,
|
||||||
|
sCameraId,
|
||||||
|
init,
|
||||||
|
);
|
||||||
|
if (opts != null) {
|
||||||
|
selectedCameraDetails = opts.$1;
|
||||||
|
cameraController = opts.$2;
|
||||||
|
}
|
||||||
|
if (cameraController?.description.lensDirection ==
|
||||||
|
CameraLensDirection.back) {
|
||||||
|
await cameraController?.startImageStream(_processCameraImage);
|
||||||
|
}
|
||||||
|
setState();
|
||||||
|
return cameraController;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> toggleSelectedCamera() async {
|
||||||
|
if (cameraController == null) return;
|
||||||
|
// do not allow switching camera when recording
|
||||||
|
if (cameraController!.value.isRecordingVideo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await cameraController!.stopImageStream();
|
||||||
|
await cameraController!.dispose();
|
||||||
|
cameraController = null;
|
||||||
|
await selectCamera((selectedCameraDetails.cameraId + 1) % 2, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
final BarcodeScanner _barcodeScanner = BarcodeScanner();
|
||||||
|
bool _isBusy = false;
|
||||||
|
CustomPaint? customPaint;
|
||||||
|
|
||||||
|
final Map<DeviceOrientation, int> _orientations = {
|
||||||
|
DeviceOrientation.portraitUp: 0,
|
||||||
|
DeviceOrientation.landscapeLeft: 90,
|
||||||
|
DeviceOrientation.portraitDown: 180,
|
||||||
|
DeviceOrientation.landscapeRight: 270,
|
||||||
|
};
|
||||||
|
|
||||||
|
void _processCameraImage(CameraImage image) {
|
||||||
|
final inputImage = _inputImageFromCameraImage(image);
|
||||||
|
if (inputImage == null) return;
|
||||||
|
_processImage(inputImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
InputImage? _inputImageFromCameraImage(CameraImage image) {
|
||||||
|
if (cameraController == null) return null;
|
||||||
|
|
||||||
|
// get image rotation
|
||||||
|
// it is used in android to convert the InputImage from Dart to Java: https://github.com/flutter-ml/google_ml_kit_flutter/blob/master/packages/google_mlkit_commons/android/src/main/java/com/google_mlkit_commons/InputImageConverter.java
|
||||||
|
// `rotation` is not used in iOS to convert the InputImage from Dart to Obj-C: https://github.com/flutter-ml/google_ml_kit_flutter/blob/master/packages/google_mlkit_commons/ios/Classes/MLKVisionImage%2BFlutterPlugin.m
|
||||||
|
// in both platforms `rotation` and `camera.lensDirection` can be used to compensate `x` and `y` coordinates on a canvas: https://github.com/flutter-ml/google_ml_kit_flutter/blob/master/packages/example/lib/vision_detector_views/painters/coordinates_translator.dart
|
||||||
|
final camera = cameraController!.description;
|
||||||
|
final sensorOrientation = camera.sensorOrientation;
|
||||||
|
// print(
|
||||||
|
// 'lensDirection: ${camera.lensDirection}, sensorOrientation: $sensorOrientation, ${_controller?.value.deviceOrientation} ${_controller?.value.lockedCaptureOrientation} ${_controller?.value.isCaptureOrientationLocked}');
|
||||||
|
InputImageRotation? rotation;
|
||||||
|
if (Platform.isIOS) {
|
||||||
|
rotation = InputImageRotationValue.fromRawValue(sensorOrientation);
|
||||||
|
} else if (Platform.isAndroid) {
|
||||||
|
var rotationCompensation =
|
||||||
|
_orientations[cameraController!.value.deviceOrientation];
|
||||||
|
if (rotationCompensation == null) return null;
|
||||||
|
if (camera.lensDirection == CameraLensDirection.front) {
|
||||||
|
// front-facing
|
||||||
|
rotationCompensation = (sensorOrientation + rotationCompensation) % 360;
|
||||||
|
} else {
|
||||||
|
// back-facing
|
||||||
|
rotationCompensation =
|
||||||
|
(sensorOrientation - rotationCompensation + 360) % 360;
|
||||||
|
}
|
||||||
|
rotation = InputImageRotationValue.fromRawValue(rotationCompensation);
|
||||||
|
// print('rotationCompensation: $rotationCompensation');
|
||||||
|
}
|
||||||
|
if (rotation == null) return null;
|
||||||
|
// print('final rotation: $rotation');
|
||||||
|
|
||||||
|
// get image format
|
||||||
|
var format = InputImageFormatValue.fromRawValue(image.format.raw as int);
|
||||||
|
// validate format depending on platform
|
||||||
|
// only supported formats:
|
||||||
|
// * nv21 for Android
|
||||||
|
// * bgra8888 for iOS
|
||||||
|
if (Platform.isAndroid && format == InputImageFormat.yuv420) {
|
||||||
|
// https://developer.android.com/reference/kotlin/androidx/camera/core/ImageAnalysis#OUTPUT_IMAGE_FORMAT_NV21()
|
||||||
|
format = InputImageFormat.nv21;
|
||||||
|
}
|
||||||
|
if (format == null ||
|
||||||
|
(Platform.isAndroid && format != InputImageFormat.nv21) ||
|
||||||
|
(Platform.isIOS && format != InputImageFormat.bgra8888)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// since format is constraint to nv21 or bgra8888, both only have one plane
|
||||||
|
if (image.planes.length != 1) return null;
|
||||||
|
final plane = image.planes.first;
|
||||||
|
|
||||||
|
// compose InputImage using bytes
|
||||||
|
return InputImage.fromBytes(
|
||||||
|
bytes: plane.bytes,
|
||||||
|
metadata: InputImageMetadata(
|
||||||
|
size: Size(image.width.toDouble(), image.height.toDouble()),
|
||||||
|
rotation: rotation, // used only in Android
|
||||||
|
format: format, // used only in iOS
|
||||||
|
bytesPerRow: plane.bytesPerRow, // used only in iOS
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _processImage(InputImage inputImage) async {
|
||||||
|
if (_isBusy) return;
|
||||||
|
_isBusy = true;
|
||||||
|
final barcodes = await _barcodeScanner.processImage(inputImage);
|
||||||
|
if (inputImage.metadata?.size != null &&
|
||||||
|
inputImage.metadata?.rotation != null &&
|
||||||
|
cameraController != null) {
|
||||||
|
final painter = BarcodeDetectorPainter(
|
||||||
|
barcodes,
|
||||||
|
inputImage.metadata!.size,
|
||||||
|
inputImage.metadata!.rotation,
|
||||||
|
cameraController!.description.lensDirection,
|
||||||
|
);
|
||||||
|
customPaint = CustomPaint(painter: painter);
|
||||||
|
}
|
||||||
|
_isBusy = false;
|
||||||
|
setState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ import 'dart:math';
|
||||||
import 'package:camera/camera.dart';
|
import 'package:camera/camera.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/views/camera/camera_preview_controller_view.dart';
|
import 'package:twonly/src/views/camera/camera_preview_components/camera_preview_controller_view.dart';
|
||||||
|
|
||||||
class CameraZoomButtons extends StatefulWidget {
|
class CameraZoomButtons extends StatefulWidget {
|
||||||
const CameraZoomButtons({
|
const CameraZoomButtons({
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:camera/camera.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:screenshot/screenshot.dart';
|
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/views/camera/camera_preview_components/camera_preview.dart';
|
import 'package:twonly/src/views/camera/camera_preview_components/camera_preview.dart';
|
||||||
import 'package:twonly/src/views/camera/camera_preview_controller_view.dart';
|
import 'package:twonly/src/views/camera/camera_preview_components/camera_preview_controller_view.dart';
|
||||||
|
import 'package:twonly/src/views/camera/camera_preview_components/main_camera_controller.dart';
|
||||||
|
|
||||||
class CameraSendToView extends StatefulWidget {
|
class CameraSendToView extends StatefulWidget {
|
||||||
const CameraSendToView(this.sendToGroup, {super.key});
|
const CameraSendToView(this.sendToGroup, {super.key});
|
||||||
|
|
@ -15,71 +13,36 @@ class CameraSendToView extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class CameraSendToViewState extends State<CameraSendToView> {
|
class CameraSendToViewState extends State<CameraSendToView> {
|
||||||
CameraController? cameraController;
|
final MainCameraController _mainCameraController = MainCameraController();
|
||||||
ScreenshotController screenshotController = ScreenshotController();
|
|
||||||
SelectedCameraDetails selectedCameraDetails = SelectedCameraDetails();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
unawaited(selectCamera(0, true));
|
_mainCameraController.setState = () {
|
||||||
|
if (mounted) setState(() {});
|
||||||
|
};
|
||||||
|
unawaited(_mainCameraController.selectCamera(0, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
cameraController?.dispose();
|
_mainCameraController.closeCamera();
|
||||||
cameraController = null;
|
|
||||||
selectedCameraDetails = SelectedCameraDetails();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<CameraController?> selectCamera(
|
|
||||||
int sCameraId,
|
|
||||||
bool init,
|
|
||||||
) async {
|
|
||||||
final opts = await initializeCameraController(
|
|
||||||
selectedCameraDetails,
|
|
||||||
sCameraId,
|
|
||||||
init,
|
|
||||||
);
|
|
||||||
if (opts != null) {
|
|
||||||
selectedCameraDetails = opts.$1;
|
|
||||||
cameraController = opts.$2;
|
|
||||||
}
|
|
||||||
setState(() {});
|
|
||||||
return cameraController;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// same function also in home.view.dart
|
|
||||||
Future<void> toggleSelectedCamera() async {
|
|
||||||
if (cameraController == null) return;
|
|
||||||
// do not allow switching camera when recording
|
|
||||||
if (cameraController!.value.isRecordingVideo) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await cameraController!.dispose();
|
|
||||||
cameraController = null;
|
|
||||||
await selectCamera((selectedCameraDetails.cameraId + 1) % 2, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: GestureDetector(
|
body: GestureDetector(
|
||||||
onDoubleTap: toggleSelectedCamera,
|
onDoubleTap: _mainCameraController.toggleSelectedCamera,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
SendToCameraPreview(
|
MainCameraPreview(
|
||||||
cameraController: cameraController,
|
mainCameraController: _mainCameraController,
|
||||||
screenshotController: screenshotController,
|
|
||||||
customPaint: null,
|
|
||||||
),
|
),
|
||||||
CameraPreviewControllerView(
|
CameraPreviewControllerView(
|
||||||
selectCamera: selectCamera,
|
mainController: _mainCameraController,
|
||||||
sendToGroup: widget.sendToGroup,
|
sendToGroup: widget.sendToGroup,
|
||||||
cameraController: cameraController,
|
|
||||||
selectedCameraDetails: selectedCameraDetails,
|
|
||||||
screenshotController: screenshotController,
|
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,14 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
|
||||||
import 'package:camera/camera.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:google_mlkit_barcode_scanning/google_mlkit_barcode_scanning.dart';
|
|
||||||
import 'package:screenshot/screenshot.dart';
|
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||||
import 'package:twonly/src/services/notifications/setup.notifications.dart';
|
import 'package:twonly/src/services/notifications/setup.notifications.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/views/camera/camera_preview_components/camera_preview.dart';
|
import 'package:twonly/src/views/camera/camera_preview_components/camera_preview.dart';
|
||||||
import 'package:twonly/src/views/camera/camera_preview_controller_view.dart';
|
import 'package:twonly/src/views/camera/camera_preview_components/camera_preview_controller_view.dart';
|
||||||
import 'package:twonly/src/views/camera/painters/barcode_detector_painter.dart';
|
import 'package:twonly/src/views/camera/camera_preview_components/main_camera_controller.dart';
|
||||||
import 'package:twonly/src/views/camera/share_image_editor_view.dart';
|
import 'package:twonly/src/views/camera/share_image_editor_view.dart';
|
||||||
import 'package:twonly/src/views/chats/chat_list.view.dart';
|
import 'package:twonly/src/views/chats/chat_list.view.dart';
|
||||||
import 'package:twonly/src/views/memories/memories.view.dart';
|
import 'package:twonly/src/views/memories/memories.view.dart';
|
||||||
|
|
@ -51,6 +46,8 @@ class Shade extends StatelessWidget {
|
||||||
class HomeViewState extends State<HomeView> {
|
class HomeViewState extends State<HomeView> {
|
||||||
int activePageIdx = 0;
|
int activePageIdx = 0;
|
||||||
|
|
||||||
|
final MainCameraController _mainCameraController = MainCameraController();
|
||||||
|
|
||||||
final PageController homeViewPageController = PageController(initialPage: 1);
|
final PageController homeViewPageController = PageController(initialPage: 1);
|
||||||
|
|
||||||
double buttonDiameter = 100;
|
double buttonDiameter = 100;
|
||||||
|
|
@ -59,11 +56,6 @@ class HomeViewState extends State<HomeView> {
|
||||||
double lastChange = 0;
|
double lastChange = 0;
|
||||||
|
|
||||||
Timer? disableCameraTimer;
|
Timer? disableCameraTimer;
|
||||||
bool initCameraStarted = true;
|
|
||||||
|
|
||||||
CameraController? cameraController;
|
|
||||||
ScreenshotController screenshotController = ScreenshotController();
|
|
||||||
SelectedCameraDetails selectedCameraDetails = SelectedCameraDetails();
|
|
||||||
|
|
||||||
bool onPageView(ScrollNotification notification) {
|
bool onPageView(ScrollNotification notification) {
|
||||||
disableCameraTimer?.cancel();
|
disableCameraTimer?.cancel();
|
||||||
|
|
@ -75,15 +67,19 @@ class HomeViewState extends State<HomeView> {
|
||||||
offsetRatio = offsetFromOne.abs();
|
offsetRatio = offsetFromOne.abs();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (cameraController == null && !initCameraStarted && offsetRatio < 1) {
|
if (_mainCameraController.cameraController == null &&
|
||||||
initCameraStarted = true;
|
!_mainCameraController.initCameraStarted &&
|
||||||
unawaited(selectCamera(selectedCameraDetails.cameraId, false));
|
offsetRatio < 1) {
|
||||||
|
unawaited(
|
||||||
|
_mainCameraController.selectCamera(
|
||||||
|
_mainCameraController.selectedCameraDetails.cameraId,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (offsetRatio == 1) {
|
if (offsetRatio == 1) {
|
||||||
disableCameraTimer = Timer(const Duration(milliseconds: 500), () async {
|
disableCameraTimer = Timer(const Duration(milliseconds: 500), () async {
|
||||||
await cameraController?.dispose();
|
await _mainCameraController.closeCamera();
|
||||||
cameraController = null;
|
|
||||||
selectedCameraDetails = SelectedCameraDetails();
|
|
||||||
disableCameraTimer = null;
|
disableCameraTimer = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -93,6 +89,9 @@ class HomeViewState extends State<HomeView> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_mainCameraController.setState = () {
|
||||||
|
if (mounted) setState(() {});
|
||||||
|
};
|
||||||
activePageIdx = widget.initialPage;
|
activePageIdx = widget.initialPage;
|
||||||
globalUpdateOfHomeViewPageIndex = (index) {
|
globalUpdateOfHomeViewPageIndex = (index) {
|
||||||
homeViewPageController.jumpToPage(index);
|
homeViewPageController.jumpToPage(index);
|
||||||
|
|
@ -104,7 +103,7 @@ class HomeViewState extends State<HomeView> {
|
||||||
.listen((NotificationResponse? response) async {
|
.listen((NotificationResponse? response) async {
|
||||||
globalUpdateOfHomeViewPageIndex(0);
|
globalUpdateOfHomeViewPageIndex(0);
|
||||||
});
|
});
|
||||||
unawaited(selectCamera(0, true));
|
unawaited(_mainCameraController.selectCamera(0, true));
|
||||||
unawaited(initAsync());
|
unawaited(initAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,159 +111,10 @@ class HomeViewState extends State<HomeView> {
|
||||||
void dispose() {
|
void dispose() {
|
||||||
unawaited(selectNotificationStream.close());
|
unawaited(selectNotificationStream.close());
|
||||||
disableCameraTimer?.cancel();
|
disableCameraTimer?.cancel();
|
||||||
cameraController?.stopImageStream();
|
_mainCameraController.closeCamera();
|
||||||
cameraController?.dispose();
|
|
||||||
cameraController = null;
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<CameraController?> selectCamera(int sCameraId, bool init) async {
|
|
||||||
final opts = await initializeCameraController(
|
|
||||||
selectedCameraDetails,
|
|
||||||
sCameraId,
|
|
||||||
init,
|
|
||||||
);
|
|
||||||
if (opts != null) {
|
|
||||||
selectedCameraDetails = opts.$1;
|
|
||||||
cameraController = opts.$2;
|
|
||||||
initCameraStarted = false;
|
|
||||||
}
|
|
||||||
if (cameraController?.description.lensDirection ==
|
|
||||||
CameraLensDirection.back) {
|
|
||||||
await cameraController?.startImageStream(_processCameraImage);
|
|
||||||
}
|
|
||||||
setState(() {});
|
|
||||||
return cameraController;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// same function also in camera_send_to_view
|
|
||||||
Future<void> toggleSelectedCamera() async {
|
|
||||||
if (cameraController == null) return;
|
|
||||||
// do not allow switching camera when recording
|
|
||||||
if (cameraController!.value.isRecordingVideo) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await cameraController!.stopImageStream();
|
|
||||||
await cameraController!.dispose();
|
|
||||||
cameraController = null;
|
|
||||||
await selectCamera((selectedCameraDetails.cameraId + 1) % 2, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
final BarcodeScanner _barcodeScanner = BarcodeScanner();
|
|
||||||
bool _canProcess = true;
|
|
||||||
bool _isBusy = false;
|
|
||||||
CustomPaint? _customPaint;
|
|
||||||
String? _text;
|
|
||||||
|
|
||||||
final Map<DeviceOrientation, int> _orientations = {
|
|
||||||
DeviceOrientation.portraitUp: 0,
|
|
||||||
DeviceOrientation.landscapeLeft: 90,
|
|
||||||
DeviceOrientation.portraitDown: 180,
|
|
||||||
DeviceOrientation.landscapeRight: 270,
|
|
||||||
};
|
|
||||||
|
|
||||||
void _processCameraImage(CameraImage image) {
|
|
||||||
final inputImage = _inputImageFromCameraImage(image);
|
|
||||||
if (inputImage == null) return;
|
|
||||||
_processImage(inputImage);
|
|
||||||
}
|
|
||||||
|
|
||||||
InputImage? _inputImageFromCameraImage(CameraImage image) {
|
|
||||||
if (cameraController == null) return null;
|
|
||||||
|
|
||||||
// get image rotation
|
|
||||||
// it is used in android to convert the InputImage from Dart to Java: https://github.com/flutter-ml/google_ml_kit_flutter/blob/master/packages/google_mlkit_commons/android/src/main/java/com/google_mlkit_commons/InputImageConverter.java
|
|
||||||
// `rotation` is not used in iOS to convert the InputImage from Dart to Obj-C: https://github.com/flutter-ml/google_ml_kit_flutter/blob/master/packages/google_mlkit_commons/ios/Classes/MLKVisionImage%2BFlutterPlugin.m
|
|
||||||
// in both platforms `rotation` and `camera.lensDirection` can be used to compensate `x` and `y` coordinates on a canvas: https://github.com/flutter-ml/google_ml_kit_flutter/blob/master/packages/example/lib/vision_detector_views/painters/coordinates_translator.dart
|
|
||||||
final camera = cameraController!.description;
|
|
||||||
final sensorOrientation = camera.sensorOrientation;
|
|
||||||
// print(
|
|
||||||
// 'lensDirection: ${camera.lensDirection}, sensorOrientation: $sensorOrientation, ${_controller?.value.deviceOrientation} ${_controller?.value.lockedCaptureOrientation} ${_controller?.value.isCaptureOrientationLocked}');
|
|
||||||
InputImageRotation? rotation;
|
|
||||||
if (Platform.isIOS) {
|
|
||||||
rotation = InputImageRotationValue.fromRawValue(sensorOrientation);
|
|
||||||
} else if (Platform.isAndroid) {
|
|
||||||
var rotationCompensation =
|
|
||||||
_orientations[cameraController!.value.deviceOrientation];
|
|
||||||
if (rotationCompensation == null) return null;
|
|
||||||
if (camera.lensDirection == CameraLensDirection.front) {
|
|
||||||
// front-facing
|
|
||||||
rotationCompensation = (sensorOrientation + rotationCompensation) % 360;
|
|
||||||
} else {
|
|
||||||
// back-facing
|
|
||||||
rotationCompensation =
|
|
||||||
(sensorOrientation - rotationCompensation + 360) % 360;
|
|
||||||
}
|
|
||||||
rotation = InputImageRotationValue.fromRawValue(rotationCompensation);
|
|
||||||
// print('rotationCompensation: $rotationCompensation');
|
|
||||||
}
|
|
||||||
if (rotation == null) return null;
|
|
||||||
// print('final rotation: $rotation');
|
|
||||||
|
|
||||||
// get image format
|
|
||||||
var format = InputImageFormatValue.fromRawValue(image.format.raw as int);
|
|
||||||
// validate format depending on platform
|
|
||||||
// only supported formats:
|
|
||||||
// * nv21 for Android
|
|
||||||
// * bgra8888 for iOS
|
|
||||||
if (Platform.isAndroid && format == InputImageFormat.yuv420) {
|
|
||||||
// https://developer.android.com/reference/kotlin/androidx/camera/core/ImageAnalysis#OUTPUT_IMAGE_FORMAT_NV21()
|
|
||||||
format = InputImageFormat.nv21;
|
|
||||||
}
|
|
||||||
if (format == null ||
|
|
||||||
(Platform.isAndroid && format != InputImageFormat.nv21) ||
|
|
||||||
(Platform.isIOS && format != InputImageFormat.bgra8888)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// since format is constraint to nv21 or bgra8888, both only have one plane
|
|
||||||
if (image.planes.length != 1) return null;
|
|
||||||
final plane = image.planes.first;
|
|
||||||
|
|
||||||
// compose InputImage using bytes
|
|
||||||
return InputImage.fromBytes(
|
|
||||||
bytes: plane.bytes,
|
|
||||||
metadata: InputImageMetadata(
|
|
||||||
size: Size(image.width.toDouble(), image.height.toDouble()),
|
|
||||||
rotation: rotation, // used only in Android
|
|
||||||
format: format, // used only in iOS
|
|
||||||
bytesPerRow: plane.bytesPerRow, // used only in iOS
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _processImage(InputImage inputImage) async {
|
|
||||||
if (!_canProcess) return;
|
|
||||||
if (_isBusy) return;
|
|
||||||
_isBusy = true;
|
|
||||||
setState(() {
|
|
||||||
_text = '';
|
|
||||||
});
|
|
||||||
final barcodes = await _barcodeScanner.processImage(inputImage);
|
|
||||||
if (inputImage.metadata?.size != null &&
|
|
||||||
inputImage.metadata?.rotation != null &&
|
|
||||||
cameraController != null) {
|
|
||||||
final painter = BarcodeDetectorPainter(
|
|
||||||
barcodes,
|
|
||||||
inputImage.metadata!.size,
|
|
||||||
inputImage.metadata!.rotation,
|
|
||||||
cameraController!.description.lensDirection);
|
|
||||||
_customPaint = CustomPaint(painter: painter);
|
|
||||||
} else {
|
|
||||||
String text = 'Barcodes found: ${barcodes.length}\n\n';
|
|
||||||
for (final barcode in barcodes) {
|
|
||||||
text += 'Barcode: ${barcode.rawValue}\n\n';
|
|
||||||
}
|
|
||||||
_text = text;
|
|
||||||
// TODO: set _customPaint to draw boundingRect on top of image
|
|
||||||
_customPaint = null;
|
|
||||||
}
|
|
||||||
_isBusy = false;
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
final notificationAppLaunchDetails =
|
final notificationAppLaunchDetails =
|
||||||
await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
|
await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
|
||||||
|
|
@ -294,14 +144,12 @@ class HomeViewState extends State<HomeView> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: GestureDetector(
|
body: GestureDetector(
|
||||||
onDoubleTap: offsetRatio == 0 ? toggleSelectedCamera : null,
|
onDoubleTap: offsetRatio == 0
|
||||||
|
? _mainCameraController.toggleSelectedCamera
|
||||||
|
: null,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
HomeViewCameraPreview(
|
MainCameraPreview(mainCameraController: _mainCameraController),
|
||||||
controller: cameraController,
|
|
||||||
screenshotController: screenshotController,
|
|
||||||
customPaint: _customPaint,
|
|
||||||
),
|
|
||||||
Shade(
|
Shade(
|
||||||
opacity: offsetRatio,
|
opacity: offsetRatio,
|
||||||
),
|
),
|
||||||
|
|
@ -333,10 +181,7 @@ class HomeViewState extends State<HomeView> {
|
||||||
child: Opacity(
|
child: Opacity(
|
||||||
opacity: 1 - (offsetRatio * 4) % 1,
|
opacity: 1 - (offsetRatio * 4) % 1,
|
||||||
child: CameraPreviewControllerView(
|
child: CameraPreviewControllerView(
|
||||||
cameraController: cameraController,
|
mainController: _mainCameraController,
|
||||||
screenshotController: screenshotController,
|
|
||||||
selectedCameraDetails: selectedCameraDetails,
|
|
||||||
selectCamera: selectCamera,
|
|
||||||
isVisible:
|
isVisible:
|
||||||
((1 - (offsetRatio * 4) % 1) == 1) && activePageIdx == 1,
|
((1 - (offsetRatio * 4) % 1) == 1) && activePageIdx == 1,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue