mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 14:48:41 +00:00
switch back to old camera plugin #28
This commit is contained in:
parent
644e5125d8
commit
9c027bd924
7 changed files with 393 additions and 457 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:camera/camera.dart';
|
||||||
import 'package:twonly/src/database/twonly_database.dart';
|
import 'package:twonly/src/database/twonly_database.dart';
|
||||||
import 'package:twonly/src/providers/api_provider.dart';
|
import 'package:twonly/src/providers/api_provider.dart';
|
||||||
|
|
||||||
|
|
@ -5,3 +6,5 @@ late ApiProvider apiProvider;
|
||||||
|
|
||||||
// uses for background notification
|
// uses for background notification
|
||||||
late TwonlyDatabase twonlyDatabase;
|
late TwonlyDatabase twonlyDatabase;
|
||||||
|
|
||||||
|
List<CameraDescription> gCameras = <CameraDescription>[];
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:camera/camera.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
|
|
@ -37,6 +38,8 @@ void main() async {
|
||||||
await initMediaStorage();
|
await initMediaStorage();
|
||||||
await initFCMService();
|
await initFCMService();
|
||||||
|
|
||||||
|
gCameras = await availableCameras();
|
||||||
|
|
||||||
apiProvider = ApiProvider();
|
apiProvider = ApiProvider();
|
||||||
twonlyDatabase = TwonlyDatabase();
|
twonlyDatabase = TwonlyDatabase();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,193 +1,107 @@
|
||||||
import 'package:camerawesome/camerawesome_plugin.dart';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:camera/camera.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class ZoomSelector extends StatefulWidget {
|
class CameraZoomButtons extends StatefulWidget {
|
||||||
final CameraState state;
|
const CameraZoomButtons(
|
||||||
|
{super.key,
|
||||||
|
required this.controller,
|
||||||
|
required this.updateScaleFactor,
|
||||||
|
required this.scaleFactor});
|
||||||
|
|
||||||
const ZoomSelector({
|
final CameraController controller;
|
||||||
super.key,
|
final double scaleFactor;
|
||||||
required this.state,
|
final Function updateScaleFactor;
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ZoomSelector> createState() => _ZoomSelectorState();
|
State<CameraZoomButtons> createState() => _CameraZoomButtonsState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ZoomSelectorState extends State<ZoomSelector> {
|
String beautifulZoomScale(double scale) {
|
||||||
double? minZoom;
|
var tmp = scale.toStringAsFixed(1);
|
||||||
double? maxZoom;
|
if (tmp[0] == "0") {
|
||||||
|
tmp = tmp.substring(1, tmp.length);
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
initAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
initAsync() async {
|
|
||||||
minZoom = await CamerawesomePlugin.getMinZoom();
|
|
||||||
maxZoom = await CamerawesomePlugin.getMaxZoom();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return StreamBuilder<SensorConfig>(
|
|
||||||
stream: widget.state.sensorConfig$,
|
|
||||||
builder: (context, sensorConfigSnapshot) {
|
|
||||||
initAsync();
|
|
||||||
if (sensorConfigSnapshot.data == null ||
|
|
||||||
minZoom == null ||
|
|
||||||
maxZoom == null) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
|
|
||||||
return StreamBuilder<double>(
|
|
||||||
stream: sensorConfigSnapshot.requireData.zoom$,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.hasData) {
|
|
||||||
return _ZoomIndicatorLayout(
|
|
||||||
zoom: snapshot.requireData,
|
|
||||||
min: minZoom!,
|
|
||||||
max: maxZoom!,
|
|
||||||
sensorConfig: widget.state.sensorConfig,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
return tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ZoomIndicatorLayout extends StatelessWidget {
|
class _CameraZoomButtonsState extends State<CameraZoomButtons> {
|
||||||
final double zoom;
|
|
||||||
final double min;
|
|
||||||
final double max;
|
|
||||||
final SensorConfig sensorConfig;
|
|
||||||
|
|
||||||
const _ZoomIndicatorLayout({
|
|
||||||
required this.zoom,
|
|
||||||
required this.min,
|
|
||||||
required this.max,
|
|
||||||
required this.sensorConfig,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final displayZoom = (max - min) * zoom + min;
|
var zoomButtonStyle = TextButton.styleFrom(
|
||||||
if (min == 1.0) {
|
padding: EdgeInsets.zero,
|
||||||
return Container();
|
foregroundColor: Colors.white,
|
||||||
}
|
minimumSize: Size(40, 40),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap);
|
||||||
|
|
||||||
return Row(
|
final zoomTextStyle = TextStyle(fontSize: 13);
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
return Center(
|
||||||
children: [
|
child: ClipRRect(
|
||||||
// Show 3 dots for zooming: min, 1.0X and max zoom. The closer one shows
|
borderRadius: BorderRadius.circular(40.0),
|
||||||
// text, the other ones a dot.
|
child: Container(
|
||||||
_ZoomIndicator(
|
color: const Color.fromARGB(90, 0, 0, 0),
|
||||||
normalValue: 0.0,
|
child: Row(
|
||||||
zoom: zoom,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
selected: displayZoom < 1.0,
|
children: [
|
||||||
min: min,
|
TextButton(
|
||||||
max: max,
|
style: zoomButtonStyle,
|
||||||
sensorConfig: sensorConfig,
|
onPressed: () async {
|
||||||
),
|
var level = await widget.controller.getMinZoomLevel();
|
||||||
Padding(
|
widget.updateScaleFactor(level);
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
},
|
||||||
child: _ZoomIndicator(
|
child: FutureBuilder(
|
||||||
normalValue: 0.5,
|
future: widget.controller.getMinZoomLevel(),
|
||||||
zoom: zoom,
|
builder: (context, snap) {
|
||||||
selected: !(displayZoom < 1.0 || displayZoom == max),
|
if (snap.hasData) {
|
||||||
min: min,
|
var minLevel =
|
||||||
max: max,
|
beautifulZoomScale(snap.data!.toDouble());
|
||||||
sensorConfig: sensorConfig,
|
var currentLevel =
|
||||||
|
beautifulZoomScale(widget.scaleFactor);
|
||||||
|
return Text(
|
||||||
|
widget.scaleFactor < 1
|
||||||
|
? "${currentLevel}x"
|
||||||
|
: "${minLevel}x",
|
||||||
|
style: zoomTextStyle,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Text("");
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
style: zoomButtonStyle,
|
||||||
|
onPressed: () {
|
||||||
|
widget.updateScaleFactor(1.0);
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
(widget.scaleFactor >= 1 && widget.scaleFactor < 2)
|
||||||
|
? "${beautifulZoomScale(widget.scaleFactor)}x"
|
||||||
|
: "1.0x",
|
||||||
|
style: zoomTextStyle,
|
||||||
|
)),
|
||||||
|
TextButton(
|
||||||
|
style: zoomButtonStyle,
|
||||||
|
onPressed: () async {
|
||||||
|
var level = min(await widget.controller.getMaxZoomLevel(), 2)
|
||||||
|
.toDouble();
|
||||||
|
widget.updateScaleFactor(level);
|
||||||
|
},
|
||||||
|
child: FutureBuilder(
|
||||||
|
future: widget.controller.getMaxZoomLevel(),
|
||||||
|
builder: (context, snap) {
|
||||||
|
if (snap.hasData) {
|
||||||
|
var maxLevel = min((snap.data?.toInt())!, 2);
|
||||||
|
return Text("${maxLevel}x", style: zoomTextStyle);
|
||||||
|
} else {
|
||||||
|
return Text("");
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_ZoomIndicator(
|
|
||||||
normalValue: 1.0,
|
|
||||||
zoom: zoom,
|
|
||||||
selected: displayZoom == max,
|
|
||||||
min: min,
|
|
||||||
max: max,
|
|
||||||
sensorConfig: sensorConfig,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ZoomIndicator extends StatelessWidget {
|
|
||||||
final double zoom;
|
|
||||||
final double min;
|
|
||||||
final double max;
|
|
||||||
final double normalValue;
|
|
||||||
final SensorConfig sensorConfig;
|
|
||||||
final bool selected;
|
|
||||||
|
|
||||||
const _ZoomIndicator({
|
|
||||||
required this.zoom,
|
|
||||||
required this.min,
|
|
||||||
required this.max,
|
|
||||||
required this.normalValue,
|
|
||||||
required this.sensorConfig,
|
|
||||||
required this.selected,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final baseTheme = AwesomeThemeProvider.of(context).theme;
|
|
||||||
final baseButtonTheme = baseTheme.buttonTheme;
|
|
||||||
final displayZoom = (max - min) * zoom + min;
|
|
||||||
Widget content = AnimatedSwitcher(
|
|
||||||
duration: const Duration(milliseconds: 100),
|
|
||||||
transitionBuilder: (child, anim) {
|
|
||||||
return ScaleTransition(scale: anim, child: child);
|
|
||||||
},
|
|
||||||
child: selected
|
|
||||||
? AwesomeBouncingWidget(
|
|
||||||
key: ValueKey("zoomIndicator_${normalValue}_selected"),
|
|
||||||
onTap: () {
|
|
||||||
sensorConfig.setZoom(normalValue);
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
color: Colors.transparent,
|
|
||||||
padding: const EdgeInsets.all(0.0),
|
|
||||||
child: AwesomeCircleWidget(
|
|
||||||
theme: baseTheme,
|
|
||||||
child: Text(
|
|
||||||
"${displayZoom.toStringAsFixed(1)}X",
|
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: AwesomeBouncingWidget(
|
|
||||||
key: ValueKey("zoomIndicator_${normalValue}_unselected"),
|
|
||||||
onTap: () {
|
|
||||||
sensorConfig.setZoom(normalValue);
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
color: Colors.transparent,
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: AwesomeCircleWidget(
|
|
||||||
theme: baseTheme.copyWith(
|
|
||||||
buttonTheme: baseButtonTheme.copyWith(
|
|
||||||
backgroundColor: baseButtonTheme.foregroundColor,
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: const SizedBox(width: 6, height: 6),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Same width for each dot to keep them in their position
|
|
||||||
return SizedBox(
|
|
||||||
width: 56,
|
|
||||||
child: Center(
|
|
||||||
child: content,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import 'dart:io';
|
import 'dart:typed_data';
|
||||||
import 'dart:ui';
|
import 'package:camera/camera.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:camerawesome/camerawesome_plugin.dart';
|
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:screenshot/screenshot.dart';
|
||||||
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/components/zoom_selector.dart';
|
import 'package:twonly/src/components/zoom_selector.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/components/image_editor/action_button.dart';
|
import 'package:twonly/src/components/image_editor/action_button.dart';
|
||||||
|
|
@ -48,13 +48,77 @@ class CameraPreviewView extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CameraPreviewViewState extends State<CameraPreviewView> {
|
class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
double _lastZoom = 1;
|
double scaleFactor = 1;
|
||||||
double _basePanY = 0;
|
|
||||||
bool sharePreviewIsShown = false;
|
bool sharePreviewIsShown = false;
|
||||||
bool isFlashOn = false;
|
bool isFlashOn = false;
|
||||||
bool showSelfieFlash = false;
|
bool showSelfieFlash = false;
|
||||||
|
int cameraId = 0;
|
||||||
|
bool isZoomAble = false;
|
||||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
|
late CameraController controller;
|
||||||
|
ScreenshotController screenshotController = ScreenshotController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
selectCamera(0, init: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void selectCamera(int sCameraId, {bool init = false}) {
|
||||||
|
if (sCameraId > gCameras.length) return;
|
||||||
|
setState(() {
|
||||||
|
isZoomAble = false;
|
||||||
|
});
|
||||||
|
controller = CameraController(gCameras[sCameraId], ResolutionPreset.high);
|
||||||
|
controller.initialize().then((_) async {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
controller.setFlashMode(isFlashOn ? FlashMode.always : FlashMode.off);
|
||||||
|
|
||||||
|
isZoomAble = await controller.getMinZoomLevel() !=
|
||||||
|
await controller.getMaxZoomLevel();
|
||||||
|
setState(() {});
|
||||||
|
}).catchError((Object e) {
|
||||||
|
if (e is CameraException) {
|
||||||
|
switch (e.code) {
|
||||||
|
case 'CameraAccessDenied':
|
||||||
|
// Handle access errors here.
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Handle other errors here.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setState(() {
|
||||||
|
cameraId = sCameraId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateScaleFactor(double newScale) async {
|
||||||
|
var minFactor = await controller.getMinZoomLevel();
|
||||||
|
var maxFactor = await controller.getMaxZoomLevel();
|
||||||
|
if (newScale < minFactor) {
|
||||||
|
newScale = minFactor;
|
||||||
|
}
|
||||||
|
if (newScale > maxFactor) {
|
||||||
|
newScale = maxFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
await controller.setZoomLevel(newScale);
|
||||||
|
setState(() {
|
||||||
|
scaleFactor = newScale;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MediaViewSizing(
|
return MediaViewSizing(
|
||||||
|
|
@ -62,237 +126,202 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
children: [
|
children: [
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(22),
|
borderRadius: BorderRadius.circular(22),
|
||||||
child: CameraAwesomeBuilder.custom(
|
child: Stack(
|
||||||
previewAlignment: Alignment.topLeft,
|
children: [
|
||||||
sensorConfig: SensorConfig.single(
|
(controller.value.isInitialized)
|
||||||
aspectRatio: CameraAspectRatios.ratio_16_9,
|
? Positioned.fill(
|
||||||
zoom: 0.5,
|
child: Screenshot(
|
||||||
),
|
controller: screenshotController,
|
||||||
previewFit: CameraPreviewFit.contain,
|
child: AspectRatio(
|
||||||
progressIndicator: Container(),
|
aspectRatio: 9 / 16,
|
||||||
onMediaCaptureEvent: (event) {
|
child: ClipRect(
|
||||||
switch ((event.status, event.isPicture, event.isVideo)) {
|
child: FittedBox(
|
||||||
case (MediaCaptureStatus.capturing, true, false):
|
fit: BoxFit.cover,
|
||||||
debugPrint('Capturing picture...');
|
child: SizedBox(
|
||||||
case (MediaCaptureStatus.success, true, false):
|
width: controller.value.previewSize!.height,
|
||||||
event.captureRequest.when(
|
height: controller.value.previewSize!.width,
|
||||||
single: (single) async {
|
child: CameraPreview(controller),
|
||||||
final imageBytes = await single.file?.readAsBytes();
|
|
||||||
if (imageBytes == null || !context.mounted) return;
|
|
||||||
debugPrint("Delete ${single.path!}");
|
|
||||||
File(single.path!).delete();
|
|
||||||
setState(() {
|
|
||||||
sharePreviewIsShown = true;
|
|
||||||
});
|
|
||||||
await Navigator.push(
|
|
||||||
context,
|
|
||||||
PageRouteBuilder(
|
|
||||||
opaque: false,
|
|
||||||
pageBuilder: (context, a1, a2) =>
|
|
||||||
ShareImageEditorView(imageBytes: imageBytes),
|
|
||||||
transitionsBuilder: (context, animation,
|
|
||||||
secondaryAnimation, child) {
|
|
||||||
return child;
|
|
||||||
},
|
|
||||||
transitionDuration: Duration.zero,
|
|
||||||
reverseTransitionDuration: Duration.zero,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (context.mounted) {
|
|
||||||
setState(() {
|
|
||||||
sharePreviewIsShown = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
builder: (cameraState, preview) {
|
|
||||||
return Stack(
|
|
||||||
children: [
|
|
||||||
Positioned.fill(
|
|
||||||
child: GestureDetector(
|
|
||||||
onPanStart: (details) async {
|
|
||||||
if (cameraState.sensorConfig.sensors.first.position ==
|
|
||||||
SensorPosition.front) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
_basePanY = details.localPosition.dy;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onPanUpdate: (details) async {
|
|
||||||
if (cameraState.sensorConfig.sensors.first.position ==
|
|
||||||
SensorPosition.front) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var diff = _basePanY - details.localPosition.dy;
|
|
||||||
if (diff > 200) diff = 200;
|
|
||||||
if (diff < 0) diff = 0;
|
|
||||||
var tmp = (diff / 200 * 50).toInt() / 50;
|
|
||||||
if (tmp != _lastZoom) {
|
|
||||||
cameraState.sensorConfig.setZoom(tmp);
|
|
||||||
setState(() {
|
|
||||||
(tmp);
|
|
||||||
_lastZoom = tmp;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onDoubleTap: () async {
|
|
||||||
bool isFront =
|
|
||||||
cameraState.sensorConfig.sensors.first.position ==
|
|
||||||
SensorPosition.front;
|
|
||||||
cameraState.switchCameraSensor(
|
|
||||||
aspectRatio: CameraAspectRatios.ratio_16_9,
|
|
||||||
flash: isFlashOn ? FlashMode.on : FlashMode.none,
|
|
||||||
zoom: isFront ? 0.5 : 0,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (!sharePreviewIsShown)
|
|
||||||
Positioned(
|
|
||||||
right: 5,
|
|
||||||
top: 0,
|
|
||||||
child: Container(
|
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
child: SafeArea(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: <Widget>[
|
|
||||||
ActionButton(
|
|
||||||
FontAwesomeIcons.repeat,
|
|
||||||
tooltipText:
|
|
||||||
context.lang.switchFrontAndBackCamera,
|
|
||||||
onPressed: () async {
|
|
||||||
cameraState.switchCameraSensor(
|
|
||||||
aspectRatio:
|
|
||||||
CameraAspectRatios.ratio_16_9,
|
|
||||||
flash: isFlashOn
|
|
||||||
? FlashMode.on
|
|
||||||
: FlashMode.none,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ActionButton(
|
|
||||||
FontAwesomeIcons.boltLightning,
|
|
||||||
tooltipText: context.lang.toggleFlashLight,
|
|
||||||
color: isFlashOn
|
|
||||||
? const Color.fromARGB(255, 255, 230, 0)
|
|
||||||
: const Color.fromARGB(
|
|
||||||
158, 255, 255, 255),
|
|
||||||
onPressed: () async {
|
|
||||||
if (isFlashOn) {
|
|
||||||
cameraState.sensorConfig
|
|
||||||
.setFlashMode(FlashMode.none);
|
|
||||||
isFlashOn = false;
|
|
||||||
} else {
|
|
||||||
cameraState.sensorConfig
|
|
||||||
.setFlashMode(FlashMode.on);
|
|
||||||
isFlashOn = true;
|
|
||||||
}
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (!sharePreviewIsShown)
|
|
||||||
Positioned(
|
|
||||||
bottom: 30,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
ZoomSelector(state: cameraState),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () async {
|
|
||||||
if (cameraState.sensorConfig.flashMode ==
|
|
||||||
FlashMode.on &&
|
|
||||||
cameraState.sensorConfig.sensors.first
|
|
||||||
.position ==
|
|
||||||
SensorPosition.front) {
|
|
||||||
setState(() {
|
|
||||||
showSelfieFlash = true;
|
|
||||||
});
|
|
||||||
await Future.delayed(
|
|
||||||
Duration(milliseconds: 500));
|
|
||||||
}
|
|
||||||
cameraState.when(
|
|
||||||
onPhotoMode: (picState) =>
|
|
||||||
picState.takePhoto());
|
|
||||||
setState(() {
|
|
||||||
showSelfieFlash = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onLongPress: () async {},
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Container(
|
|
||||||
height: 100,
|
|
||||||
width: 100,
|
|
||||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
|
||||||
padding: const EdgeInsets.all(2),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
border: Border.all(
|
|
||||||
width: 7,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
)),
|
||||||
),
|
)
|
||||||
|
: Container(),
|
||||||
|
Positioned.fill(
|
||||||
|
child: GestureDetector(
|
||||||
|
onPanStart: (details) async {
|
||||||
|
// if (cameraState.sensorConfig.sensors.first.position ==
|
||||||
|
// SensorPosition.front) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// setState(() {
|
||||||
|
// _basePanY = details.localPosition.dy;
|
||||||
|
// });
|
||||||
|
},
|
||||||
|
onPanUpdate: (details) async {
|
||||||
|
// if (cameraState.sensorConfig.sensors.first.position ==
|
||||||
|
// SensorPosition.front) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// var diff = _basePanY - details.localPosition.dy;
|
||||||
|
// if (diff > 200) diff = 200;
|
||||||
|
// if (diff < 0) diff = 0;
|
||||||
|
// var tmp = (diff / 200 * 50).toInt() / 50;
|
||||||
|
// if (tmp != _lastZoom) {
|
||||||
|
// cameraState.sensorConfig.setZoom(tmp);
|
||||||
|
// setState(() {
|
||||||
|
// (tmp);
|
||||||
|
// _lastZoom = tmp;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
onDoubleTap: () async {
|
||||||
|
selectCamera((cameraId + 1) % 2);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (!sharePreviewIsShown)
|
||||||
|
Positioned(
|
||||||
|
right: 5,
|
||||||
|
top: 0,
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
child: SafeArea(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
ActionButton(
|
||||||
|
FontAwesomeIcons.repeat,
|
||||||
|
tooltipText:
|
||||||
|
context.lang.switchFrontAndBackCamera,
|
||||||
|
onPressed: () async {
|
||||||
|
selectCamera((cameraId + 1) % 2);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ActionButton(
|
||||||
|
FontAwesomeIcons.boltLightning,
|
||||||
|
tooltipText: context.lang.toggleFlashLight,
|
||||||
|
color: isFlashOn
|
||||||
|
? const Color.fromARGB(255, 255, 230, 0)
|
||||||
|
: const Color.fromARGB(158, 255, 255, 255),
|
||||||
|
onPressed: () async {
|
||||||
|
if (isFlashOn) {
|
||||||
|
controller.setFlashMode(FlashMode.off);
|
||||||
|
isFlashOn = false;
|
||||||
|
} else {
|
||||||
|
controller.setFlashMode(FlashMode.always);
|
||||||
|
isFlashOn = true;
|
||||||
|
}
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
);
|
),
|
||||||
},
|
if (!sharePreviewIsShown)
|
||||||
saveConfig: SaveConfig.photoAndVideo(
|
Positioned(
|
||||||
photoPathBuilder: (sensors) async {
|
bottom: 30,
|
||||||
final Directory extDir = await getTemporaryDirectory();
|
left: 0,
|
||||||
final testDir = await Directory(
|
right: 0,
|
||||||
'${extDir.path}/images',
|
child: Align(
|
||||||
).create(recursive: true);
|
alignment: Alignment.bottomCenter,
|
||||||
final String filePath =
|
child: Column(
|
||||||
'${testDir.path}/${DateTime.now().millisecondsSinceEpoch}.jpg';
|
children: [
|
||||||
return SingleCaptureRequest(filePath, sensors.first);
|
if (controller.value.isInitialized &&
|
||||||
},
|
isZoomAble &&
|
||||||
),
|
controller.description.lensDirection !=
|
||||||
|
CameraLensDirection.front)
|
||||||
|
SizedBox(
|
||||||
|
width: 150,
|
||||||
|
child: CameraZoomButtons(
|
||||||
|
key: widget.key,
|
||||||
|
scaleFactor: scaleFactor,
|
||||||
|
updateScaleFactor: updateScaleFactor,
|
||||||
|
controller: controller,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
if (isFlashOn) {
|
||||||
|
if (controller.description.lensDirection ==
|
||||||
|
CameraLensDirection.front) {
|
||||||
|
setState(() {
|
||||||
|
showSelfieFlash = true;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
controller.setFlashMode(FlashMode.torch);
|
||||||
|
}
|
||||||
|
await Future.delayed(
|
||||||
|
Duration(milliseconds: 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
await controller.pausePreview();
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
|
controller.setFlashMode(
|
||||||
|
isFlashOn ? FlashMode.always : FlashMode.off);
|
||||||
|
|
||||||
|
Future<Uint8List?> imageBytes =
|
||||||
|
screenshotController.capture(pixelRatio: 1);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
sharePreviewIsShown = true;
|
||||||
|
});
|
||||||
|
await Navigator.push(
|
||||||
|
context,
|
||||||
|
PageRouteBuilder(
|
||||||
|
opaque: false,
|
||||||
|
pageBuilder: (context, a1, a2) =>
|
||||||
|
ShareImageEditorView(
|
||||||
|
imageBytes: imageBytes),
|
||||||
|
transitionsBuilder: (context, animation,
|
||||||
|
secondaryAnimation, child) {
|
||||||
|
return child;
|
||||||
|
},
|
||||||
|
transitionDuration: Duration.zero,
|
||||||
|
reverseTransitionDuration: Duration.zero,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
// does not work??
|
||||||
|
//await controller.resumePreview();
|
||||||
|
selectCamera(0);
|
||||||
|
if (context.mounted) {
|
||||||
|
setState(() {
|
||||||
|
sharePreviewIsShown = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
showSelfieFlash = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onLongPress: () async {},
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Container(
|
||||||
|
height: 100,
|
||||||
|
width: 100,
|
||||||
|
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||||
|
padding: const EdgeInsets.all(2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(
|
||||||
|
width: 7,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (showSelfieFlash)
|
if (showSelfieFlash)
|
||||||
|
|
@ -304,18 +333,6 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (sharePreviewIsShown)
|
|
||||||
Positioned.fill(
|
|
||||||
child: BackdropFilter(
|
|
||||||
filter: ImageFilter.blur(
|
|
||||||
sigmaX: 100.0,
|
|
||||||
sigmaY: 100.0,
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ List<Layer> removedLayers = [];
|
||||||
|
|
||||||
class ShareImageEditorView extends StatefulWidget {
|
class ShareImageEditorView extends StatefulWidget {
|
||||||
const ShareImageEditorView({super.key, required this.imageBytes});
|
const ShareImageEditorView({super.key, required this.imageBytes});
|
||||||
final Uint8List imageBytes;
|
final Future<Uint8List?> imageBytes;
|
||||||
@override
|
@override
|
||||||
State<ShareImageEditorView> createState() => _ShareImageEditorView();
|
State<ShareImageEditorView> createState() => _ShareImageEditorView();
|
||||||
}
|
}
|
||||||
|
|
@ -223,8 +223,9 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadImage(dynamic imageFile) async {
|
Future<void> loadImage(Future<Uint8List?> imageFile) async {
|
||||||
await currentImage.load(imageFile);
|
Uint8List? imageBytes = await imageFile;
|
||||||
|
await currentImage.load(imageBytes);
|
||||||
|
|
||||||
layers.clear();
|
layers.clear();
|
||||||
|
|
||||||
|
|
|
||||||
64
pubspec.lock
64
pubspec.lock
|
|
@ -129,22 +129,46 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.9.3"
|
version: "8.9.3"
|
||||||
camerawesome:
|
camera:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: camerawesome
|
name: camera
|
||||||
sha256: "3619d5605fb14ab72c815532c1d9f635512c75df07b5a742b60a9a4b03b6081e"
|
sha256: "413d2b34fe28496c35c69ede5b232fb9dd5ca2c3a4cb606b14efc1c7546cc8cb"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "0.11.1"
|
||||||
carousel_slider:
|
camera_android_camerax:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: carousel_slider
|
name: camera_android_camerax
|
||||||
sha256: "7b006ec356205054af5beaef62e2221160ea36b90fb70a35e4deacd49d0349ae"
|
sha256: "13784f539c7f104766bff84e4479a70f03b29d78b208278be45c939250d9d7f5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.0"
|
version: "0.6.14+1"
|
||||||
|
camera_avfoundation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: camera_avfoundation
|
||||||
|
sha256: "3057ada0b30402e3a9b6dffec365c9736a36edbf04abaecc67c4309eadc86b49"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.18+9"
|
||||||
|
camera_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: camera_platform_interface
|
||||||
|
sha256: "953e7baed3a7c8fae92f7200afeb2be503ff1a17c3b4e4ed7b76f008c2810a31"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.9.0"
|
||||||
|
camera_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: camera_web
|
||||||
|
sha256: "595f28c89d1fb62d77c73c633193755b781c6d2e0ebcd8dc25b763b514e6ba8f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.5"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -201,14 +225,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.19.0"
|
version: "1.19.0"
|
||||||
colorfilter_generator:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: colorfilter_generator
|
|
||||||
sha256: ccc2995e440b1d828d55d99150e7cad64624f3cb4a1e235000de3f93cf10d35c
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.0.8"
|
|
||||||
connectivity_plus:
|
connectivity_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -869,14 +885,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.11.1"
|
version: "0.11.1"
|
||||||
matrix2d:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: matrix2d
|
|
||||||
sha256: "188718dd3bc2a31e372cfd0791b0f77f4f13ea76164147342cc378d9132949e7"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.4"
|
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1173,14 +1181,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.2"
|
version: "1.3.2"
|
||||||
rxdart:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: rxdart
|
|
||||||
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.28.0"
|
|
||||||
screenshot:
|
screenshot:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,6 @@ environment:
|
||||||
sdk: ^3.6.0
|
sdk: ^3.6.0
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
camerawesome: ^2.1.0
|
|
||||||
# camerawesome:
|
|
||||||
# path: ../CamerAwesome
|
|
||||||
collection: ^1.18.0
|
collection: ^1.18.0
|
||||||
connectivity_plus: ^6.1.2
|
connectivity_plus: ^6.1.2
|
||||||
drift: ^2.25.1
|
drift: ^2.25.1
|
||||||
|
|
@ -54,6 +51,7 @@ dependencies:
|
||||||
screenshot: ^3.0.0
|
screenshot: ^3.0.0
|
||||||
url_launcher: ^6.3.1
|
url_launcher: ^6.3.1
|
||||||
web_socket_channel: ^3.0.1
|
web_socket_channel: ^3.0.1
|
||||||
|
camera: ^0.11.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue