starting with #25

This commit is contained in:
otsmr 2025-04-21 23:38:07 +02:00
parent f5b4e35e18
commit 5e90af79d8
6 changed files with 555 additions and 347 deletions

View file

@ -216,6 +216,9 @@ PODS:
- sqlite3/rtree
- url_launcher_ios (0.0.1):
- Flutter
- video_player_avfoundation (0.0.1):
- Flutter
- FlutterMacOS
DEPENDENCIES:
- camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`)
@ -247,6 +250,7 @@ DEPENDENCIES:
- sqlite3
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
SPEC REPOS:
trunk:
@ -315,6 +319,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/sqlite3_flutter_libs/darwin"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
video_player_avfoundation:
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
SPEC CHECKSUMS:
camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436
@ -357,6 +363,7 @@ SPEC CHECKSUMS:
sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b
PODFILE CHECKSUM: 4d78ee29daee4dd5268f87f2e6b41e472cc27728

View file

@ -5,6 +5,8 @@ import 'package:flutter/services.dart';
import 'package:flutter_volume_controller/flutter_volume_controller.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.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:twonly/globals.dart';
import 'package:twonly/src/views/camera/components/zoom_selector.dart';
@ -37,6 +39,9 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
double baseScaleFactor = 0;
bool cameraLoaded = false;
bool useHighQuality = false;
bool isVideoRecording = false;
bool hasAudioPermission = true;
final GlobalKey keyTriggerButton = GlobalKey();
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
late CameraController controller;
@ -64,10 +69,10 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
final user = await getUser();
if (user == null) return;
if (user.useHighQuality != null) {
setState(() {
useHighQuality = user.useHighQuality!;
});
}
hasAudioPermission = await Permission.microphone.isGranted;
setState(() {});
}
@override
@ -79,6 +84,18 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
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}) {
if (sCameraId >= gCameras.length) return;
if (init) {
@ -181,18 +198,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
final XFile picture = await controller.takePicture();
imageBytes = loadAndDeletePictureFromFile(picture);
} catch (e) {
try {
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) {}
_showCameraException(e);
return;
}
} else {
@ -215,31 +221,22 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
imageBytes = screenshotController.capture(pixelRatio: 1);
}
if (await pushImageEditor(imageBytes)) {
if (await pushMediaEditor(imageBytes, null)) {
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(
context,
PageRouteBuilder(
opaque: false,
pageBuilder: (context, a1, a2) =>
ShareImageEditorView(imageBytes: imageBytes, sendTo: widget.sendTo),
pageBuilder: (context, a1, a2) => ShareImageEditorView(
videFilePath: videFilePath,
imageBytes: imageBytes,
sendTo: widget.sendTo,
),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return child;
},
@ -254,6 +251,13 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
Navigator.pop(context);
return true;
}
selectCamera(cameraId);
if (context.mounted) {
setState(() {
sharePreviewIsShown = false;
showSelfieFlash = false;
});
}
return false;
}
@ -261,18 +265,93 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
controller.description.lensDirection == CameraLensDirection.front;
Future onPanUpdate(details) async {
print(details);
if (isFront) {
return;
}
var diff = basePanY - details.localPosition.dy;
if (diff > 200) diff = 200;
if (diff < -200) diff = -200;
var tmp = (diff / 200 * (7 * 2)).toInt() / 2;
var baseDiff = Platform.isAndroid ? 200.0 : 300.0;
if (diff > baseDiff) diff = baseDiff;
if (diff < -baseDiff) diff = -baseDiff;
var tmp = (diff / baseDiff * (14 * 2)).toInt() / 4;
tmp = baseScaleFactor + tmp;
if (tmp < 1) tmp = 1;
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
Widget build(BuildContext context) {
if (cameraId >= gCameras.length) {
@ -281,6 +360,43 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
);
}
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(
children: [
ClipRRect(
@ -302,26 +418,12 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
strokeWidth: 1, color: context.color.primary),
),
),
Positioned.fill(
child: GestureDetector(
onPanStart: (details) async {
if (isFront) {
return;
}
setState(() {
basePanY = details.localPosition.dy;
baseScaleFactor = scaleFactor;
});
},
onPanUpdate: onPanUpdate,
onDoubleTap: () async {
selectCamera((cameraId + 1) % 2);
},
),
),
// Positioned.fill(
// child: GestureDetector(),
// ),
if (!sharePreviewIsShown && widget.sendTo != null)
SendToWidget(sendTo: getContactDisplayName(widget.sendTo!)),
if (!sharePreviewIsShown)
if (!sharePreviewIsShown && !isVideoRecording)
Positioned(
right: 5,
top: 0,
@ -333,7 +435,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ActionButton(
FontAwesomeIcons.repeat,
Icons.repeat_rounded,
tooltipText:
context.lang.switchFrontAndBackCamera,
onPressed: () async {
@ -347,7 +449,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
tooltipText: context.lang.toggleFlashLight,
color: isFlashOn
? Colors.white
: Color.fromARGB(158, 255, 255, 255),
: Colors.white.withAlpha(160),
onPressed: () async {
if (isFlashOn) {
controller.setFlashMode(FlashMode.off);
@ -365,7 +467,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
tooltipText: context.lang.toggleHighQuality,
color: useHighQuality
? Colors.white
: const Color.fromARGB(158, 255, 255, 255),
: Colors.white.withAlpha(160),
onPressed: () async {
useHighQuality = !useHighQuality;
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: [
if (controller.value.isInitialized &&
isZoomAble &&
!isFront)
!isFront &&
!isVideoRecording)
SizedBox(
width: 120,
child: CameraZoomButtons(
@ -407,28 +518,9 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (!isVideoRecording)
GestureDetector(
onTap: () 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 pushImageEditor(
imageFile.readAsBytes())) {
return;
}
}
setState(() {
galleryLoadedImageIsShown = false;
sharePreviewIsShown = false;
});
},
onTap: pickImageFromGallery,
child: Align(
alignment: Alignment.center,
child: Container(
@ -446,10 +538,9 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
),
),
GestureDetector(
onTap: () async {
takePicture();
},
onLongPress: () async {},
onTap: takePicture,
// onLongPress: startVideoRecording,
key: keyTriggerButton,
child: Align(
alignment: Alignment.center,
child: Container(
@ -461,13 +552,15 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
shape: BoxShape.circle,
border: Border.all(
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> {
),
],
),
),
);
}
}

View 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)
],
),
);
}
}

View file

@ -1,6 +1,10 @@
import 'dart:io';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.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/components/media_view_sizing.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/modules/all_emojis.dart';
import 'package:screenshot/screenshot.dart';
import 'package:video_player/video_player.dart';
List<Layer> layers = [];
List<Layer> undoLayers = [];
@ -24,8 +29,9 @@ List<Layer> removedLayers = [];
class ShareImageEditorView extends StatefulWidget {
const ShareImageEditorView(
{super.key, required this.imageBytes, this.sendTo});
final Future<Uint8List?> imageBytes;
{super.key, this.imageBytes, this.sendTo, this.videFilePath});
final Future<Uint8List?>? imageBytes;
final XFile? videFilePath;
final Contact? sendTo;
@override
State<ShareImageEditorView> createState() => _ShareImageEditorView();
@ -33,13 +39,13 @@ class ShareImageEditorView extends StatefulWidget {
class _ShareImageEditorView extends State<ShareImageEditorView> {
bool imageLoadedReady = false;
bool _imageSaved = false;
bool _imageSaving = false;
bool _isRealTwonly = false;
int maxShowTime = 999999;
String? sendNextMediaToUserName;
double tabDownPostion = 0;
bool sendingImage = false;
double widthRatio = 1, heightRatio = 1, pixelRatio = 1;
VideoPlayerController? videoController;
ImageItem currentImage = ImageItem();
ScreenshotController screenshotController = ScreenshotController();
@ -48,7 +54,25 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
void initState() {
super.initState();
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 {
@ -64,6 +88,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
@override
void dispose() {
layers.clear();
videoController?.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 {
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
Widget build(BuildContext context) {
pixelRatio = MediaQuery.of(context).devicePixelRatio;
@ -301,7 +362,11 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
child: SizedBox(
height: currentImage.height / pixelRatio,
width: currentImage.width / pixelRatio,
child: Screenshot(
child: Stack(
children: [
if (videoController != null)
Positioned.fill(child: VideoPlayer(videoController!)),
Screenshot(
controller: screenshotController,
child: LayersViewer(
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(
mainAxisAlignment: MainAxisAlignment.center,
children: [
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 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)
],
),
SaveToGalleryButton(
getMergedImage: getMergedImage,
sendNextMediaToUserName: sendNextMediaToUserName,
),
if (sendNextMediaToUserName != null) SizedBox(width: 10),
if (sendNextMediaToUserName != null)
@ -395,27 +425,10 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
iconColor: Theme.of(context).colorScheme.primary,
foregroundColor: Theme.of(context).colorScheme.primary,
),
onPressed: () 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);
}
},
onPressed: pushShareImageView,
child: FaIcon(FontAwesomeIcons.userPlus),
),
if (sendNextMediaToUserName != null) SizedBox(width: 10),
if (sendNextMediaToUserName == null) SizedBox(width: 20),
SizedBox(width: sendNextMediaToUserName == null ? 20 : 10),
FilledButton.icon(
icon: sendingImage
? SizedBox(
@ -429,40 +442,8 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
: FaIcon(FontAwesomeIcons.solidPaperPlane),
onPressed: () async {
if (sendingImage) return;
if (widget.sendTo != null) {
setState(() {
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);
}
if (widget.sendTo == null) return pushShareImageView();
sendImageToSinglePerson();
},
style: ButtonStyle(
padding: WidgetStateProperty.all<EdgeInsets>(

View file

@ -313,6 +313,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.7.1"
csslib:
dependency: transitive
description:
name: csslib
sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
dart_style:
dependency: transitive
description:
@ -796,6 +804,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.3"
html:
dependency: transitive
description:
name: html
sha256: "9475be233c437f0e3637af55e7702cbbe5c23a68bd56e8a5fa2d426297b7c6c8"
url: "https://pub.dev"
source: hosted
version: "0.15.5+1"
http:
dependency: "direct main"
description:
@ -1753,6 +1769,46 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:

View file

@ -66,6 +66,7 @@ dependencies:
image_picker: ^1.1.2
http: ^1.3.0
get: ^4.7.2
video_player: ^2.9.5
# avatar_maker
# avatar_maker:
# path: ./dependencies/avatar_maker/