Showing
100 changed files
with
849 additions
and
403 deletions
| ... | @@ -50,7 +50,7 @@ android { | ... | @@ -50,7 +50,7 @@ android { |
| 50 | defaultConfig { | 50 | defaultConfig { |
| 51 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). | 51 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). |
| 52 | applicationId "com.mofunsky.one_poem" | 52 | applicationId "com.mofunsky.one_poem" |
| 53 | - minSdkVersion 20 | 53 | + minSdkVersion 21 |
| 54 | targetSdkVersion 30 | 54 | targetSdkVersion 30 |
| 55 | versionCode flutterVersionCode.toInteger() | 55 | versionCode flutterVersionCode.toInteger() |
| 56 | versionName flutterVersionName | 56 | versionName flutterVersionName | ... | ... |
| ... | @@ -17,19 +17,6 @@ | ... | @@ -17,19 +17,6 @@ |
| 17 | the Android process has started. This theme is visible to the user | 17 | the Android process has started. This theme is visible to the user |
| 18 | while the Flutter UI initializes. After that, this theme continues | 18 | while the Flutter UI initializes. After that, this theme continues |
| 19 | to determine the Window background behind the Flutter UI. --> | 19 | to determine the Window background behind the Flutter UI. --> |
| 20 | - <meta-data | ||
| 21 | - android:name="io.flutter.embedding.android.NormalTheme" | ||
| 22 | - android:resource="@style/NormalTheme" | ||
| 23 | - /> | ||
| 24 | - <!-- Displays an Android View that continues showing the launch screen | ||
| 25 | - Drawable until Flutter paints its first frame, then this splash | ||
| 26 | - screen fades out. A splash screen is useful to avoid any visual | ||
| 27 | - gap between the end of Android's launch screen and the painting of | ||
| 28 | - Flutter's first frame. --> | ||
| 29 | - <meta-data | ||
| 30 | - android:name="io.flutter.embedding.android.SplashScreenDrawable" | ||
| 31 | - android:resource="@drawable/launch_background" | ||
| 32 | - /> | ||
| 33 | <intent-filter> | 20 | <intent-filter> |
| 34 | <action android:name="android.intent.action.MAIN"/> | 21 | <action android:name="android.intent.action.MAIN"/> |
| 35 | <category android:name="android.intent.category.LAUNCHER"/> | 22 | <category android:name="android.intent.category.LAUNCHER"/> | ... | ... |
assets/images/account/bg.png
deleted
100644 → 0
68.7 KB
assets/images/account/del.png
deleted
100644 → 0
922 Bytes
assets/images/account/gongshang.png
deleted
100644 → 0
2.29 KB
assets/images/account/jianhang.png
deleted
100644 → 0
2.15 KB
assets/images/account/jiaohang.png
deleted
100644 → 0
2.27 KB
assets/images/account/minsheng.png
deleted
100644 → 0
2.98 KB
assets/images/account/nonghang.png
deleted
100644 → 0
2.36 KB
assets/images/account/pufa.png
deleted
100644 → 0
2.1 KB
assets/images/account/rmb.png
deleted
100644 → 0
738 Bytes
assets/images/account/selected.png
deleted
100644 → 0
1.38 KB
assets/images/account/sm.png
deleted
100644 → 0
708 Bytes
assets/images/account/sqcg.png
deleted
100644 → 0
10 KB
assets/images/account/sqsb.png
deleted
100644 → 0
9.33 KB
assets/images/account/txwxz.png
deleted
100644 → 0
483 Bytes
assets/images/account/txxz.png
deleted
100644 → 0
723 Bytes
assets/images/account/wechat.png
deleted
100644 → 0
974 Bytes
assets/images/account/xingye.png
deleted
100644 → 0
2.35 KB
assets/images/account/yhk.png
deleted
100644 → 0
276 Bytes
assets/images/account/zhaohang.png
deleted
100644 → 0
2.69 KB
assets/images/account/zhonghang.png
deleted
100644 → 0
2.2 KB
assets/images/account/zhongxin.png
deleted
100644 → 0
1.88 KB
470 Bytes
assets/images/home/icon_order.png
deleted
100644 → 0
126 Bytes
assets/images/home/icon_shop.png
deleted
100644 → 0
400 Bytes
646 Bytes
460 Bytes
423 Bytes
387 Bytes
726 Bytes
720 Bytes
653 Bytes
489 Bytes
423 Bytes
411 Bytes
665 Bytes
647 Bytes
838 Bytes
769 Bytes
assets/images/poem/dps_n.png
deleted
100644 → 0
700 Bytes
assets/images/poem/dps_s.png
deleted
100644 → 0
1.68 KB
assets/images/poem/dwc_n.png
deleted
100644 → 0
838 Bytes
assets/images/poem/dwc_s.png
deleted
100644 → 0
1.8 KB
assets/images/poem/ic_check.png
deleted
100644 → 0
924 Bytes
assets/images/poem/icon_address.png
deleted
100644 → 0
826 Bytes
assets/images/poem/icon_avatar.png
deleted
100644 → 0
4.17 KB
assets/images/poem/icon_calendar.png
deleted
100644 → 0
1.03 KB
334 Bytes
assets/images/poem/icon_goods.png
deleted
100644 → 0
16.8 KB
assets/images/poem/icon_phone.png
deleted
100644 → 0
1.29 KB
assets/images/poem/icon_search.png
deleted
100644 → 0
562 Bytes
assets/images/poem/order_bg.png
deleted
100644 → 0
31.9 KB
assets/images/poem/order_bg1.png
deleted
100644 → 0
32.9 KB
assets/images/poem/order_delete.png
deleted
100644 → 0
1.15 KB
assets/images/poem/order_search.png
deleted
100644 → 0
955 Bytes
assets/images/poem/xdd_n.png
deleted
100644 → 0
760 Bytes
assets/images/poem/xdd_s.png
deleted
100644 → 0
1.52 KB
assets/images/poem/yqx_n.png
deleted
100644 → 0
908 Bytes
assets/images/poem/yqx_s.png
deleted
100644 → 0
1.79 KB
assets/images/poem/ywc_n.png
deleted
100644 → 0
1.05 KB
assets/images/poem/ywc_s.png
deleted
100644 → 0
1.95 KB
| ... | @@ -3,7 +3,7 @@ import 'dart:ui'; | ... | @@ -3,7 +3,7 @@ import 'dart:ui'; |
| 3 | import 'package:flutter/cupertino.dart'; | 3 | import 'package:flutter/cupertino.dart'; |
| 4 | import 'package:flutter/material.dart'; | 4 | import 'package:flutter/material.dart'; |
| 5 | import 'package:one_poem/poem/widgets/poem_content.dart'; | 5 | import 'package:one_poem/poem/widgets/poem_content.dart'; |
| 6 | -import 'package:one_poem/recorder/widgets/poem_voice_widget.dart'; | 6 | +import 'package:one_poem/recorder/audio/widgets/poem_voice_widget.dart'; |
| 7 | import 'package:one_poem/routers/fluro_navigator.dart'; | 7 | import 'package:one_poem/routers/fluro_navigator.dart'; |
| 8 | import 'package:one_poem/util/toast_utils.dart'; | 8 | import 'package:one_poem/util/toast_utils.dart'; |
| 9 | import 'package:one_poem/widgets/bars/home_action_bar.dart'; | 9 | import 'package:one_poem/widgets/bars/home_action_bar.dart'; | ... | ... |
| 1 | -// Copyright 2013 The Flutter Authors. All rights reserved. | ||
| 2 | -// Use of this source code is governed by a BSD-style license that can be | ||
| 3 | -// found in the LICENSE file. | ||
| 4 | - | ||
| 5 | -// ignore_for_file: public_member_api_docs | ||
| 6 | - | ||
| 7 | -import 'dart:async'; | ||
| 8 | import 'dart:io'; | 1 | import 'dart:io'; |
| 9 | 2 | ||
| 10 | -import 'package:flutter/foundation.dart'; | 3 | +import 'package:camera/camera.dart'; |
| 11 | import 'package:flutter/material.dart'; | 4 | import 'package:flutter/material.dart'; |
| 12 | -import 'package:image_picker/image_picker.dart'; | 5 | +import 'package:flutter/services.dart'; |
| 13 | -import 'package:one_poem/widgets/my_app_bar.dart'; | 6 | +import 'package:one_poem/recorder/video/preview_screen.dart'; |
| 7 | +import 'package:one_poem/util/toast_utils.dart'; | ||
| 8 | +import 'package:path_provider/path_provider.dart'; | ||
| 14 | import 'package:video_player/video_player.dart'; | 9 | import 'package:video_player/video_player.dart'; |
| 15 | 10 | ||
| 16 | class PoemRecordVideoPage extends StatefulWidget { | 11 | class PoemRecordVideoPage extends StatefulWidget { |
| 17 | - const PoemRecordVideoPage({Key? key, this.title}) : super(key: key); | 12 | + const PoemRecordVideoPage({Key? key}) : super(key: key); |
| 18 | - | ||
| 19 | - final String? title; | ||
| 20 | 13 | ||
| 21 | @override | 14 | @override |
| 22 | _PoemRecordVideoPageState createState() => _PoemRecordVideoPageState(); | 15 | _PoemRecordVideoPageState createState() => _PoemRecordVideoPageState(); |
| 23 | } | 16 | } |
| 24 | 17 | ||
| 25 | -class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> { | 18 | +class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> |
| 26 | - List<XFile>? _imageFileList; | 19 | + with WidgetsBindingObserver { |
| 20 | + CameraController? controller; | ||
| 21 | + VideoPlayerController? videoController; | ||
| 27 | 22 | ||
| 28 | - set _imageFile(XFile? value) { | 23 | + File? _imageFile; |
| 29 | - _imageFileList = value == null ? null : [value]; | 24 | + File? _videoFile; |
| 30 | - } | 25 | + |
| 26 | + // Initial values | ||
| 27 | + bool _isCameraInitialized = false; | ||
| 28 | + bool _isRearCameraSelected = true; | ||
| 29 | + bool _isVideoCameraSelected = false; | ||
| 30 | + bool _isRecordingInProgress = false; | ||
| 31 | + double _minAvailableExposureOffset = 0.0; | ||
| 32 | + double _maxAvailableExposureOffset = 0.0; | ||
| 33 | + double _minAvailableZoom = 1.0; | ||
| 34 | + double _maxAvailableZoom = 1.0; | ||
| 31 | 35 | ||
| 32 | - dynamic _pickImageError; | 36 | + // Current values |
| 33 | - bool isVideo = false; | 37 | + double _currentZoomLevel = 1.0; |
| 38 | + double _currentExposureOffset = 0.0; | ||
| 39 | + FlashMode? _currentFlashMode; | ||
| 34 | 40 | ||
| 35 | - VideoPlayerController? _controller; | 41 | + List<File> allFileList = []; |
| 36 | - VideoPlayerController? _toBeDisposed; | ||
| 37 | - String? _retrieveDataError; | ||
| 38 | 42 | ||
| 39 | - final ImagePicker _picker = ImagePicker(); | 43 | + final resolutionPresets = ResolutionPreset.values; |
| 40 | - final TextEditingController maxWidthController = TextEditingController(); | ||
| 41 | - final TextEditingController maxHeightController = TextEditingController(); | ||
| 42 | - final TextEditingController qualityController = TextEditingController(); | ||
| 43 | 44 | ||
| 44 | - Future<void> _playVideo(XFile? file) async { | 45 | + ResolutionPreset currentResolutionPreset = ResolutionPreset.high; |
| 45 | - if (file != null && mounted) { | 46 | + |
| 46 | - await _disposeVideoController(); | 47 | + List<CameraDescription>? cameras = []; |
| 47 | - late VideoPlayerController controller; | 48 | + @override |
| 48 | - if (kIsWeb) { | 49 | + void initState() { |
| 49 | - controller = VideoPlayerController.network(file.path); | 50 | + // Hide the status bar in Android |
| 51 | + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); | ||
| 52 | + initCameras().then((value) { | ||
| 53 | + cameras = value; | ||
| 54 | + if (cameras != null) { | ||
| 55 | + onNewCameraSelected(cameras![0]); | ||
| 56 | + refreshAlreadyCapturedImages(); | ||
| 50 | } else { | 57 | } else { |
| 51 | - controller = VideoPlayerController.file(File(file.path)); | 58 | + Toast.show("摄像头出错啦~"); |
| 52 | } | 59 | } |
| 53 | - _controller = controller; | 60 | + }); |
| 54 | - // In web, most browsers won't honor a programmatic call to .play | 61 | + super.initState(); |
| 55 | - // if the video has a sound track (and is not muted). | 62 | + } |
| 56 | - // Mute the video so it auto-plays in web! | 63 | + |
| 57 | - // This is not needed if the call to .play is the result of user | 64 | + Future<List<CameraDescription>?> initCameras() async { |
| 58 | - // interaction (clicking on a "play" button, for example). | 65 | + try { |
| 59 | - final double volume = kIsWeb ? 0.0 : 1.0; | 66 | + WidgetsFlutterBinding.ensureInitialized(); |
| 60 | - await controller.setVolume(volume); | 67 | + List<CameraDescription> cameras = await availableCameras(); |
| 61 | - await controller.initialize(); | 68 | + return cameras; |
| 62 | - await controller.setLooping(true); | 69 | + } on CameraException catch (e) { |
| 63 | - await controller.play(); | 70 | + return null; |
| 64 | - setState(() {}); | ||
| 65 | } | 71 | } |
| 66 | } | 72 | } |
| 67 | 73 | ||
| 68 | - void _onImageButtonPressed(ImageSource source, | 74 | + refreshAlreadyCapturedImages() async { |
| 69 | - {BuildContext? context, bool isMultiImage = false}) async { | 75 | + final directory = await getApplicationDocumentsDirectory(); |
| 70 | - if (_controller != null) { | 76 | + List<FileSystemEntity> fileList = await directory.list().toList(); |
| 71 | - await _controller!.setVolume(0.0); | 77 | + allFileList.clear(); |
| 78 | + List<Map<int, dynamic>> fileNames = []; | ||
| 79 | + | ||
| 80 | + for (var file in fileList) { | ||
| 81 | + if (file.path.contains('.jpg') || file.path.contains('.mp4')) { | ||
| 82 | + allFileList.add(File(file.path)); | ||
| 83 | + | ||
| 84 | + String name = file.path.split('/').last.split('.').first; | ||
| 85 | + fileNames.add({0: int.parse(name), 1: file.path.split('/').last}); | ||
| 86 | + } | ||
| 72 | } | 87 | } |
| 73 | - if (isVideo) { | 88 | + |
| 74 | - final XFile? file = await _picker.pickVideo( | 89 | + if (fileNames.isNotEmpty) { |
| 75 | - source: source, maxDuration: const Duration(seconds: 10)); | 90 | + final recentFile = |
| 76 | - await _playVideo(file); | 91 | + fileNames.reduce((curr, next) => curr[0] > next[0] ? curr : next); |
| 77 | - } else if (isMultiImage) { | 92 | + String recentFileName = recentFile[1]; |
| 78 | - await _displayPickImageDialog(context!, | 93 | + if (recentFileName.contains('.mp4')) { |
| 79 | - (double? maxWidth, double? maxHeight, int? quality) async { | 94 | + _videoFile = File('${directory.path}/$recentFileName'); |
| 80 | - try { | 95 | + _imageFile = null; |
| 81 | - final pickedFileList = await _picker.pickMultiImage( | 96 | + _startVideoPlayer(); |
| 82 | - maxWidth: maxWidth, | 97 | + } else { |
| 83 | - maxHeight: maxHeight, | 98 | + _imageFile = File('${directory.path}/$recentFileName'); |
| 84 | - imageQuality: quality, | 99 | + _videoFile = null; |
| 85 | - ); | 100 | + } |
| 86 | - setState(() { | 101 | + |
| 87 | - _imageFileList = pickedFileList; | 102 | + setState(() {}); |
| 88 | - }); | ||
| 89 | - } catch (e) { | ||
| 90 | - setState(() { | ||
| 91 | - _pickImageError = e; | ||
| 92 | - }); | ||
| 93 | - } | ||
| 94 | - }); | ||
| 95 | - } else { | ||
| 96 | - await _displayPickImageDialog(context!, | ||
| 97 | - (double? maxWidth, double? maxHeight, int? quality) async { | ||
| 98 | - try { | ||
| 99 | - final pickedFile = await _picker.pickImage( | ||
| 100 | - source: source, | ||
| 101 | - maxWidth: maxWidth, | ||
| 102 | - maxHeight: maxHeight, | ||
| 103 | - imageQuality: quality, | ||
| 104 | - ); | ||
| 105 | - setState(() { | ||
| 106 | - _imageFile = pickedFile; | ||
| 107 | - }); | ||
| 108 | - } catch (e) { | ||
| 109 | - setState(() { | ||
| 110 | - _pickImageError = e; | ||
| 111 | - }); | ||
| 112 | - } | ||
| 113 | - }); | ||
| 114 | } | 103 | } |
| 115 | } | 104 | } |
| 116 | 105 | ||
| 117 | - @override | 106 | + Future<XFile?> takePicture() async { |
| 118 | - void deactivate() { | 107 | + final CameraController? cameraController = controller; |
| 119 | - if (_controller != null) { | 108 | + |
| 120 | - _controller!.setVolume(0.0); | 109 | + if (cameraController!.value.isTakingPicture) { |
| 121 | - _controller!.pause(); | 110 | + // A capture is already pending, do nothing. |
| 111 | + return null; | ||
| 122 | } | 112 | } |
| 123 | - super.deactivate(); | ||
| 124 | - } | ||
| 125 | 113 | ||
| 126 | - @override | 114 | + try { |
| 127 | - void dispose() { | 115 | + XFile file = await cameraController.takePicture(); |
| 128 | - _disposeVideoController(); | 116 | + return file; |
| 129 | - maxWidthController.dispose(); | 117 | + } on CameraException catch (e) { |
| 130 | - maxHeightController.dispose(); | 118 | + return null; |
| 131 | - qualityController.dispose(); | 119 | + } |
| 132 | - super.dispose(); | ||
| 133 | } | 120 | } |
| 134 | 121 | ||
| 135 | - Future<void> _disposeVideoController() async { | 122 | + Future<void> _startVideoPlayer() async { |
| 136 | - if (_toBeDisposed != null) { | 123 | + if (_videoFile != null) { |
| 137 | - await _toBeDisposed!.dispose(); | 124 | + videoController = VideoPlayerController.file(_videoFile!); |
| 125 | + await videoController!.initialize().then((_) { | ||
| 126 | + // Ensure the first frame is shown after the video is initialized, | ||
| 127 | + // even before the play button has been pressed. | ||
| 128 | + setState(() {}); | ||
| 129 | + }); | ||
| 130 | + await videoController!.setLooping(true); | ||
| 131 | + await videoController!.play(); | ||
| 138 | } | 132 | } |
| 139 | - _toBeDisposed = _controller; | ||
| 140 | - _controller = null; | ||
| 141 | } | 133 | } |
| 142 | 134 | ||
| 143 | - Widget _previewVideo() { | 135 | + Future<void> startVideoRecording() async { |
| 144 | - final Text? retrieveError = _getRetrieveErrorWidget(); | 136 | + final CameraController? cameraController = controller; |
| 145 | - if (retrieveError != null) { | 137 | + |
| 146 | - return retrieveError; | 138 | + if (controller!.value.isRecordingVideo) { |
| 139 | + // A recording has already started, do nothing. | ||
| 140 | + return; | ||
| 147 | } | 141 | } |
| 148 | - if (_controller == null) { | 142 | + |
| 149 | - return const Text( | 143 | + try { |
| 150 | - 'You have not yet picked a video', | 144 | + await cameraController!.startVideoRecording(); |
| 151 | - textAlign: TextAlign.center, | 145 | + setState(() { |
| 152 | - ); | 146 | + _isRecordingInProgress = true; |
| 147 | + }); | ||
| 148 | + } on CameraException catch (e) { | ||
| 149 | + // print('Error starting to record video: $e'); | ||
| 153 | } | 150 | } |
| 154 | - return Padding( | ||
| 155 | - padding: const EdgeInsets.all(10.0), | ||
| 156 | - child: AspectRatioVideo(_controller), | ||
| 157 | - ); | ||
| 158 | } | 151 | } |
| 159 | 152 | ||
| 160 | - Widget _previewImages() { | 153 | + Future<XFile?> stopVideoRecording() async { |
| 161 | - final Text? retrieveError = _getRetrieveErrorWidget(); | 154 | + if (!controller!.value.isRecordingVideo) { |
| 162 | - if (retrieveError != null) { | 155 | + // Recording is already is stopped state |
| 163 | - return retrieveError; | 156 | + return null; |
| 164 | } | 157 | } |
| 165 | - if (_imageFileList != null) { | ||
| 166 | - return Semantics( | ||
| 167 | - child: ListView.builder( | ||
| 168 | - key: UniqueKey(), | ||
| 169 | - itemBuilder: (context, index) { | ||
| 170 | - // Why network for web? | ||
| 171 | - // See https://pub.dev/packages/image_picker#getting-ready-for-the-web-platform | ||
| 172 | - return Semantics( | ||
| 173 | - label: 'image_picker_example_picked_image', | ||
| 174 | - child: kIsWeb | ||
| 175 | - ? Image.network(_imageFileList![index].path) | ||
| 176 | - : Image.file(File(_imageFileList![index].path)), | ||
| 177 | - ); | ||
| 178 | - }, | ||
| 179 | - itemCount: _imageFileList!.length, | ||
| 180 | - ), | ||
| 181 | - label: 'image_picker_example_picked_images'); | ||
| 182 | - } else if (_pickImageError != null) { | ||
| 183 | - return Text( | ||
| 184 | - 'Pick image error: $_pickImageError', | ||
| 185 | - textAlign: TextAlign.center, | ||
| 186 | - ); | ||
| 187 | - } else { | ||
| 188 | - return const Text( | ||
| 189 | - 'You have not yet picked an image.', | ||
| 190 | - textAlign: TextAlign.center, | ||
| 191 | - ); | ||
| 192 | - } | ||
| 193 | - } | ||
| 194 | 158 | ||
| 195 | - Widget _handlePreview() { | 159 | + try { |
| 196 | - if (isVideo) { | 160 | + XFile file = await controller!.stopVideoRecording(); |
| 197 | - return _previewVideo(); | 161 | + setState(() { |
| 198 | - } else { | 162 | + _isRecordingInProgress = false; |
| 199 | - return _previewImages(); | 163 | + }); |
| 164 | + return file; | ||
| 165 | + } on CameraException catch (e) { | ||
| 166 | + return null; | ||
| 200 | } | 167 | } |
| 201 | } | 168 | } |
| 202 | 169 | ||
| 203 | - Future<void> retrieveLostData() async { | 170 | + Future<void> pauseVideoRecording() async { |
| 204 | - final LostDataResponse response = await _picker.retrieveLostData(); | 171 | + if (!controller!.value.isRecordingVideo) { |
| 205 | - if (response.isEmpty) { | 172 | + // Video recording is not in progress |
| 206 | return; | 173 | return; |
| 207 | } | 174 | } |
| 208 | - if (response.file != null) { | 175 | + |
| 209 | - if (response.type == RetrieveType.video) { | 176 | + try { |
| 210 | - isVideo = true; | 177 | + await controller!.pauseVideoRecording(); |
| 211 | - await _playVideo(response.file); | 178 | + } on CameraException catch (e) { |
| 212 | - } else { | 179 | + // print('Error pausing video recording: $e'); |
| 213 | - isVideo = false; | ||
| 214 | - setState(() { | ||
| 215 | - _imageFile = response.file; | ||
| 216 | - _imageFileList = response.files; | ||
| 217 | - }); | ||
| 218 | - } | ||
| 219 | - } else { | ||
| 220 | - _retrieveDataError = response.exception!.code; | ||
| 221 | } | 180 | } |
| 222 | } | 181 | } |
| 223 | 182 | ||
| 224 | - @override | 183 | + Future<void> resumeVideoRecording() async { |
| 225 | - Widget build(BuildContext context) { | 184 | + if (!controller!.value.isRecordingVideo) { |
| 226 | - return Scaffold( | 185 | + // No video recording was in progress |
| 227 | - appBar: const MyAppBar( | 186 | + return; |
| 228 | - isBack: true, | 187 | + } |
| 229 | - isTransparent: true, | ||
| 230 | - ), | ||
| 231 | - body: Center( | ||
| 232 | - child: !kIsWeb && defaultTargetPlatform == TargetPlatform.android | ||
| 233 | - ? FutureBuilder<void>( | ||
| 234 | - future: retrieveLostData(), | ||
| 235 | - builder: (BuildContext context, AsyncSnapshot<void> snapshot) { | ||
| 236 | - switch (snapshot.connectionState) { | ||
| 237 | - case ConnectionState.none: | ||
| 238 | - case ConnectionState.waiting: | ||
| 239 | - return const Text( | ||
| 240 | - 'You have not yet picked an image.', | ||
| 241 | - textAlign: TextAlign.center, | ||
| 242 | - ); | ||
| 243 | - case ConnectionState.done: | ||
| 244 | - return _handlePreview(); | ||
| 245 | - default: | ||
| 246 | - if (snapshot.hasError) { | ||
| 247 | - return Text( | ||
| 248 | - 'Pick image/video error: ${snapshot.error}}', | ||
| 249 | - textAlign: TextAlign.center, | ||
| 250 | - ); | ||
| 251 | - } else { | ||
| 252 | - return const Text( | ||
| 253 | - 'You have not yet picked an image.', | ||
| 254 | - textAlign: TextAlign.center, | ||
| 255 | - ); | ||
| 256 | - } | ||
| 257 | - } | ||
| 258 | - }, | ||
| 259 | - ) | ||
| 260 | - : _handlePreview(), | ||
| 261 | - ), | ||
| 262 | - floatingActionButton: Column( | ||
| 263 | - mainAxisAlignment: MainAxisAlignment.end, | ||
| 264 | - children: <Widget>[ | ||
| 265 | - Semantics( | ||
| 266 | - label: 'image_picker_example_from_gallery', | ||
| 267 | - child: FloatingActionButton( | ||
| 268 | - onPressed: () { | ||
| 269 | - isVideo = false; | ||
| 270 | - _onImageButtonPressed(ImageSource.gallery, context: context); | ||
| 271 | - }, | ||
| 272 | - heroTag: 'image0', | ||
| 273 | - tooltip: 'Pick Image from gallery', | ||
| 274 | - child: const Icon(Icons.photo), | ||
| 275 | - ), | ||
| 276 | - ), | ||
| 277 | - Padding( | ||
| 278 | - padding: const EdgeInsets.only(top: 16.0), | ||
| 279 | - child: FloatingActionButton( | ||
| 280 | - onPressed: () { | ||
| 281 | - isVideo = false; | ||
| 282 | - _onImageButtonPressed( | ||
| 283 | - ImageSource.gallery, | ||
| 284 | - context: context, | ||
| 285 | - isMultiImage: true, | ||
| 286 | - ); | ||
| 287 | - }, | ||
| 288 | - heroTag: 'image1', | ||
| 289 | - tooltip: 'Pick Multiple Image from gallery', | ||
| 290 | - child: const Icon(Icons.photo_library), | ||
| 291 | - ), | ||
| 292 | - ), | ||
| 293 | - Padding( | ||
| 294 | - padding: const EdgeInsets.only(top: 16.0), | ||
| 295 | - child: FloatingActionButton( | ||
| 296 | - onPressed: () { | ||
| 297 | - isVideo = false; | ||
| 298 | - _onImageButtonPressed(ImageSource.camera, context: context); | ||
| 299 | - }, | ||
| 300 | - heroTag: 'image2', | ||
| 301 | - tooltip: 'Take a Photo', | ||
| 302 | - child: const Icon(Icons.camera_alt), | ||
| 303 | - ), | ||
| 304 | - ), | ||
| 305 | - Padding( | ||
| 306 | - padding: const EdgeInsets.only(top: 16.0), | ||
| 307 | - child: FloatingActionButton( | ||
| 308 | - backgroundColor: Colors.red, | ||
| 309 | - onPressed: () { | ||
| 310 | - isVideo = true; | ||
| 311 | - _onImageButtonPressed(ImageSource.gallery); | ||
| 312 | - }, | ||
| 313 | - heroTag: 'video0', | ||
| 314 | - tooltip: 'Pick Video from gallery', | ||
| 315 | - child: const Icon(Icons.video_library), | ||
| 316 | - ), | ||
| 317 | - ), | ||
| 318 | - Padding( | ||
| 319 | - padding: const EdgeInsets.only(top: 16.0), | ||
| 320 | - child: FloatingActionButton( | ||
| 321 | - backgroundColor: Colors.red, | ||
| 322 | - onPressed: () { | ||
| 323 | - isVideo = true; | ||
| 324 | - _onImageButtonPressed(ImageSource.camera); | ||
| 325 | - }, | ||
| 326 | - heroTag: 'video1', | ||
| 327 | - tooltip: 'Take a Video', | ||
| 328 | - child: const Icon(Icons.videocam), | ||
| 329 | - ), | ||
| 330 | - ), | ||
| 331 | - ], | ||
| 332 | - ), | ||
| 333 | - ); | ||
| 334 | - } | ||
| 335 | 188 | ||
| 336 | - Text? _getRetrieveErrorWidget() { | 189 | + try { |
| 337 | - if (_retrieveDataError != null) { | 190 | + await controller!.resumeVideoRecording(); |
| 338 | - final Text result = Text(_retrieveDataError!); | 191 | + } on CameraException catch (e) { |
| 339 | - _retrieveDataError = null; | 192 | + // print('Error resuming video recording: $e'); |
| 340 | - return result; | ||
| 341 | } | 193 | } |
| 342 | - return null; | ||
| 343 | } | 194 | } |
| 344 | 195 | ||
| 345 | - Future<void> _displayPickImageDialog( | 196 | + void resetCameraValues() async { |
| 346 | - BuildContext context, OnPickImageCallback onPick) async { | 197 | + _currentZoomLevel = 1.0; |
| 347 | - return showDialog( | 198 | + _currentExposureOffset = 0.0; |
| 348 | - context: context, | ||
| 349 | - builder: (context) { | ||
| 350 | - return AlertDialog( | ||
| 351 | - title: const Text('Add optional parameters'), | ||
| 352 | - content: Column( | ||
| 353 | - children: <Widget>[ | ||
| 354 | - TextField( | ||
| 355 | - controller: maxWidthController, | ||
| 356 | - keyboardType: | ||
| 357 | - const TextInputType.numberWithOptions(decimal: true), | ||
| 358 | - decoration: const InputDecoration( | ||
| 359 | - hintText: "Enter maxWidth if desired"), | ||
| 360 | - ), | ||
| 361 | - TextField( | ||
| 362 | - controller: maxHeightController, | ||
| 363 | - keyboardType: | ||
| 364 | - const TextInputType.numberWithOptions(decimal: true), | ||
| 365 | - decoration: const InputDecoration( | ||
| 366 | - hintText: "Enter maxHeight if desired"), | ||
| 367 | - ), | ||
| 368 | - TextField( | ||
| 369 | - controller: qualityController, | ||
| 370 | - keyboardType: TextInputType.number, | ||
| 371 | - decoration: const InputDecoration( | ||
| 372 | - hintText: "Enter quality if desired"), | ||
| 373 | - ), | ||
| 374 | - ], | ||
| 375 | - ), | ||
| 376 | - actions: <Widget>[ | ||
| 377 | - TextButton( | ||
| 378 | - child: const Text('CANCEL'), | ||
| 379 | - onPressed: () { | ||
| 380 | - Navigator.of(context).pop(); | ||
| 381 | - }, | ||
| 382 | - ), | ||
| 383 | - TextButton( | ||
| 384 | - child: const Text('PICK'), | ||
| 385 | - onPressed: () { | ||
| 386 | - double? width = maxWidthController.text.isNotEmpty | ||
| 387 | - ? double.parse(maxWidthController.text) | ||
| 388 | - : null; | ||
| 389 | - double? height = maxHeightController.text.isNotEmpty | ||
| 390 | - ? double.parse(maxHeightController.text) | ||
| 391 | - : null; | ||
| 392 | - int? quality = qualityController.text.isNotEmpty | ||
| 393 | - ? int.parse(qualityController.text) | ||
| 394 | - : null; | ||
| 395 | - onPick(width, height, quality); | ||
| 396 | - Navigator.of(context).pop(); | ||
| 397 | - }), | ||
| 398 | - ], | ||
| 399 | - ); | ||
| 400 | - }); | ||
| 401 | } | 199 | } |
| 402 | -} | ||
| 403 | 200 | ||
| 404 | -typedef OnPickImageCallback = void Function( | 201 | + void onNewCameraSelected(CameraDescription cameraDescription) async { |
| 405 | - double? maxWidth, double? maxHeight, int? quality); | 202 | + final previousCameraController = controller; |
| 406 | 203 | ||
| 407 | -class AspectRatioVideo extends StatefulWidget { | 204 | + final CameraController cameraController = CameraController( |
| 408 | - const AspectRatioVideo(this.controller, {Key? key}) : super(key: key); | 205 | + cameraDescription, |
| 206 | + currentResolutionPreset, | ||
| 207 | + imageFormatGroup: ImageFormatGroup.jpeg, | ||
| 208 | + ); | ||
| 409 | 209 | ||
| 410 | - final VideoPlayerController? controller; | 210 | + await previousCameraController?.dispose(); |
| 411 | 211 | ||
| 412 | - @override | 212 | + resetCameraValues(); |
| 413 | - AspectRatioVideoState createState() => AspectRatioVideoState(); | ||
| 414 | -} | ||
| 415 | 213 | ||
| 416 | -class AspectRatioVideoState extends State<AspectRatioVideo> { | 214 | + if (mounted) { |
| 417 | - VideoPlayerController? get controller => widget.controller; | 215 | + setState(() { |
| 418 | - bool initialized = false; | 216 | + controller = cameraController; |
| 217 | + }); | ||
| 218 | + } | ||
| 419 | 219 | ||
| 420 | - void _onVideoControllerUpdate() { | 220 | + // Update UI if controller updated |
| 421 | - if (!mounted) { | 221 | + cameraController.addListener(() { |
| 422 | - return; | 222 | + if (mounted) setState(() {}); |
| 223 | + }); | ||
| 224 | + | ||
| 225 | + try { | ||
| 226 | + await cameraController.initialize(); | ||
| 227 | + await Future.wait([ | ||
| 228 | + cameraController | ||
| 229 | + .getMinExposureOffset() | ||
| 230 | + .then((value) => _minAvailableExposureOffset = value), | ||
| 231 | + cameraController | ||
| 232 | + .getMaxExposureOffset() | ||
| 233 | + .then((value) => _maxAvailableExposureOffset = value), | ||
| 234 | + cameraController | ||
| 235 | + .getMaxZoomLevel() | ||
| 236 | + .then((value) => _maxAvailableZoom = value), | ||
| 237 | + cameraController | ||
| 238 | + .getMinZoomLevel() | ||
| 239 | + .then((value) => _minAvailableZoom = value), | ||
| 240 | + ]); | ||
| 241 | + | ||
| 242 | + _currentFlashMode = controller!.value.flashMode; | ||
| 243 | + } on CameraException catch (e) { | ||
| 244 | + // print('Error initializing camera: $e'); | ||
| 423 | } | 245 | } |
| 424 | - if (initialized != controller!.value.isInitialized) { | 246 | + |
| 425 | - initialized = controller!.value.isInitialized; | 247 | + if (mounted) { |
| 426 | - setState(() {}); | 248 | + setState(() { |
| 249 | + _isCameraInitialized = controller!.value.isInitialized; | ||
| 250 | + }); | ||
| 427 | } | 251 | } |
| 428 | } | 252 | } |
| 429 | 253 | ||
| 430 | @override | 254 | @override |
| 431 | - void initState() { | 255 | + void didChangeAppLifecycleState(AppLifecycleState state) { |
| 432 | - super.initState(); | 256 | + final CameraController? cameraController = controller; |
| 433 | - controller!.addListener(_onVideoControllerUpdate); | 257 | + |
| 258 | + // App state changed before we got the chance to initialize. | ||
| 259 | + if (cameraController == null || !cameraController.value.isInitialized) { | ||
| 260 | + return; | ||
| 261 | + } | ||
| 262 | + | ||
| 263 | + if (state == AppLifecycleState.inactive) { | ||
| 264 | + cameraController.dispose(); | ||
| 265 | + } else if (state == AppLifecycleState.resumed) { | ||
| 266 | + onNewCameraSelected(cameraController.description); | ||
| 267 | + } | ||
| 434 | } | 268 | } |
| 435 | 269 | ||
| 436 | @override | 270 | @override |
| 437 | void dispose() { | 271 | void dispose() { |
| 438 | - controller!.removeListener(_onVideoControllerUpdate); | 272 | + controller?.dispose(); |
| 273 | + videoController?.dispose(); | ||
| 439 | super.dispose(); | 274 | super.dispose(); |
| 440 | } | 275 | } |
| 441 | 276 | ||
| 442 | @override | 277 | @override |
| 443 | Widget build(BuildContext context) { | 278 | Widget build(BuildContext context) { |
| 444 | - if (initialized) { | 279 | + return SafeArea( |
| 445 | - return Center( | 280 | + child: Scaffold( |
| 446 | - child: AspectRatio( | 281 | + backgroundColor: Colors.black, |
| 447 | - aspectRatio: controller!.value.aspectRatio, | 282 | + body: _isCameraInitialized |
| 448 | - child: VideoPlayer(controller!), | 283 | + ? Column( |
| 449 | - ), | 284 | + children: [ |
| 450 | - ); | 285 | + AspectRatio( |
| 451 | - } else { | 286 | + aspectRatio: 1 / controller!.value.aspectRatio, |
| 452 | - return Container(); | 287 | + child: Stack( |
| 453 | - } | 288 | + children: [ |
| 289 | + controller!.buildPreview(), | ||
| 290 | + Padding( | ||
| 291 | + padding: const EdgeInsets.fromLTRB( | ||
| 292 | + 16.0, | ||
| 293 | + 8.0, | ||
| 294 | + 16.0, | ||
| 295 | + 8.0, | ||
| 296 | + ), | ||
| 297 | + child: Column( | ||
| 298 | + crossAxisAlignment: CrossAxisAlignment.end, | ||
| 299 | + children: [ | ||
| 300 | + Align( | ||
| 301 | + alignment: Alignment.topRight, | ||
| 302 | + child: Container( | ||
| 303 | + decoration: BoxDecoration( | ||
| 304 | + color: Colors.black87, | ||
| 305 | + borderRadius: BorderRadius.circular(10.0), | ||
| 306 | + ), | ||
| 307 | + child: Padding( | ||
| 308 | + padding: const EdgeInsets.only( | ||
| 309 | + left: 8.0, | ||
| 310 | + right: 8.0, | ||
| 311 | + ), | ||
| 312 | + child: DropdownButton<ResolutionPreset>( | ||
| 313 | + dropdownColor: Colors.black87, | ||
| 314 | + underline: Container(), | ||
| 315 | + value: currentResolutionPreset, | ||
| 316 | + items: [ | ||
| 317 | + for (ResolutionPreset preset | ||
| 318 | + in resolutionPresets) | ||
| 319 | + DropdownMenuItem( | ||
| 320 | + child: Text( | ||
| 321 | + preset | ||
| 322 | + .toString() | ||
| 323 | + .split('.')[1] | ||
| 324 | + .toUpperCase(), | ||
| 325 | + style: const TextStyle( | ||
| 326 | + color: Colors.white), | ||
| 327 | + ), | ||
| 328 | + value: preset, | ||
| 329 | + ) | ||
| 330 | + ], | ||
| 331 | + onChanged: (value) { | ||
| 332 | + setState(() { | ||
| 333 | + currentResolutionPreset = value!; | ||
| 334 | + _isCameraInitialized = false; | ||
| 335 | + }); | ||
| 336 | + onNewCameraSelected( | ||
| 337 | + controller!.description); | ||
| 338 | + }, | ||
| 339 | + hint: const Text("Select item"), | ||
| 340 | + ), | ||
| 341 | + ), | ||
| 342 | + ), | ||
| 343 | + ), | ||
| 344 | + // Spacer(), | ||
| 345 | + Padding( | ||
| 346 | + padding: const EdgeInsets.only( | ||
| 347 | + right: 8.0, top: 16.0), | ||
| 348 | + child: Container( | ||
| 349 | + decoration: BoxDecoration( | ||
| 350 | + color: Colors.white, | ||
| 351 | + borderRadius: BorderRadius.circular(10.0), | ||
| 352 | + ), | ||
| 353 | + child: Padding( | ||
| 354 | + padding: const EdgeInsets.all(8.0), | ||
| 355 | + child: Text( | ||
| 356 | + _currentExposureOffset | ||
| 357 | + .toStringAsFixed(1) + | ||
| 358 | + 'x', | ||
| 359 | + style: | ||
| 360 | + const TextStyle(color: Colors.black), | ||
| 361 | + ), | ||
| 362 | + ), | ||
| 363 | + ), | ||
| 364 | + ), | ||
| 365 | + Expanded( | ||
| 366 | + child: RotatedBox( | ||
| 367 | + quarterTurns: 3, | ||
| 368 | + child: SizedBox( | ||
| 369 | + height: 30, | ||
| 370 | + child: Slider( | ||
| 371 | + value: _currentExposureOffset, | ||
| 372 | + min: _minAvailableExposureOffset, | ||
| 373 | + max: _maxAvailableExposureOffset, | ||
| 374 | + activeColor: Colors.white, | ||
| 375 | + inactiveColor: Colors.white30, | ||
| 376 | + onChanged: (value) async { | ||
| 377 | + setState(() { | ||
| 378 | + _currentExposureOffset = value; | ||
| 379 | + }); | ||
| 380 | + await controller! | ||
| 381 | + .setExposureOffset(value); | ||
| 382 | + }, | ||
| 383 | + ), | ||
| 384 | + ), | ||
| 385 | + ), | ||
| 386 | + ), | ||
| 387 | + Row( | ||
| 388 | + children: [ | ||
| 389 | + Expanded( | ||
| 390 | + child: Slider( | ||
| 391 | + value: _currentZoomLevel, | ||
| 392 | + min: _minAvailableZoom, | ||
| 393 | + max: _maxAvailableZoom, | ||
| 394 | + activeColor: Colors.white, | ||
| 395 | + inactiveColor: Colors.white30, | ||
| 396 | + onChanged: (value) async { | ||
| 397 | + setState(() { | ||
| 398 | + _currentZoomLevel = value; | ||
| 399 | + }); | ||
| 400 | + await controller!.setZoomLevel(value); | ||
| 401 | + }, | ||
| 402 | + ), | ||
| 403 | + ), | ||
| 404 | + Padding( | ||
| 405 | + padding: const EdgeInsets.only(right: 8.0), | ||
| 406 | + child: Container( | ||
| 407 | + decoration: BoxDecoration( | ||
| 408 | + color: Colors.black87, | ||
| 409 | + borderRadius: | ||
| 410 | + BorderRadius.circular(10.0), | ||
| 411 | + ), | ||
| 412 | + child: Padding( | ||
| 413 | + padding: const EdgeInsets.all(8.0), | ||
| 414 | + child: Text( | ||
| 415 | + _currentZoomLevel.toStringAsFixed(1) + | ||
| 416 | + 'x', | ||
| 417 | + style: const TextStyle( | ||
| 418 | + color: Colors.white), | ||
| 419 | + ), | ||
| 420 | + ), | ||
| 421 | + ), | ||
| 422 | + ), | ||
| 423 | + ], | ||
| 424 | + ), | ||
| 425 | + Row( | ||
| 426 | + mainAxisAlignment: | ||
| 427 | + MainAxisAlignment.spaceBetween, | ||
| 428 | + children: [ | ||
| 429 | + InkWell( | ||
| 430 | + onTap: _isRecordingInProgress | ||
| 431 | + ? () async { | ||
| 432 | + if (controller! | ||
| 433 | + .value.isRecordingPaused) { | ||
| 434 | + await resumeVideoRecording(); | ||
| 435 | + } else { | ||
| 436 | + await pauseVideoRecording(); | ||
| 437 | + } | ||
| 438 | + } | ||
| 439 | + : () { | ||
| 440 | + setState(() { | ||
| 441 | + _isCameraInitialized = false; | ||
| 442 | + }); | ||
| 443 | + onNewCameraSelected(cameras![ | ||
| 444 | + _isRearCameraSelected ? 1 : 0]); | ||
| 445 | + setState(() { | ||
| 446 | + _isRearCameraSelected = | ||
| 447 | + !_isRearCameraSelected; | ||
| 448 | + }); | ||
| 449 | + }, | ||
| 450 | + child: Stack( | ||
| 451 | + alignment: Alignment.center, | ||
| 452 | + children: [ | ||
| 453 | + const Icon( | ||
| 454 | + Icons.circle, | ||
| 455 | + color: Colors.black38, | ||
| 456 | + size: 60, | ||
| 457 | + ), | ||
| 458 | + _isRecordingInProgress | ||
| 459 | + ? controller! | ||
| 460 | + .value.isRecordingPaused | ||
| 461 | + ? const Icon( | ||
| 462 | + Icons.play_arrow, | ||
| 463 | + color: Colors.white, | ||
| 464 | + size: 30, | ||
| 465 | + ) | ||
| 466 | + : const Icon( | ||
| 467 | + Icons.pause, | ||
| 468 | + color: Colors.white, | ||
| 469 | + size: 30, | ||
| 470 | + ) | ||
| 471 | + : Icon( | ||
| 472 | + _isRearCameraSelected | ||
| 473 | + ? Icons.camera_front | ||
| 474 | + : Icons.camera_rear, | ||
| 475 | + color: Colors.white, | ||
| 476 | + size: 30, | ||
| 477 | + ), | ||
| 478 | + ], | ||
| 479 | + ), | ||
| 480 | + ), | ||
| 481 | + InkWell( | ||
| 482 | + onTap: _isVideoCameraSelected | ||
| 483 | + ? () async { | ||
| 484 | + if (_isRecordingInProgress) { | ||
| 485 | + XFile? rawVideo = | ||
| 486 | + await stopVideoRecording(); | ||
| 487 | + File videoFile = | ||
| 488 | + File(rawVideo!.path); | ||
| 489 | + | ||
| 490 | + int currentUnix = DateTime.now() | ||
| 491 | + .millisecondsSinceEpoch; | ||
| 492 | + | ||
| 493 | + final directory = | ||
| 494 | + await getApplicationDocumentsDirectory(); | ||
| 495 | + | ||
| 496 | + String fileFormat = videoFile.path | ||
| 497 | + .split('.') | ||
| 498 | + .last; | ||
| 499 | + | ||
| 500 | + _videoFile = await videoFile.copy( | ||
| 501 | + '${directory.path}/$currentUnix.$fileFormat', | ||
| 502 | + ); | ||
| 503 | + | ||
| 504 | + _startVideoPlayer(); | ||
| 505 | + } else { | ||
| 506 | + await startVideoRecording(); | ||
| 507 | + } | ||
| 508 | + } | ||
| 509 | + : () async { | ||
| 510 | + XFile? rawImage = | ||
| 511 | + await takePicture(); | ||
| 512 | + File imageFile = | ||
| 513 | + File(rawImage!.path); | ||
| 514 | + | ||
| 515 | + int currentUnix = DateTime.now() | ||
| 516 | + .millisecondsSinceEpoch; | ||
| 517 | + | ||
| 518 | + final directory = | ||
| 519 | + await getApplicationDocumentsDirectory(); | ||
| 520 | + | ||
| 521 | + String fileFormat = | ||
| 522 | + imageFile.path.split('.').last; | ||
| 523 | + await imageFile.copy( | ||
| 524 | + '${directory.path}/$currentUnix.$fileFormat', | ||
| 525 | + ); | ||
| 526 | + | ||
| 527 | + refreshAlreadyCapturedImages(); | ||
| 528 | + }, | ||
| 529 | + child: Stack( | ||
| 530 | + alignment: Alignment.center, | ||
| 531 | + children: [ | ||
| 532 | + Icon( | ||
| 533 | + Icons.circle, | ||
| 534 | + color: _isVideoCameraSelected | ||
| 535 | + ? Colors.white | ||
| 536 | + : Colors.white38, | ||
| 537 | + size: 80, | ||
| 538 | + ), | ||
| 539 | + Icon( | ||
| 540 | + Icons.circle, | ||
| 541 | + color: _isVideoCameraSelected | ||
| 542 | + ? Colors.red | ||
| 543 | + : Colors.white, | ||
| 544 | + size: 65, | ||
| 545 | + ), | ||
| 546 | + _isVideoCameraSelected && | ||
| 547 | + _isRecordingInProgress | ||
| 548 | + ? const Icon( | ||
| 549 | + Icons.stop_rounded, | ||
| 550 | + color: Colors.white, | ||
| 551 | + size: 32, | ||
| 552 | + ) | ||
| 553 | + : Container(), | ||
| 554 | + ], | ||
| 555 | + ), | ||
| 556 | + ), | ||
| 557 | + InkWell( | ||
| 558 | + onTap: | ||
| 559 | + _imageFile != null || _videoFile != null | ||
| 560 | + ? () { | ||
| 561 | + Navigator.of(context).push( | ||
| 562 | + MaterialPageRoute( | ||
| 563 | + builder: (context) => | ||
| 564 | + PreviewScreen( | ||
| 565 | + imageFile: _imageFile!, | ||
| 566 | + fileList: allFileList, | ||
| 567 | + ), | ||
| 568 | + ), | ||
| 569 | + ); | ||
| 570 | + } | ||
| 571 | + : null, | ||
| 572 | + child: Container( | ||
| 573 | + width: 60, | ||
| 574 | + height: 60, | ||
| 575 | + decoration: BoxDecoration( | ||
| 576 | + color: Colors.black, | ||
| 577 | + borderRadius: | ||
| 578 | + BorderRadius.circular(10.0), | ||
| 579 | + border: Border.all( | ||
| 580 | + color: Colors.white, | ||
| 581 | + width: 2, | ||
| 582 | + ), | ||
| 583 | + image: _imageFile != null | ||
| 584 | + ? DecorationImage( | ||
| 585 | + image: FileImage(_imageFile!), | ||
| 586 | + fit: BoxFit.cover, | ||
| 587 | + ) | ||
| 588 | + : null, | ||
| 589 | + ), | ||
| 590 | + child: videoController != null && | ||
| 591 | + videoController! | ||
| 592 | + .value.isInitialized | ||
| 593 | + ? ClipRRect( | ||
| 594 | + borderRadius: | ||
| 595 | + BorderRadius.circular(8.0), | ||
| 596 | + child: AspectRatio( | ||
| 597 | + aspectRatio: videoController! | ||
| 598 | + .value.aspectRatio, | ||
| 599 | + child: VideoPlayer( | ||
| 600 | + videoController!), | ||
| 601 | + ), | ||
| 602 | + ) | ||
| 603 | + : Container(), | ||
| 604 | + ), | ||
| 605 | + ), | ||
| 606 | + ], | ||
| 607 | + ), | ||
| 608 | + ], | ||
| 609 | + ), | ||
| 610 | + ), | ||
| 611 | + ], | ||
| 612 | + ), | ||
| 613 | + ), | ||
| 614 | + Expanded( | ||
| 615 | + child: SingleChildScrollView( | ||
| 616 | + physics: const BouncingScrollPhysics(), | ||
| 617 | + child: Column( | ||
| 618 | + children: [ | ||
| 619 | + Padding( | ||
| 620 | + padding: const EdgeInsets.only(top: 8.0), | ||
| 621 | + child: Row( | ||
| 622 | + children: [ | ||
| 623 | + Expanded( | ||
| 624 | + child: Padding( | ||
| 625 | + padding: const EdgeInsets.only( | ||
| 626 | + left: 8.0, | ||
| 627 | + right: 4.0, | ||
| 628 | + ), | ||
| 629 | + child: TextButton( | ||
| 630 | + onPressed: _isRecordingInProgress | ||
| 631 | + ? null | ||
| 632 | + : () { | ||
| 633 | + if (_isVideoCameraSelected) { | ||
| 634 | + setState(() { | ||
| 635 | + _isVideoCameraSelected = | ||
| 636 | + false; | ||
| 637 | + }); | ||
| 638 | + } | ||
| 639 | + }, | ||
| 640 | + style: TextButton.styleFrom( | ||
| 641 | + primary: _isVideoCameraSelected | ||
| 642 | + ? Colors.black54 | ||
| 643 | + : Colors.black, | ||
| 644 | + backgroundColor: _isVideoCameraSelected | ||
| 645 | + ? Colors.white30 | ||
| 646 | + : Colors.white, | ||
| 647 | + ), | ||
| 648 | + child: const Text('IMAGE'), | ||
| 649 | + ), | ||
| 650 | + ), | ||
| 651 | + ), | ||
| 652 | + Expanded( | ||
| 653 | + child: Padding( | ||
| 654 | + padding: const EdgeInsets.only( | ||
| 655 | + left: 4.0, right: 8.0), | ||
| 656 | + child: TextButton( | ||
| 657 | + onPressed: () { | ||
| 658 | + if (!_isVideoCameraSelected) { | ||
| 659 | + setState(() { | ||
| 660 | + _isVideoCameraSelected = true; | ||
| 661 | + }); | ||
| 662 | + } | ||
| 663 | + }, | ||
| 664 | + style: TextButton.styleFrom( | ||
| 665 | + primary: _isVideoCameraSelected | ||
| 666 | + ? Colors.black | ||
| 667 | + : Colors.black54, | ||
| 668 | + backgroundColor: _isVideoCameraSelected | ||
| 669 | + ? Colors.white | ||
| 670 | + : Colors.white30, | ||
| 671 | + ), | ||
| 672 | + child: const Text('VIDEO'), | ||
| 673 | + ), | ||
| 674 | + ), | ||
| 675 | + ), | ||
| 676 | + ], | ||
| 677 | + ), | ||
| 678 | + ), | ||
| 679 | + Padding( | ||
| 680 | + padding: | ||
| 681 | + const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 8.0), | ||
| 682 | + child: Row( | ||
| 683 | + mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||
| 684 | + children: [ | ||
| 685 | + InkWell( | ||
| 686 | + onTap: () async { | ||
| 687 | + setState(() { | ||
| 688 | + _currentFlashMode = FlashMode.off; | ||
| 689 | + }); | ||
| 690 | + await controller!.setFlashMode( | ||
| 691 | + FlashMode.off, | ||
| 692 | + ); | ||
| 693 | + }, | ||
| 694 | + child: Icon( | ||
| 695 | + Icons.flash_off, | ||
| 696 | + color: _currentFlashMode == FlashMode.off | ||
| 697 | + ? Colors.amber | ||
| 698 | + : Colors.white, | ||
| 699 | + ), | ||
| 700 | + ), | ||
| 701 | + InkWell( | ||
| 702 | + onTap: () async { | ||
| 703 | + setState(() { | ||
| 704 | + _currentFlashMode = FlashMode.auto; | ||
| 705 | + }); | ||
| 706 | + await controller!.setFlashMode( | ||
| 707 | + FlashMode.auto, | ||
| 708 | + ); | ||
| 709 | + }, | ||
| 710 | + child: Icon( | ||
| 711 | + Icons.flash_auto, | ||
| 712 | + color: _currentFlashMode == FlashMode.auto | ||
| 713 | + ? Colors.amber | ||
| 714 | + : Colors.white, | ||
| 715 | + ), | ||
| 716 | + ), | ||
| 717 | + InkWell( | ||
| 718 | + onTap: () async { | ||
| 719 | + setState(() { | ||
| 720 | + _currentFlashMode = FlashMode.always; | ||
| 721 | + }); | ||
| 722 | + await controller!.setFlashMode( | ||
| 723 | + FlashMode.always, | ||
| 724 | + ); | ||
| 725 | + }, | ||
| 726 | + child: Icon( | ||
| 727 | + Icons.flash_on, | ||
| 728 | + color: _currentFlashMode == FlashMode.always | ||
| 729 | + ? Colors.amber | ||
| 730 | + : Colors.white, | ||
| 731 | + ), | ||
| 732 | + ), | ||
| 733 | + InkWell( | ||
| 734 | + onTap: () async { | ||
| 735 | + setState(() { | ||
| 736 | + _currentFlashMode = FlashMode.torch; | ||
| 737 | + }); | ||
| 738 | + await controller!.setFlashMode( | ||
| 739 | + FlashMode.torch, | ||
| 740 | + ); | ||
| 741 | + }, | ||
| 742 | + child: Icon( | ||
| 743 | + Icons.highlight, | ||
| 744 | + color: _currentFlashMode == FlashMode.torch | ||
| 745 | + ? Colors.amber | ||
| 746 | + : Colors.white, | ||
| 747 | + ), | ||
| 748 | + ), | ||
| 749 | + ], | ||
| 750 | + ), | ||
| 751 | + ) | ||
| 752 | + ], | ||
| 753 | + ), | ||
| 754 | + ), | ||
| 755 | + ), | ||
| 756 | + ], | ||
| 757 | + ) | ||
| 758 | + : const Center( | ||
| 759 | + child: Text( | ||
| 760 | + 'LOADING', | ||
| 761 | + style: TextStyle(color: Colors.white), | ||
| 762 | + ), | ||
| 763 | + ), | ||
| 764 | + ), | ||
| 765 | + ); | ||
| 454 | } | 766 | } |
| 455 | } | 767 | } | ... | ... |
File moved
File moved
lib/recorder/video/captures_screen.dart
0 → 100644
| 1 | +import 'dart:io'; | ||
| 2 | + | ||
| 3 | +import 'package:flutter/material.dart'; | ||
| 4 | + | ||
| 5 | +import 'preview_screen.dart'; | ||
| 6 | + | ||
| 7 | +class CapturesScreen extends StatelessWidget { | ||
| 8 | + final List<File> imageFileList; | ||
| 9 | + | ||
| 10 | + const CapturesScreen({required this.imageFileList}); | ||
| 11 | + | ||
| 12 | + @override | ||
| 13 | + Widget build(BuildContext context) { | ||
| 14 | + return Scaffold( | ||
| 15 | + backgroundColor: Colors.black, | ||
| 16 | + body: SingleChildScrollView( | ||
| 17 | + physics: BouncingScrollPhysics(), | ||
| 18 | + child: Column( | ||
| 19 | + crossAxisAlignment: CrossAxisAlignment.start, | ||
| 20 | + children: [ | ||
| 21 | + Padding( | ||
| 22 | + padding: const EdgeInsets.all(16.0), | ||
| 23 | + child: Text( | ||
| 24 | + 'Captures', | ||
| 25 | + style: TextStyle( | ||
| 26 | + fontSize: 32.0, | ||
| 27 | + color: Colors.white, | ||
| 28 | + ), | ||
| 29 | + ), | ||
| 30 | + ), | ||
| 31 | + GridView.count( | ||
| 32 | + shrinkWrap: true, | ||
| 33 | + physics: NeverScrollableScrollPhysics(), | ||
| 34 | + crossAxisCount: 2, | ||
| 35 | + children: [ | ||
| 36 | + for (File imageFile in imageFileList) | ||
| 37 | + Container( | ||
| 38 | + decoration: BoxDecoration( | ||
| 39 | + border: Border.all( | ||
| 40 | + color: Colors.black, | ||
| 41 | + width: 2, | ||
| 42 | + ), | ||
| 43 | + ), | ||
| 44 | + child: InkWell( | ||
| 45 | + onTap: () { | ||
| 46 | + Navigator.of(context).pushReplacement( | ||
| 47 | + MaterialPageRoute( | ||
| 48 | + builder: (context) => PreviewScreen( | ||
| 49 | + fileList: imageFileList, | ||
| 50 | + imageFile: imageFile, | ||
| 51 | + ), | ||
| 52 | + ), | ||
| 53 | + ); | ||
| 54 | + }, | ||
| 55 | + child: Image.file( | ||
| 56 | + imageFile, | ||
| 57 | + fit: BoxFit.cover, | ||
| 58 | + ), | ||
| 59 | + ), | ||
| 60 | + ), | ||
| 61 | + ], | ||
| 62 | + ), | ||
| 63 | + ], | ||
| 64 | + ), | ||
| 65 | + ), | ||
| 66 | + ); | ||
| 67 | + } | ||
| 68 | +} |
lib/recorder/video/preview_screen.dart
0 → 100644
| 1 | +import 'dart:io'; | ||
| 2 | + | ||
| 3 | +import 'package:flutter/material.dart'; | ||
| 4 | + | ||
| 5 | +import 'captures_screen.dart'; | ||
| 6 | + | ||
| 7 | +class PreviewScreen extends StatelessWidget { | ||
| 8 | + final File imageFile; | ||
| 9 | + final List<File> fileList; | ||
| 10 | + | ||
| 11 | + const PreviewScreen({ | ||
| 12 | + required this.imageFile, | ||
| 13 | + required this.fileList, | ||
| 14 | + }); | ||
| 15 | + | ||
| 16 | + @override | ||
| 17 | + Widget build(BuildContext context) { | ||
| 18 | + return Scaffold( | ||
| 19 | + backgroundColor: Colors.black, | ||
| 20 | + body: Column( | ||
| 21 | + crossAxisAlignment: CrossAxisAlignment.start, | ||
| 22 | + children: [ | ||
| 23 | + Padding( | ||
| 24 | + padding: const EdgeInsets.all(8.0), | ||
| 25 | + child: TextButton( | ||
| 26 | + onPressed: () { | ||
| 27 | + Navigator.of(context).pushReplacement( | ||
| 28 | + MaterialPageRoute( | ||
| 29 | + builder: (context) => CapturesScreen( | ||
| 30 | + imageFileList: fileList, | ||
| 31 | + ), | ||
| 32 | + ), | ||
| 33 | + ); | ||
| 34 | + }, | ||
| 35 | + child: Text('Go to all captures'), | ||
| 36 | + style: TextButton.styleFrom( | ||
| 37 | + primary: Colors.black, | ||
| 38 | + backgroundColor: Colors.white, | ||
| 39 | + ), | ||
| 40 | + ), | ||
| 41 | + ), | ||
| 42 | + Expanded( | ||
| 43 | + child: Image.file(imageFile), | ||
| 44 | + ), | ||
| 45 | + ], | ||
| 46 | + ), | ||
| 47 | + ); | ||
| 48 | + } | ||
| 49 | +} |
| ... | @@ -31,7 +31,7 @@ class _AccountManagerPageState extends State<AccountManagerPage> { | ... | @@ -31,7 +31,7 @@ class _AccountManagerPageState extends State<AccountManagerPage> { |
| 31 | children: <Widget>[ | 31 | children: <Widget>[ |
| 32 | Stack( | 32 | Stack( |
| 33 | children: <Widget>[ | 33 | children: <Widget>[ |
| 34 | - ClickItem(title: '店铺logo', onTap: () {}), | 34 | + ClickItem(title: '头像', onTap: () {}), |
| 35 | Positioned( | 35 | Positioned( |
| 36 | top: 8.px, | 36 | top: 8.px, |
| 37 | bottom: 8.px, | 37 | bottom: 8.px, | ... | ... |
| ... | @@ -120,6 +120,27 @@ packages: | ... | @@ -120,6 +120,27 @@ packages: |
| 120 | url: "https://pub.dartlang.org" | 120 | url: "https://pub.dartlang.org" |
| 121 | source: hosted | 121 | source: hosted |
| 122 | version: "1.0.1" | 122 | version: "1.0.1" |
| 123 | + camera: | ||
| 124 | + dependency: "direct main" | ||
| 125 | + description: | ||
| 126 | + name: camera | ||
| 127 | + url: "https://pub.dartlang.org" | ||
| 128 | + source: hosted | ||
| 129 | + version: "0.9.4+5" | ||
| 130 | + camera_platform_interface: | ||
| 131 | + dependency: transitive | ||
| 132 | + description: | ||
| 133 | + name: camera_platform_interface | ||
| 134 | + url: "https://pub.dartlang.org" | ||
| 135 | + source: hosted | ||
| 136 | + version: "2.1.4" | ||
| 137 | + camera_web: | ||
| 138 | + dependency: transitive | ||
| 139 | + description: | ||
| 140 | + name: camera_web | ||
| 141 | + url: "https://pub.dartlang.org" | ||
| 142 | + source: hosted | ||
| 143 | + version: "0.2.1+1" | ||
| 123 | characters: | 144 | characters: |
| 124 | dependency: transitive | 145 | dependency: transitive |
| 125 | description: | 146 | description: |
| ... | @@ -632,7 +653,7 @@ packages: | ... | @@ -632,7 +653,7 @@ packages: |
| 632 | source: hosted | 653 | source: hosted |
| 633 | version: "1.8.0" | 654 | version: "1.8.0" |
| 634 | path_provider: | 655 | path_provider: |
| 635 | - dependency: transitive | 656 | + dependency: "direct main" |
| 636 | description: | 657 | description: |
| 637 | name: path_provider | 658 | name: path_provider |
| 638 | url: "https://pub.dartlang.org" | 659 | url: "https://pub.dartlang.org" |
| ... | @@ -764,6 +785,13 @@ packages: | ... | @@ -764,6 +785,13 @@ packages: |
| 764 | url: "https://pub.dartlang.org" | 785 | url: "https://pub.dartlang.org" |
| 765 | source: hosted | 786 | source: hosted |
| 766 | version: "1.0.1" | 787 | version: "1.0.1" |
| 788 | + quiver: | ||
| 789 | + dependency: transitive | ||
| 790 | + description: | ||
| 791 | + name: quiver | ||
| 792 | + url: "https://pub.dartlang.org" | ||
| 793 | + source: hosted | ||
| 794 | + version: "3.0.1+1" | ||
| 767 | rational: | 795 | rational: |
| 768 | dependency: transitive | 796 | dependency: transitive |
| 769 | description: | 797 | description: | ... | ... |
| ... | @@ -89,12 +89,16 @@ dependencies: | ... | @@ -89,12 +89,16 @@ dependencies: |
| 89 | tapped: ^2.0.0-nullsafety.0 | 89 | tapped: ^2.0.0-nullsafety.0 |
| 90 | # 加载动画库 | 90 | # 加载动画库 |
| 91 | flutter_spinkit: ^5.0.0 | 91 | flutter_spinkit: ^5.0.0 |
| 92 | - # fijkplayer (Video player plugin for Flutter) Flutter 媒体播放器 | ||
| 93 | - fijkplayer: ^0.10.1 | ||
| 94 | 92 | ||
| 95 | json_annotation: ^4.4.0 | 93 | json_annotation: ^4.4.0 |
| 96 | flutter_plugin_record: ^1.0.1 | 94 | flutter_plugin_record: ^1.0.1 |
| 97 | 95 | ||
| 96 | + # fijkplayer (Video player plugin for Flutter) Flutter 媒体播放器 | ||
| 97 | + fijkplayer: ^0.10.1 | ||
| 98 | + | ||
| 99 | + camera: ^0.9.4+5 | ||
| 100 | + path_provider: ^2.0.8 | ||
| 101 | + | ||
| 98 | dependency_overrides: | 102 | dependency_overrides: |
| 99 | decimal: 1.5.0 | 103 | decimal: 1.5.0 |
| 100 | 104 | ||
| ... | @@ -169,9 +173,7 @@ flutter: | ... | @@ -169,9 +173,7 @@ flutter: |
| 169 | assets: | 173 | assets: |
| 170 | - assets/data/ | 174 | - assets/data/ |
| 171 | - assets/images/ | 175 | - assets/images/ |
| 172 | - - assets/images/home/ | ||
| 173 | - assets/images/login/ | 176 | - assets/images/login/ |
| 174 | - - assets/images/account/ | ||
| 175 | - assets/images/state/ | 177 | - assets/images/state/ |
| 176 | - assets/images/poem/ | 178 | - assets/images/poem/ |
| 177 | - assets/images/category/ | 179 | - assets/images/category/ | ... | ... |
-
Please register or login to post a comment