mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 13:08:42 +00:00
starting with #25
This commit is contained in:
parent
f5b4e35e18
commit
5e90af79d8
6 changed files with 555 additions and 347 deletions
|
|
@ -216,6 +216,9 @@ PODS:
|
||||||
- sqlite3/rtree
|
- sqlite3/rtree
|
||||||
- url_launcher_ios (0.0.1):
|
- url_launcher_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- video_player_avfoundation (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`)
|
- camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`)
|
||||||
|
|
@ -247,6 +250,7 @@ DEPENDENCIES:
|
||||||
- sqlite3
|
- sqlite3
|
||||||
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
|
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
|
||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
trunk:
|
trunk:
|
||||||
|
|
@ -315,6 +319,8 @@ EXTERNAL SOURCES:
|
||||||
:path: ".symlinks/plugins/sqlite3_flutter_libs/darwin"
|
:path: ".symlinks/plugins/sqlite3_flutter_libs/darwin"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
|
video_player_avfoundation:
|
||||||
|
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436
|
camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436
|
||||||
|
|
@ -357,6 +363,7 @@ SPEC CHECKSUMS:
|
||||||
sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
|
sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
|
||||||
sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2
|
sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2
|
||||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
||||||
|
video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b
|
||||||
|
|
||||||
PODFILE CHECKSUM: 4d78ee29daee4dd5268f87f2e6b41e472cc27728
|
PODFILE CHECKSUM: 4d78ee29daee4dd5268f87f2e6b41e472cc27728
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_volume_controller/flutter_volume_controller.dart';
|
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:logging/logging.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:screenshot/screenshot.dart';
|
import 'package:screenshot/screenshot.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/views/camera/components/zoom_selector.dart';
|
import 'package:twonly/src/views/camera/components/zoom_selector.dart';
|
||||||
|
|
@ -37,6 +39,9 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
double baseScaleFactor = 0;
|
double baseScaleFactor = 0;
|
||||||
bool cameraLoaded = false;
|
bool cameraLoaded = false;
|
||||||
bool useHighQuality = false;
|
bool useHighQuality = false;
|
||||||
|
bool isVideoRecording = false;
|
||||||
|
bool hasAudioPermission = true;
|
||||||
|
final GlobalKey keyTriggerButton = GlobalKey();
|
||||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
late CameraController controller;
|
late CameraController controller;
|
||||||
|
|
@ -64,10 +69,10 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
final user = await getUser();
|
final user = await getUser();
|
||||||
if (user == null) return;
|
if (user == null) return;
|
||||||
if (user.useHighQuality != null) {
|
if (user.useHighQuality != null) {
|
||||||
setState(() {
|
|
||||||
useHighQuality = user.useHighQuality!;
|
useHighQuality = user.useHighQuality!;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
hasAudioPermission = await Permission.microphone.isGranted;
|
||||||
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -79,6 +84,18 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future requestMicrophonePermission() async {
|
||||||
|
Map<Permission, PermissionStatus> statuses = await [
|
||||||
|
Permission.microphone,
|
||||||
|
].request();
|
||||||
|
if (statuses[Permission.microphone]!.isPermanentlyDenied) {
|
||||||
|
openAppSettings();
|
||||||
|
} else {
|
||||||
|
hasAudioPermission = await Permission.microphone.isGranted;
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void selectCamera(int sCameraId, {bool init = false}) {
|
void selectCamera(int sCameraId, {bool init = false}) {
|
||||||
if (sCameraId >= gCameras.length) return;
|
if (sCameraId >= gCameras.length) return;
|
||||||
if (init) {
|
if (init) {
|
||||||
|
|
@ -181,18 +198,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
final XFile picture = await controller.takePicture();
|
final XFile picture = await controller.takePicture();
|
||||||
imageBytes = loadAndDeletePictureFromFile(picture);
|
imageBytes = loadAndDeletePictureFromFile(picture);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
try {
|
_showCameraException(e);
|
||||||
if (context.mounted) {
|
|
||||||
// ignore: use_build_context_synchronously
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text('Error taking picture: $e'),
|
|
||||||
duration: Duration(seconds: 3),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// ignore: empty_catches
|
|
||||||
} catch (e) {}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -215,31 +221,22 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
imageBytes = screenshotController.capture(pixelRatio: 1);
|
imageBytes = screenshotController.capture(pixelRatio: 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await pushImageEditor(imageBytes)) {
|
if (await pushMediaEditor(imageBytes, null)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// does not work??
|
|
||||||
// if (Platform.isIOS) {
|
|
||||||
// await controller.resumePreview();
|
|
||||||
// } else {
|
|
||||||
selectCamera(cameraId);
|
|
||||||
// }
|
|
||||||
if (context.mounted) {
|
|
||||||
setState(() {
|
|
||||||
sharePreviewIsShown = false;
|
|
||||||
showSelfieFlash = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> pushImageEditor(Future<Uint8List?> imageBytes) async {
|
Future<bool> pushMediaEditor(
|
||||||
|
Future<Uint8List?>? imageBytes, XFile? videFilePath) async {
|
||||||
bool? shoudReturn = await Navigator.push(
|
bool? shoudReturn = await Navigator.push(
|
||||||
context,
|
context,
|
||||||
PageRouteBuilder(
|
PageRouteBuilder(
|
||||||
opaque: false,
|
opaque: false,
|
||||||
pageBuilder: (context, a1, a2) =>
|
pageBuilder: (context, a1, a2) => ShareImageEditorView(
|
||||||
ShareImageEditorView(imageBytes: imageBytes, sendTo: widget.sendTo),
|
videFilePath: videFilePath,
|
||||||
|
imageBytes: imageBytes,
|
||||||
|
sendTo: widget.sendTo,
|
||||||
|
),
|
||||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||||
return child;
|
return child;
|
||||||
},
|
},
|
||||||
|
|
@ -254,6 +251,13 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
selectCamera(cameraId);
|
||||||
|
if (context.mounted) {
|
||||||
|
setState(() {
|
||||||
|
sharePreviewIsShown = false;
|
||||||
|
showSelfieFlash = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -261,18 +265,93 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
controller.description.lensDirection == CameraLensDirection.front;
|
controller.description.lensDirection == CameraLensDirection.front;
|
||||||
|
|
||||||
Future onPanUpdate(details) async {
|
Future onPanUpdate(details) async {
|
||||||
|
print(details);
|
||||||
if (isFront) {
|
if (isFront) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var diff = basePanY - details.localPosition.dy;
|
var diff = basePanY - details.localPosition.dy;
|
||||||
if (diff > 200) diff = 200;
|
|
||||||
if (diff < -200) diff = -200;
|
var baseDiff = Platform.isAndroid ? 200.0 : 300.0;
|
||||||
var tmp = (diff / 200 * (7 * 2)).toInt() / 2;
|
|
||||||
|
if (diff > baseDiff) diff = baseDiff;
|
||||||
|
if (diff < -baseDiff) diff = -baseDiff;
|
||||||
|
var tmp = (diff / baseDiff * (14 * 2)).toInt() / 4;
|
||||||
tmp = baseScaleFactor + tmp;
|
tmp = baseScaleFactor + tmp;
|
||||||
if (tmp < 1) tmp = 1;
|
if (tmp < 1) tmp = 1;
|
||||||
updateScaleFactor(tmp);
|
updateScaleFactor(tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future pickImageFromGallery() async {
|
||||||
|
setState(() {
|
||||||
|
galleryLoadedImageIsShown = true;
|
||||||
|
sharePreviewIsShown = true;
|
||||||
|
});
|
||||||
|
final picker = ImagePicker();
|
||||||
|
final pickedFile = await picker.pickImage(source: ImageSource.gallery);
|
||||||
|
|
||||||
|
if (pickedFile != null) {
|
||||||
|
File imageFile = File(pickedFile.path);
|
||||||
|
if (await pushMediaEditor(imageFile.readAsBytes(), null)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
galleryLoadedImageIsShown = false;
|
||||||
|
sharePreviewIsShown = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future startVideoRecording() async {
|
||||||
|
if (controller.value.isRecordingVideo) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await controller.startVideoRecording();
|
||||||
|
setState(() {
|
||||||
|
isVideoRecording = true;
|
||||||
|
});
|
||||||
|
} on CameraException catch (e) {
|
||||||
|
_showCameraException(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future stopVideoRecording() async {
|
||||||
|
if (!controller.value.isRecordingVideo) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setState(() {
|
||||||
|
isVideoRecording = false;
|
||||||
|
sharePreviewIsShown = true;
|
||||||
|
});
|
||||||
|
XFile? videoPath = await controller.stopVideoRecording();
|
||||||
|
await controller.pausePreview();
|
||||||
|
if (await pushMediaEditor(null, videoPath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} on CameraException catch (e) {
|
||||||
|
_showCameraException(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showCameraException(dynamic e) {
|
||||||
|
Logger("ui.camera").shout("$e");
|
||||||
|
try {
|
||||||
|
if (context.mounted) {
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Error: $e'),
|
||||||
|
duration: Duration(seconds: 3),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// ignore: empty_catches
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (cameraId >= gCameras.length) {
|
if (cameraId >= gCameras.length) {
|
||||||
|
|
@ -281,6 +360,43 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return MediaViewSizing(
|
return MediaViewSizing(
|
||||||
|
child: GestureDetector(
|
||||||
|
onPanStart: (details) async {
|
||||||
|
if (isFront) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
basePanY = details.localPosition.dy;
|
||||||
|
baseScaleFactor = scaleFactor;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onDoubleTap: () async {
|
||||||
|
selectCamera((cameraId + 1) % 2);
|
||||||
|
},
|
||||||
|
onLongPressMoveUpdate: onPanUpdate,
|
||||||
|
onLongPressStart: (details) {
|
||||||
|
setState(() {
|
||||||
|
basePanY = details.localPosition.dy;
|
||||||
|
baseScaleFactor = scaleFactor;
|
||||||
|
});
|
||||||
|
print("onLongPressDown");
|
||||||
|
// Get the position of the pointer
|
||||||
|
RenderBox renderBox =
|
||||||
|
keyTriggerButton.currentContext?.findRenderObject() as RenderBox;
|
||||||
|
Offset localPosition =
|
||||||
|
renderBox.globalToLocal(details.globalPosition);
|
||||||
|
|
||||||
|
final containerRect =
|
||||||
|
Rect.fromLTWH(0, 0, renderBox.size.width, renderBox.size.height);
|
||||||
|
|
||||||
|
if (containerRect.contains(localPosition)) {
|
||||||
|
startVideoRecording();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongPressEnd: (a) {
|
||||||
|
stopVideoRecording();
|
||||||
|
},
|
||||||
|
onPanUpdate: onPanUpdate,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
|
|
@ -302,26 +418,12 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
strokeWidth: 1, color: context.color.primary),
|
strokeWidth: 1, color: context.color.primary),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned.fill(
|
// Positioned.fill(
|
||||||
child: GestureDetector(
|
// child: GestureDetector(),
|
||||||
onPanStart: (details) async {
|
// ),
|
||||||
if (isFront) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
basePanY = details.localPosition.dy;
|
|
||||||
baseScaleFactor = scaleFactor;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onPanUpdate: onPanUpdate,
|
|
||||||
onDoubleTap: () async {
|
|
||||||
selectCamera((cameraId + 1) % 2);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (!sharePreviewIsShown && widget.sendTo != null)
|
if (!sharePreviewIsShown && widget.sendTo != null)
|
||||||
SendToWidget(sendTo: getContactDisplayName(widget.sendTo!)),
|
SendToWidget(sendTo: getContactDisplayName(widget.sendTo!)),
|
||||||
if (!sharePreviewIsShown)
|
if (!sharePreviewIsShown && !isVideoRecording)
|
||||||
Positioned(
|
Positioned(
|
||||||
right: 5,
|
right: 5,
|
||||||
top: 0,
|
top: 0,
|
||||||
|
|
@ -333,7 +435,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
ActionButton(
|
ActionButton(
|
||||||
FontAwesomeIcons.repeat,
|
Icons.repeat_rounded,
|
||||||
tooltipText:
|
tooltipText:
|
||||||
context.lang.switchFrontAndBackCamera,
|
context.lang.switchFrontAndBackCamera,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
|
@ -347,7 +449,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
tooltipText: context.lang.toggleFlashLight,
|
tooltipText: context.lang.toggleFlashLight,
|
||||||
color: isFlashOn
|
color: isFlashOn
|
||||||
? Colors.white
|
? Colors.white
|
||||||
: Color.fromARGB(158, 255, 255, 255),
|
: Colors.white.withAlpha(160),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (isFlashOn) {
|
if (isFlashOn) {
|
||||||
controller.setFlashMode(FlashMode.off);
|
controller.setFlashMode(FlashMode.off);
|
||||||
|
|
@ -365,7 +467,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
tooltipText: context.lang.toggleHighQuality,
|
tooltipText: context.lang.toggleHighQuality,
|
||||||
color: useHighQuality
|
color: useHighQuality
|
||||||
? Colors.white
|
? Colors.white
|
||||||
: const Color.fromARGB(158, 255, 255, 255),
|
: Colors.white.withAlpha(160),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
useHighQuality = !useHighQuality;
|
useHighQuality = !useHighQuality;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
|
@ -376,6 +478,14 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
if (!hasAudioPermission)
|
||||||
|
ActionButton(
|
||||||
|
Icons.mic_off_rounded,
|
||||||
|
color: Colors.white.withAlpha(160),
|
||||||
|
tooltipText:
|
||||||
|
"Allow microphone access for video recording.",
|
||||||
|
onPressed: requestMicrophonePermission,
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -392,7 +502,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
children: [
|
children: [
|
||||||
if (controller.value.isInitialized &&
|
if (controller.value.isInitialized &&
|
||||||
isZoomAble &&
|
isZoomAble &&
|
||||||
!isFront)
|
!isFront &&
|
||||||
|
!isVideoRecording)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 120,
|
width: 120,
|
||||||
child: CameraZoomButtons(
|
child: CameraZoomButtons(
|
||||||
|
|
@ -407,28 +518,9 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
|
if (!isVideoRecording)
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () async {
|
onTap: pickImageFromGallery,
|
||||||
setState(() {
|
|
||||||
galleryLoadedImageIsShown = true;
|
|
||||||
sharePreviewIsShown = true;
|
|
||||||
});
|
|
||||||
final picker = ImagePicker();
|
|
||||||
final pickedFile = await picker.pickImage(
|
|
||||||
source: ImageSource.gallery);
|
|
||||||
|
|
||||||
if (pickedFile != null) {
|
|
||||||
File imageFile = File(pickedFile.path);
|
|
||||||
if (await pushImageEditor(
|
|
||||||
imageFile.readAsBytes())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
galleryLoadedImageIsShown = false;
|
|
||||||
sharePreviewIsShown = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Container(
|
child: Container(
|
||||||
|
|
@ -446,10 +538,9 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () async {
|
onTap: takePicture,
|
||||||
takePicture();
|
// onLongPress: startVideoRecording,
|
||||||
},
|
key: keyTriggerButton,
|
||||||
onLongPress: () async {},
|
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Container(
|
child: Container(
|
||||||
|
|
@ -461,13 +552,15 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
width: 7,
|
width: 7,
|
||||||
color: Colors.white,
|
color: isVideoRecording
|
||||||
|
? Colors.red
|
||||||
|
: Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 80)
|
if (!isVideoRecording) SizedBox(width: 80)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -500,6 +593,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
69
lib/src/views/camera/components/save_to_gallery.dart
Normal file
69
lib/src/views/camera/components/save_to_gallery.dart
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
|
class SaveToGalleryButton extends StatefulWidget {
|
||||||
|
final Future<Uint8List?> Function() getMergedImage;
|
||||||
|
final String? sendNextMediaToUserName;
|
||||||
|
|
||||||
|
const SaveToGalleryButton({
|
||||||
|
super.key,
|
||||||
|
required this.getMergedImage,
|
||||||
|
this.sendNextMediaToUserName,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SaveToGalleryButton> createState() => SaveToGalleryButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class SaveToGalleryButtonState extends State<SaveToGalleryButton> {
|
||||||
|
bool _imageSaving = false;
|
||||||
|
bool _imageSaved = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return OutlinedButton(
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
iconColor: _imageSaved
|
||||||
|
? Theme.of(context).colorScheme.outline
|
||||||
|
: Theme.of(context).colorScheme.primary,
|
||||||
|
foregroundColor: _imageSaved
|
||||||
|
? Theme.of(context).colorScheme.outline
|
||||||
|
: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
setState(() {
|
||||||
|
_imageSaving = true;
|
||||||
|
});
|
||||||
|
Uint8List? imageBytes = await widget.getMergedImage();
|
||||||
|
if (imageBytes == null || !context.mounted) return;
|
||||||
|
final res = await saveImageToGallery(imageBytes);
|
||||||
|
if (res == null) {
|
||||||
|
setState(() {
|
||||||
|
_imageSaving = false;
|
||||||
|
_imageSaved = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
_imageSaving
|
||||||
|
? SizedBox(
|
||||||
|
width: 12,
|
||||||
|
height: 12,
|
||||||
|
child: CircularProgressIndicator(strokeWidth: 1))
|
||||||
|
: _imageSaved
|
||||||
|
? Icon(Icons.check)
|
||||||
|
: FaIcon(FontAwesomeIcons.floppyDisk),
|
||||||
|
if (widget.sendNextMediaToUserName == null) SizedBox(width: 10),
|
||||||
|
if (widget.sendNextMediaToUserName == null)
|
||||||
|
Text(_imageSaved
|
||||||
|
? context.lang.shareImagedEditorSavedImage
|
||||||
|
: context.lang.shareImagedEditorSaveImage)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:camera/camera.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
|
import 'package:twonly/src/views/camera/components/save_to_gallery.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/components/media_view_sizing.dart';
|
import 'package:twonly/src/views/components/media_view_sizing.dart';
|
||||||
import 'package:twonly/src/views/components/notification_badge.dart';
|
import 'package:twonly/src/views/components/notification_badge.dart';
|
||||||
|
|
@ -17,6 +21,7 @@ import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
|
||||||
import 'package:twonly/src/views/camera/image_editor/layers_viewer.dart';
|
import 'package:twonly/src/views/camera/image_editor/layers_viewer.dart';
|
||||||
import 'package:twonly/src/views/camera/image_editor/modules/all_emojis.dart';
|
import 'package:twonly/src/views/camera/image_editor/modules/all_emojis.dart';
|
||||||
import 'package:screenshot/screenshot.dart';
|
import 'package:screenshot/screenshot.dart';
|
||||||
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
List<Layer> layers = [];
|
List<Layer> layers = [];
|
||||||
List<Layer> undoLayers = [];
|
List<Layer> undoLayers = [];
|
||||||
|
|
@ -24,8 +29,9 @@ List<Layer> removedLayers = [];
|
||||||
|
|
||||||
class ShareImageEditorView extends StatefulWidget {
|
class ShareImageEditorView extends StatefulWidget {
|
||||||
const ShareImageEditorView(
|
const ShareImageEditorView(
|
||||||
{super.key, required this.imageBytes, this.sendTo});
|
{super.key, this.imageBytes, this.sendTo, this.videFilePath});
|
||||||
final Future<Uint8List?> imageBytes;
|
final Future<Uint8List?>? imageBytes;
|
||||||
|
final XFile? videFilePath;
|
||||||
final Contact? sendTo;
|
final Contact? sendTo;
|
||||||
@override
|
@override
|
||||||
State<ShareImageEditorView> createState() => _ShareImageEditorView();
|
State<ShareImageEditorView> createState() => _ShareImageEditorView();
|
||||||
|
|
@ -33,13 +39,13 @@ class ShareImageEditorView extends StatefulWidget {
|
||||||
|
|
||||||
class _ShareImageEditorView extends State<ShareImageEditorView> {
|
class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
bool imageLoadedReady = false;
|
bool imageLoadedReady = false;
|
||||||
bool _imageSaved = false;
|
|
||||||
bool _imageSaving = false;
|
|
||||||
bool _isRealTwonly = false;
|
bool _isRealTwonly = false;
|
||||||
int maxShowTime = 999999;
|
int maxShowTime = 999999;
|
||||||
String? sendNextMediaToUserName;
|
String? sendNextMediaToUserName;
|
||||||
double tabDownPostion = 0;
|
double tabDownPostion = 0;
|
||||||
bool sendingImage = false;
|
bool sendingImage = false;
|
||||||
|
double widthRatio = 1, heightRatio = 1, pixelRatio = 1;
|
||||||
|
VideoPlayerController? videoController;
|
||||||
|
|
||||||
ImageItem currentImage = ImageItem();
|
ImageItem currentImage = ImageItem();
|
||||||
ScreenshotController screenshotController = ScreenshotController();
|
ScreenshotController screenshotController = ScreenshotController();
|
||||||
|
|
@ -48,7 +54,25 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
initAsync();
|
initAsync();
|
||||||
loadImage(widget.imageBytes);
|
if (widget.imageBytes != null) {
|
||||||
|
loadImage(widget.imageBytes!);
|
||||||
|
} else if (widget.videFilePath != null) {
|
||||||
|
videoController =
|
||||||
|
VideoPlayerController.file(File(widget.videFilePath!.path));
|
||||||
|
videoController?.addListener(() {
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
videoController?.setLooping(true);
|
||||||
|
videoController?.initialize().then((_) {
|
||||||
|
videoController!.play();
|
||||||
|
|
||||||
|
setState(() {});
|
||||||
|
}).catchError((Object error) {
|
||||||
|
print(error);
|
||||||
|
});
|
||||||
|
videoController?.play();
|
||||||
|
print(widget.videFilePath!.path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void initAsync() async {
|
void initAsync() async {
|
||||||
|
|
@ -64,6 +88,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
layers.clear();
|
layers.clear();
|
||||||
|
videoController?.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -221,7 +246,23 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
double widthRatio = 1, heightRatio = 1, pixelRatio = 1;
|
Future pushShareImageView() async {
|
||||||
|
Future<Uint8List?> imageBytes = getMergedImage();
|
||||||
|
bool? wasSend = await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => ShareImageView(
|
||||||
|
imageBytesFuture: imageBytes,
|
||||||
|
isRealTwonly: _isRealTwonly,
|
||||||
|
maxShowTime: maxShowTime,
|
||||||
|
preselectedUser: widget.sendTo,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (wasSend != null && wasSend && context.mounted) {
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<Uint8List?> getMergedImage() async {
|
Future<Uint8List?> getMergedImage() async {
|
||||||
Uint8List? image;
|
Uint8List? image;
|
||||||
|
|
@ -263,6 +304,26 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future sendImageToSinglePerson() async {
|
||||||
|
setState(() {
|
||||||
|
sendingImage = true;
|
||||||
|
});
|
||||||
|
Uint8List? imageBytes = await getMergedImage();
|
||||||
|
if (!context.mounted) return;
|
||||||
|
if (imageBytes == null) {
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
Navigator.pop(context, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendImage(
|
||||||
|
[widget.sendTo!.userId],
|
||||||
|
imageBytes,
|
||||||
|
_isRealTwonly,
|
||||||
|
maxShowTime,
|
||||||
|
);
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
pixelRatio = MediaQuery.of(context).devicePixelRatio;
|
pixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||||
|
|
@ -301,7 +362,11 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: currentImage.height / pixelRatio,
|
height: currentImage.height / pixelRatio,
|
||||||
width: currentImage.width / pixelRatio,
|
width: currentImage.width / pixelRatio,
|
||||||
child: Screenshot(
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
if (videoController != null)
|
||||||
|
Positioned.fill(child: VideoPlayer(videoController!)),
|
||||||
|
Screenshot(
|
||||||
controller: screenshotController,
|
controller: screenshotController,
|
||||||
child: LayersViewer(
|
child: LayersViewer(
|
||||||
layers: layers.where((x) => !x.isDeleted).toList(),
|
layers: layers.where((x) => !x.isDeleted).toList(),
|
||||||
|
|
@ -310,6 +375,8 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -347,46 +414,9 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
OutlinedButton(
|
SaveToGalleryButton(
|
||||||
style: OutlinedButton.styleFrom(
|
getMergedImage: getMergedImage,
|
||||||
iconColor: _imageSaved
|
sendNextMediaToUserName: sendNextMediaToUserName,
|
||||||
? Theme.of(context).colorScheme.outline
|
|
||||||
: Theme.of(context).colorScheme.primary,
|
|
||||||
foregroundColor: _imageSaved
|
|
||||||
? Theme.of(context).colorScheme.outline
|
|
||||||
: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
onPressed: () async {
|
|
||||||
setState(() {
|
|
||||||
_imageSaving = true;
|
|
||||||
});
|
|
||||||
Uint8List? imageBytes = await getMergedImage();
|
|
||||||
if (imageBytes == null || !context.mounted) return;
|
|
||||||
final res = await saveImageToGallery(imageBytes);
|
|
||||||
if (res == null) {
|
|
||||||
setState(() {
|
|
||||||
_imageSaving = false;
|
|
||||||
_imageSaved = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
_imageSaving
|
|
||||||
? SizedBox(
|
|
||||||
width: 12,
|
|
||||||
height: 12,
|
|
||||||
child: CircularProgressIndicator(strokeWidth: 1))
|
|
||||||
: _imageSaved
|
|
||||||
? Icon(Icons.check)
|
|
||||||
: FaIcon(FontAwesomeIcons.floppyDisk),
|
|
||||||
if (sendNextMediaToUserName == null) SizedBox(width: 10),
|
|
||||||
if (sendNextMediaToUserName == null)
|
|
||||||
Text(_imageSaved
|
|
||||||
? context.lang.shareImagedEditorSavedImage
|
|
||||||
: context.lang.shareImagedEditorSaveImage)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (sendNextMediaToUserName != null) SizedBox(width: 10),
|
if (sendNextMediaToUserName != null) SizedBox(width: 10),
|
||||||
if (sendNextMediaToUserName != null)
|
if (sendNextMediaToUserName != null)
|
||||||
|
|
@ -395,27 +425,10 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
iconColor: Theme.of(context).colorScheme.primary,
|
iconColor: Theme.of(context).colorScheme.primary,
|
||||||
foregroundColor: Theme.of(context).colorScheme.primary,
|
foregroundColor: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: pushShareImageView,
|
||||||
Future<Uint8List?> imageBytes = getMergedImage();
|
|
||||||
bool? wasSend = await Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => ShareImageView(
|
|
||||||
imageBytesFuture: imageBytes,
|
|
||||||
isRealTwonly: _isRealTwonly,
|
|
||||||
maxShowTime: maxShowTime,
|
|
||||||
preselectedUser: widget.sendTo,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (wasSend != null && wasSend && context.mounted) {
|
|
||||||
Navigator.pop(context, true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: FaIcon(FontAwesomeIcons.userPlus),
|
child: FaIcon(FontAwesomeIcons.userPlus),
|
||||||
),
|
),
|
||||||
if (sendNextMediaToUserName != null) SizedBox(width: 10),
|
SizedBox(width: sendNextMediaToUserName == null ? 20 : 10),
|
||||||
if (sendNextMediaToUserName == null) SizedBox(width: 20),
|
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
icon: sendingImage
|
icon: sendingImage
|
||||||
? SizedBox(
|
? SizedBox(
|
||||||
|
|
@ -429,40 +442,8 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
: FaIcon(FontAwesomeIcons.solidPaperPlane),
|
: FaIcon(FontAwesomeIcons.solidPaperPlane),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (sendingImage) return;
|
if (sendingImage) return;
|
||||||
if (widget.sendTo != null) {
|
if (widget.sendTo == null) return pushShareImageView();
|
||||||
setState(() {
|
sendImageToSinglePerson();
|
||||||
sendingImage = true;
|
|
||||||
});
|
|
||||||
Uint8List? imageBytes = await getMergedImage();
|
|
||||||
if (!context.mounted) return;
|
|
||||||
if (imageBytes == null) {
|
|
||||||
Navigator.pop(context, false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sendImage(
|
|
||||||
[widget.sendTo!.userId],
|
|
||||||
imageBytes,
|
|
||||||
_isRealTwonly,
|
|
||||||
maxShowTime,
|
|
||||||
);
|
|
||||||
Navigator.pop(context, true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Future<Uint8List?> imageBytes = getMergedImage();
|
|
||||||
bool? wasSend = await Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => ShareImageView(
|
|
||||||
imageBytesFuture: imageBytes,
|
|
||||||
isRealTwonly: _isRealTwonly,
|
|
||||||
maxShowTime: maxShowTime,
|
|
||||||
preselectedUser: widget.sendTo,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (wasSend != null && wasSend && context.mounted) {
|
|
||||||
Navigator.pop(context, true);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
padding: WidgetStateProperty.all<EdgeInsets>(
|
padding: WidgetStateProperty.all<EdgeInsets>(
|
||||||
|
|
|
||||||
56
pubspec.lock
56
pubspec.lock
|
|
@ -313,6 +313,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.7.1"
|
version: "2.7.1"
|
||||||
|
csslib:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: csslib
|
||||||
|
sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
dart_style:
|
dart_style:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -796,6 +804,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.3"
|
version: "2.2.3"
|
||||||
|
html:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: html
|
||||||
|
sha256: "9475be233c437f0e3637af55e7702cbbe5c23a68bd56e8a5fa2d426297b7c6c8"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.15.5+1"
|
||||||
http:
|
http:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -1753,6 +1769,46 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.0"
|
version: "7.0.0"
|
||||||
|
video_player:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: video_player
|
||||||
|
sha256: "7d78f0cfaddc8c19d4cb2d3bebe1bfef11f2103b0a03e5398b303a1bf65eeb14"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.9.5"
|
||||||
|
video_player_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: video_player_android
|
||||||
|
sha256: ae7d4f1b41e3ac6d24dd9b9d5d6831b52d74a61bdd90a7a6262a33d8bb97c29a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.8.2"
|
||||||
|
video_player_avfoundation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: video_player_avfoundation
|
||||||
|
sha256: "9ee764e5cd2fc1e10911ae8ad588e1a19db3b6aa9a6eb53c127c42d3a3c3f22f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.7.1"
|
||||||
|
video_player_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: video_player_platform_interface
|
||||||
|
sha256: df534476c341ab2c6a835078066fc681b8265048addd853a1e3c78740316a844
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.0"
|
||||||
|
video_player_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: video_player_web
|
||||||
|
sha256: "3ef40ea6d72434edbfdba4624b90fd3a80a0740d260667d91e7ecd2d79e13476"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.4"
|
||||||
vm_service:
|
vm_service:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ dependencies:
|
||||||
image_picker: ^1.1.2
|
image_picker: ^1.1.2
|
||||||
http: ^1.3.0
|
http: ^1.3.0
|
||||||
get: ^4.7.2
|
get: ^4.7.2
|
||||||
|
video_player: ^2.9.5
|
||||||
# avatar_maker
|
# avatar_maker
|
||||||
# avatar_maker:
|
# avatar_maker:
|
||||||
# path: ./dependencies/avatar_maker/
|
# path: ./dependencies/avatar_maker/
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue