mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-03-03 13:56:45 +00:00
improving camera
This commit is contained in:
parent
d83e9a26c4
commit
cd5deca6b6
7 changed files with 114 additions and 84 deletions
|
|
@ -1,8 +1,10 @@
|
|||
# Changelog
|
||||
|
||||
## 0.0.87
|
||||
## 0.0.89
|
||||
|
||||
- Added basic support for face filters
|
||||
- Adds option to manual focus in the camera
|
||||
- Adds support to switch between front and back camera during video recording
|
||||
- Adds basic face filters
|
||||
|
||||
## 0.0.86
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ class MainCameraPreview extends StatelessWidget {
|
|||
height: mainCameraController
|
||||
.cameraController!.value.previewSize!.width,
|
||||
child: CameraPreview(
|
||||
key: mainCameraController.cameraPreviewKey,
|
||||
mainCameraController.cameraController!,
|
||||
child: Stack(
|
||||
children: [
|
||||
|
|
@ -47,6 +48,24 @@ class MainCameraPreview extends StatelessWidget {
|
|||
Positioned.fill(
|
||||
child: mainCameraController.facePaint!,
|
||||
),
|
||||
if (mainCameraController.focusPointOffset != null)
|
||||
Positioned(
|
||||
top: mainCameraController.focusPointOffset!.dy - 40,
|
||||
left:
|
||||
mainCameraController.focusPointOffset!.dx - 40,
|
||||
child: Container(
|
||||
height: 80,
|
||||
width: 80,
|
||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
width: 1,
|
||||
color: Colors.white.withAlpha(150),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -37,55 +37,6 @@ import 'package:url_launcher/url_launcher_string.dart';
|
|||
|
||||
int maxVideoRecordingTime = 60;
|
||||
|
||||
Future<(SelectedCameraDetails, CameraController)?> initializeCameraController(
|
||||
SelectedCameraDetails details,
|
||||
int sCameraId,
|
||||
bool init,
|
||||
) async {
|
||||
var cameraId = sCameraId;
|
||||
if (cameraId >= gCameras.length) return null;
|
||||
if (init) {
|
||||
for (; cameraId < gCameras.length; cameraId++) {
|
||||
if (gCameras[cameraId].lensDirection == CameraLensDirection.back) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
details.isZoomAble = false;
|
||||
if (details.cameraId != cameraId) {
|
||||
// switch between front and back
|
||||
details.scaleFactor = 1;
|
||||
}
|
||||
|
||||
final cameraController = CameraController(
|
||||
gCameras[cameraId],
|
||||
ResolutionPreset.high,
|
||||
enableAudio: await Permission.microphone.isGranted,
|
||||
imageFormatGroup:
|
||||
Platform.isAndroid ? ImageFormatGroup.nv21 : ImageFormatGroup.bgra8888,
|
||||
);
|
||||
|
||||
await cameraController.initialize().then((_) async {
|
||||
await cameraController.setZoomLevel(details.scaleFactor);
|
||||
await cameraController.lockCaptureOrientation(DeviceOrientation.portraitUp);
|
||||
await cameraController
|
||||
.setFlashMode(details.isFlashOn ? FlashMode.always : FlashMode.off);
|
||||
await cameraController
|
||||
.getMaxZoomLevel()
|
||||
.then((double value) => details.maxAvailableZoom = value);
|
||||
await cameraController
|
||||
.getMinZoomLevel()
|
||||
.then((double value) => details.minAvailableZoom = value);
|
||||
details
|
||||
..isZoomAble = details.maxAvailableZoom != details.minAvailableZoom
|
||||
..cameraLoaded = true
|
||||
..cameraId = cameraId;
|
||||
}).catchError((Object e) {
|
||||
Log.error('$e');
|
||||
});
|
||||
return (details, cameraController);
|
||||
}
|
||||
|
||||
class SelectedCameraDetails {
|
||||
double maxAvailableZoom = 1;
|
||||
double minAvailableZoom = 1;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
|
@ -6,6 +7,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:google_mlkit_barcode_scanning/google_mlkit_barcode_scanning.dart';
|
||||
import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
|
|
@ -50,6 +52,7 @@ class MainCameraController {
|
|||
Map<int, ScannedNewProfile> scannedNewProfiles = {};
|
||||
String? scannedUrl;
|
||||
GlobalKey zoomButtonKey = GlobalKey();
|
||||
GlobalKey cameraPreviewKey = GlobalKey();
|
||||
bool isSelectingFaceFilters = false;
|
||||
|
||||
final BarcodeScanner _barcodeScanner = BarcodeScanner();
|
||||
|
|
@ -63,6 +66,7 @@ class MainCameraController {
|
|||
bool _isBusyFaces = false;
|
||||
CustomPaint? customPaint;
|
||||
CustomPaint? facePaint;
|
||||
Offset? focusPointOffset;
|
||||
|
||||
FaceFilterType _currentFilterType = FaceFilterType.beardUpperLip;
|
||||
FaceFilterType get currentFilterType => _currentFilterType;
|
||||
|
|
@ -83,44 +87,96 @@ class MainCameraController {
|
|||
selectedCameraDetails = SelectedCameraDetails();
|
||||
}
|
||||
|
||||
Future<CameraController?> selectCamera(int sCameraId, bool init) async {
|
||||
Future<void> selectCamera(int sCameraId, bool init) async {
|
||||
initCameraStarted = true;
|
||||
final opts = await initializeCameraController(
|
||||
selectedCameraDetails,
|
||||
sCameraId,
|
||||
init,
|
||||
);
|
||||
if (opts != null) {
|
||||
selectedCameraDetails = opts.$1;
|
||||
cameraController = opts.$2;
|
||||
}
|
||||
isSelectingFaceFilters = false;
|
||||
setFilter(FaceFilterType.none);
|
||||
await cameraController?.startImageStream(_processCameraImage);
|
||||
zoomButtonKey = GlobalKey();
|
||||
setState();
|
||||
return cameraController;
|
||||
}
|
||||
|
||||
Future<void> toggleSelectedCamera() async {
|
||||
if (cameraController == null) return;
|
||||
// do not allow switching camera when recording
|
||||
if (cameraController!.value.isRecordingVideo) {
|
||||
var cameraId = sCameraId;
|
||||
if (cameraId >= gCameras.length) {
|
||||
Log.warn(
|
||||
'Trying to select a non existing camera $cameraId >= ${gCameras.length}',
|
||||
);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await cameraController!.stopImageStream();
|
||||
} catch (e) {
|
||||
// Log.warn(e);
|
||||
|
||||
if (init) {
|
||||
for (; cameraId < gCameras.length; cameraId++) {
|
||||
if (gCameras[cameraId].lensDirection == CameraLensDirection.back) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
final tmp = cameraController;
|
||||
cameraController = null;
|
||||
|
||||
selectedCameraDetails.isZoomAble = false;
|
||||
if (selectedCameraDetails.cameraId != cameraId) {
|
||||
// switched camera so reset the scaleFactor
|
||||
selectedCameraDetails.scaleFactor = 1;
|
||||
}
|
||||
|
||||
if (cameraController == null) {
|
||||
cameraController = CameraController(
|
||||
gCameras[cameraId],
|
||||
ResolutionPreset.high,
|
||||
enableAudio: await Permission.microphone.isGranted,
|
||||
imageFormatGroup: Platform.isAndroid
|
||||
? ImageFormatGroup.nv21
|
||||
: ImageFormatGroup.bgra8888,
|
||||
);
|
||||
await cameraController?.initialize();
|
||||
await cameraController?.startImageStream(_processCameraImage);
|
||||
} else {
|
||||
await HapticFeedback.lightImpact();
|
||||
await cameraController?.setDescription(gCameras[cameraId]);
|
||||
}
|
||||
|
||||
await cameraController?.setZoomLevel(selectedCameraDetails.scaleFactor);
|
||||
await cameraController
|
||||
?.lockCaptureOrientation(DeviceOrientation.portraitUp);
|
||||
await cameraController?.setFlashMode(
|
||||
selectedCameraDetails.isFlashOn ? FlashMode.always : FlashMode.off,
|
||||
);
|
||||
selectedCameraDetails.maxAvailableZoom =
|
||||
await cameraController?.getMaxZoomLevel() ?? 1;
|
||||
selectedCameraDetails.minAvailableZoom =
|
||||
await cameraController?.getMinZoomLevel() ?? 1;
|
||||
selectedCameraDetails
|
||||
..isZoomAble = selectedCameraDetails.maxAvailableZoom !=
|
||||
selectedCameraDetails.minAvailableZoom
|
||||
..cameraLoaded = true
|
||||
..cameraId = cameraId;
|
||||
|
||||
facePaint = null;
|
||||
customPaint = null;
|
||||
await tmp!.dispose();
|
||||
isSelectingFaceFilters = false;
|
||||
setFilter(FaceFilterType.none);
|
||||
zoomButtonKey = GlobalKey();
|
||||
setState();
|
||||
}
|
||||
|
||||
Future<void> onDoubleTap() async {
|
||||
await selectCamera((selectedCameraDetails.cameraId + 1) % 2, false);
|
||||
}
|
||||
|
||||
Future<void> onTapDown(TapDownDetails details) async {
|
||||
final box =
|
||||
cameraPreviewKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
if (box == null) return;
|
||||
final localPosition = box.globalToLocal(details.globalPosition);
|
||||
|
||||
focusPointOffset = Offset(localPosition.dx, localPosition.dy);
|
||||
|
||||
final dx = localPosition.dx / box.size.width;
|
||||
final dy = localPosition.dy / box.size.height;
|
||||
|
||||
setState();
|
||||
|
||||
await HapticFeedback.lightImpact();
|
||||
await cameraController?.setFocusPoint(Offset(dx, dy));
|
||||
await cameraController?.setFocusMode(FocusMode.auto);
|
||||
|
||||
focusPointOffset = null;
|
||||
setState();
|
||||
}
|
||||
|
||||
void setFilter(FaceFilterType type) {
|
||||
_currentFilterType = type;
|
||||
if (_currentFilterType == FaceFilterType.none) {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@ class QrCodeScannerState extends State<QrCodeScanner> {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: GestureDetector(
|
||||
onDoubleTap: _mainCameraController.toggleSelectedCamera,
|
||||
onDoubleTap: _mainCameraController.onDoubleTap,
|
||||
onTapDown: _mainCameraController.onTapDown,
|
||||
child: Stack(
|
||||
children: [
|
||||
MainCameraPreview(
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@ class CameraSendToViewState extends State<CameraSendToView> {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: GestureDetector(
|
||||
onDoubleTap: _mainCameraController.toggleSelectedCamera,
|
||||
onDoubleTap: _mainCameraController.onDoubleTap,
|
||||
onTapDown: _mainCameraController.onTapDown,
|
||||
child: Stack(
|
||||
children: [
|
||||
MainCameraPreview(
|
||||
|
|
|
|||
|
|
@ -172,9 +172,9 @@ class HomeViewState extends State<HomeView> {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: GestureDetector(
|
||||
onDoubleTap: offsetRatio == 0
|
||||
? _mainCameraController.toggleSelectedCamera
|
||||
: null,
|
||||
onDoubleTap:
|
||||
offsetRatio == 0 ? _mainCameraController.onDoubleTap : null,
|
||||
onTapDown: offsetRatio == 0 ? _mainCameraController.onTapDown : null,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
MainCameraPreview(mainCameraController: _mainCameraController),
|
||||
|
|
|
|||
Loading…
Reference in a new issue