Showing
100 changed files
with
826 additions
and
380 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; | ||
22 | + | ||
23 | + File? _imageFile; | ||
24 | + File? _videoFile; | ||
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; | ||
35 | + | ||
36 | + // Current values | ||
37 | + double _currentZoomLevel = 1.0; | ||
38 | + double _currentExposureOffset = 0.0; | ||
39 | + FlashMode? _currentFlashMode; | ||
40 | + | ||
41 | + List<File> allFileList = []; | ||
42 | + | ||
43 | + final resolutionPresets = ResolutionPreset.values; | ||
44 | + | ||
45 | + ResolutionPreset currentResolutionPreset = ResolutionPreset.high; | ||
27 | 46 | ||
28 | - set _imageFile(XFile? value) { | 47 | + List<CameraDescription>? cameras = []; |
29 | - _imageFileList = value == null ? null : [value]; | 48 | + @override |
49 | + void initState() { | ||
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(); | ||
57 | + } else { | ||
58 | + Toast.show("摄像头出错啦~"); | ||
59 | + } | ||
60 | + }); | ||
61 | + super.initState(); | ||
30 | } | 62 | } |
31 | 63 | ||
32 | - dynamic _pickImageError; | 64 | + Future<List<CameraDescription>?> initCameras() async { |
33 | - bool isVideo = false; | 65 | + try { |
66 | + WidgetsFlutterBinding.ensureInitialized(); | ||
67 | + List<CameraDescription> cameras = await availableCameras(); | ||
68 | + return cameras; | ||
69 | + } on CameraException catch (e) { | ||
70 | + return null; | ||
71 | + } | ||
72 | + } | ||
34 | 73 | ||
35 | - VideoPlayerController? _controller; | 74 | + refreshAlreadyCapturedImages() async { |
36 | - VideoPlayerController? _toBeDisposed; | 75 | + final directory = await getApplicationDocumentsDirectory(); |
37 | - String? _retrieveDataError; | 76 | + List<FileSystemEntity> fileList = await directory.list().toList(); |
77 | + allFileList.clear(); | ||
78 | + List<Map<int, dynamic>> fileNames = []; | ||
38 | 79 | ||
39 | - final ImagePicker _picker = ImagePicker(); | 80 | + for (var file in fileList) { |
40 | - final TextEditingController maxWidthController = TextEditingController(); | 81 | + if (file.path.contains('.jpg') || file.path.contains('.mp4')) { |
41 | - final TextEditingController maxHeightController = TextEditingController(); | 82 | + allFileList.add(File(file.path)); |
42 | - final TextEditingController qualityController = TextEditingController(); | 83 | + |
84 | + String name = file.path.split('/').last.split('.').first; | ||
85 | + fileNames.add({0: int.parse(name), 1: file.path.split('/').last}); | ||
86 | + } | ||
87 | + } | ||
43 | 88 | ||
44 | - Future<void> _playVideo(XFile? file) async { | 89 | + if (fileNames.isNotEmpty) { |
45 | - if (file != null && mounted) { | 90 | + final recentFile = |
46 | - await _disposeVideoController(); | 91 | + fileNames.reduce((curr, next) => curr[0] > next[0] ? curr : next); |
47 | - late VideoPlayerController controller; | 92 | + String recentFileName = recentFile[1]; |
48 | - if (kIsWeb) { | 93 | + if (recentFileName.contains('.mp4')) { |
49 | - controller = VideoPlayerController.network(file.path); | 94 | + _videoFile = File('${directory.path}/$recentFileName'); |
95 | + _imageFile = null; | ||
96 | + _startVideoPlayer(); | ||
50 | } else { | 97 | } else { |
51 | - controller = VideoPlayerController.file(File(file.path)); | 98 | + _imageFile = File('${directory.path}/$recentFileName'); |
52 | - } | 99 | + _videoFile = null; |
53 | - _controller = controller; | 100 | + } |
54 | - // In web, most browsers won't honor a programmatic call to .play | 101 | + |
55 | - // if the video has a sound track (and is not muted). | ||
56 | - // Mute the video so it auto-plays in web! | ||
57 | - // This is not needed if the call to .play is the result of user | ||
58 | - // interaction (clicking on a "play" button, for example). | ||
59 | - final double volume = kIsWeb ? 0.0 : 1.0; | ||
60 | - await controller.setVolume(volume); | ||
61 | - await controller.initialize(); | ||
62 | - await controller.setLooping(true); | ||
63 | - await controller.play(); | ||
64 | setState(() {}); | 102 | setState(() {}); |
65 | } | 103 | } |
66 | } | 104 | } |
67 | 105 | ||
68 | - void _onImageButtonPressed(ImageSource source, | 106 | + Future<XFile?> takePicture() async { |
69 | - {BuildContext? context, bool isMultiImage = false}) async { | 107 | + final CameraController? cameraController = controller; |
70 | - if (_controller != null) { | 108 | + |
71 | - await _controller!.setVolume(0.0); | 109 | + if (cameraController!.value.isTakingPicture) { |
110 | + // A capture is already pending, do nothing. | ||
111 | + return null; | ||
72 | } | 112 | } |
73 | - if (isVideo) { | 113 | + |
74 | - final XFile? file = await _picker.pickVideo( | ||
75 | - source: source, maxDuration: const Duration(seconds: 10)); | ||
76 | - await _playVideo(file); | ||
77 | - } else if (isMultiImage) { | ||
78 | - await _displayPickImageDialog(context!, | ||
79 | - (double? maxWidth, double? maxHeight, int? quality) async { | ||
80 | try { | 114 | try { |
81 | - final pickedFileList = await _picker.pickMultiImage( | 115 | + XFile file = await cameraController.takePicture(); |
82 | - maxWidth: maxWidth, | 116 | + return file; |
83 | - maxHeight: maxHeight, | 117 | + } on CameraException catch (e) { |
84 | - imageQuality: quality, | 118 | + return null; |
85 | - ); | ||
86 | - setState(() { | ||
87 | - _imageFileList = pickedFileList; | ||
88 | - }); | ||
89 | - } catch (e) { | ||
90 | - setState(() { | ||
91 | - _pickImageError = e; | ||
92 | - }); | ||
93 | } | 119 | } |
120 | + } | ||
121 | + | ||
122 | + Future<void> _startVideoPlayer() async { | ||
123 | + if (_videoFile != null) { | ||
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(() {}); | ||
94 | }); | 129 | }); |
95 | - } else { | 130 | + await videoController!.setLooping(true); |
96 | - await _displayPickImageDialog(context!, | 131 | + await videoController!.play(); |
97 | - (double? maxWidth, double? maxHeight, int? quality) async { | 132 | + } |
133 | + } | ||
134 | + | ||
135 | + Future<void> startVideoRecording() async { | ||
136 | + final CameraController? cameraController = controller; | ||
137 | + | ||
138 | + if (controller!.value.isRecordingVideo) { | ||
139 | + // A recording has already started, do nothing. | ||
140 | + return; | ||
141 | + } | ||
142 | + | ||
98 | try { | 143 | try { |
99 | - final pickedFile = await _picker.pickImage( | 144 | + await cameraController!.startVideoRecording(); |
100 | - source: source, | ||
101 | - maxWidth: maxWidth, | ||
102 | - maxHeight: maxHeight, | ||
103 | - imageQuality: quality, | ||
104 | - ); | ||
105 | - setState(() { | ||
106 | - _imageFile = pickedFile; | ||
107 | - }); | ||
108 | - } catch (e) { | ||
109 | setState(() { | 145 | setState(() { |
110 | - _pickImageError = e; | 146 | + _isRecordingInProgress = true; |
111 | }); | 147 | }); |
148 | + } on CameraException catch (e) { | ||
149 | + // print('Error starting to record video: $e'); | ||
112 | } | 150 | } |
113 | - }); | ||
114 | } | 151 | } |
152 | + | ||
153 | + Future<XFile?> stopVideoRecording() async { | ||
154 | + if (!controller!.value.isRecordingVideo) { | ||
155 | + // Recording is already is stopped state | ||
156 | + return null; | ||
115 | } | 157 | } |
116 | 158 | ||
117 | - @override | 159 | + try { |
118 | - void deactivate() { | 160 | + XFile file = await controller!.stopVideoRecording(); |
119 | - if (_controller != null) { | 161 | + setState(() { |
120 | - _controller!.setVolume(0.0); | 162 | + _isRecordingInProgress = false; |
121 | - _controller!.pause(); | 163 | + }); |
164 | + return file; | ||
165 | + } on CameraException catch (e) { | ||
166 | + return null; | ||
122 | } | 167 | } |
123 | - super.deactivate(); | ||
124 | } | 168 | } |
125 | 169 | ||
126 | - @override | 170 | + Future<void> pauseVideoRecording() async { |
127 | - void dispose() { | 171 | + if (!controller!.value.isRecordingVideo) { |
128 | - _disposeVideoController(); | 172 | + // Video recording is not in progress |
129 | - maxWidthController.dispose(); | 173 | + return; |
130 | - maxHeightController.dispose(); | ||
131 | - qualityController.dispose(); | ||
132 | - super.dispose(); | ||
133 | } | 174 | } |
134 | 175 | ||
135 | - Future<void> _disposeVideoController() async { | 176 | + try { |
136 | - if (_toBeDisposed != null) { | 177 | + await controller!.pauseVideoRecording(); |
137 | - await _toBeDisposed!.dispose(); | 178 | + } on CameraException catch (e) { |
179 | + // print('Error pausing video recording: $e'); | ||
138 | } | 180 | } |
139 | - _toBeDisposed = _controller; | ||
140 | - _controller = null; | ||
141 | } | 181 | } |
142 | 182 | ||
143 | - Widget _previewVideo() { | 183 | + Future<void> resumeVideoRecording() async { |
144 | - final Text? retrieveError = _getRetrieveErrorWidget(); | 184 | + if (!controller!.value.isRecordingVideo) { |
145 | - if (retrieveError != null) { | 185 | + // No video recording was in progress |
146 | - return retrieveError; | 186 | + return; |
147 | } | 187 | } |
148 | - if (_controller == null) { | 188 | + |
149 | - return const Text( | 189 | + try { |
150 | - 'You have not yet picked a video', | 190 | + await controller!.resumeVideoRecording(); |
151 | - textAlign: TextAlign.center, | 191 | + } on CameraException catch (e) { |
152 | - ); | 192 | + // print('Error resuming video recording: $e'); |
153 | } | 193 | } |
154 | - return Padding( | ||
155 | - padding: const EdgeInsets.all(10.0), | ||
156 | - child: AspectRatioVideo(_controller), | ||
157 | - ); | ||
158 | } | 194 | } |
159 | 195 | ||
160 | - Widget _previewImages() { | 196 | + void resetCameraValues() async { |
161 | - final Text? retrieveError = _getRetrieveErrorWidget(); | 197 | + _currentZoomLevel = 1.0; |
162 | - if (retrieveError != null) { | 198 | + _currentExposureOffset = 0.0; |
163 | - return retrieveError; | ||
164 | - } | ||
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 | } | 199 | } |
200 | + | ||
201 | + void onNewCameraSelected(CameraDescription cameraDescription) async { | ||
202 | + final previousCameraController = controller; | ||
203 | + | ||
204 | + final CameraController cameraController = CameraController( | ||
205 | + cameraDescription, | ||
206 | + currentResolutionPreset, | ||
207 | + imageFormatGroup: ImageFormatGroup.jpeg, | ||
208 | + ); | ||
209 | + | ||
210 | + await previousCameraController?.dispose(); | ||
211 | + | ||
212 | + resetCameraValues(); | ||
213 | + | ||
214 | + if (mounted) { | ||
215 | + setState(() { | ||
216 | + controller = cameraController; | ||
217 | + }); | ||
193 | } | 218 | } |
194 | 219 | ||
195 | - Widget _handlePreview() { | 220 | + // Update UI if controller updated |
196 | - if (isVideo) { | 221 | + cameraController.addListener(() { |
197 | - return _previewVideo(); | 222 | + if (mounted) setState(() {}); |
198 | - } else { | 223 | + }); |
199 | - return _previewImages(); | 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'); | ||
245 | + } | ||
246 | + | ||
247 | + if (mounted) { | ||
248 | + setState(() { | ||
249 | + _isCameraInitialized = controller!.value.isInitialized; | ||
250 | + }); | ||
200 | } | 251 | } |
201 | } | 252 | } |
202 | 253 | ||
203 | - Future<void> retrieveLostData() async { | 254 | + @override |
204 | - final LostDataResponse response = await _picker.retrieveLostData(); | 255 | + void didChangeAppLifecycleState(AppLifecycleState state) { |
205 | - if (response.isEmpty) { | 256 | + final CameraController? cameraController = controller; |
257 | + | ||
258 | + // App state changed before we got the chance to initialize. | ||
259 | + if (cameraController == null || !cameraController.value.isInitialized) { | ||
206 | return; | 260 | return; |
207 | } | 261 | } |
208 | - if (response.file != null) { | 262 | + |
209 | - if (response.type == RetrieveType.video) { | 263 | + if (state == AppLifecycleState.inactive) { |
210 | - isVideo = true; | 264 | + cameraController.dispose(); |
211 | - await _playVideo(response.file); | 265 | + } else if (state == AppLifecycleState.resumed) { |
212 | - } else { | 266 | + onNewCameraSelected(cameraController.description); |
213 | - isVideo = false; | ||
214 | - setState(() { | ||
215 | - _imageFile = response.file; | ||
216 | - _imageFileList = response.files; | ||
217 | - }); | ||
218 | } | 267 | } |
219 | - } else { | ||
220 | - _retrieveDataError = response.exception!.code; | ||
221 | } | 268 | } |
269 | + | ||
270 | + @override | ||
271 | + void dispose() { | ||
272 | + controller?.dispose(); | ||
273 | + videoController?.dispose(); | ||
274 | + super.dispose(); | ||
222 | } | 275 | } |
223 | 276 | ||
224 | @override | 277 | @override |
225 | Widget build(BuildContext context) { | 278 | Widget build(BuildContext context) { |
226 | - return Scaffold( | 279 | + return SafeArea( |
227 | - appBar: const MyAppBar( | 280 | + child: Scaffold( |
228 | - isBack: true, | 281 | + backgroundColor: Colors.black, |
229 | - isTransparent: true, | 282 | + body: _isCameraInitialized |
230 | - ), | 283 | + ? Column( |
231 | - body: Center( | 284 | + children: [ |
232 | - child: !kIsWeb && defaultTargetPlatform == TargetPlatform.android | 285 | + AspectRatio( |
233 | - ? FutureBuilder<void>( | 286 | + aspectRatio: 1 / controller!.value.aspectRatio, |
234 | - future: retrieveLostData(), | 287 | + child: Stack( |
235 | - builder: (BuildContext context, AsyncSnapshot<void> snapshot) { | 288 | + children: [ |
236 | - switch (snapshot.connectionState) { | 289 | + controller!.buildPreview(), |
237 | - case ConnectionState.none: | 290 | + Padding( |
238 | - case ConnectionState.waiting: | 291 | + padding: const EdgeInsets.fromLTRB( |
239 | - return const Text( | 292 | + 16.0, |
240 | - 'You have not yet picked an image.', | 293 | + 8.0, |
241 | - textAlign: TextAlign.center, | 294 | + 16.0, |
242 | - ); | 295 | + 8.0, |
243 | - case ConnectionState.done: | 296 | + ), |
244 | - return _handlePreview(); | 297 | + child: Column( |
245 | - default: | 298 | + crossAxisAlignment: CrossAxisAlignment.end, |
246 | - if (snapshot.hasError) { | 299 | + children: [ |
247 | - return Text( | 300 | + Align( |
248 | - 'Pick image/video error: ${snapshot.error}}', | 301 | + alignment: Alignment.topRight, |
249 | - textAlign: TextAlign.center, | 302 | + child: Container( |
250 | - ); | 303 | + decoration: BoxDecoration( |
251 | - } else { | 304 | + color: Colors.black87, |
252 | - return const Text( | 305 | + borderRadius: BorderRadius.circular(10.0), |
253 | - 'You have not yet picked an image.', | 306 | + ), |
254 | - textAlign: TextAlign.center, | 307 | + child: Padding( |
255 | - ); | 308 | + padding: const EdgeInsets.only( |
256 | - } | 309 | + left: 8.0, |
257 | - } | 310 | + right: 8.0, |
258 | - }, | 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, | ||
259 | ) | 329 | ) |
260 | - : _handlePreview(), | 330 | + ], |
261 | - ), | 331 | + onChanged: (value) { |
262 | - floatingActionButton: Column( | 332 | + setState(() { |
263 | - mainAxisAlignment: MainAxisAlignment.end, | 333 | + currentResolutionPreset = value!; |
264 | - children: <Widget>[ | 334 | + _isCameraInitialized = false; |
265 | - Semantics( | 335 | + }); |
266 | - label: 'image_picker_example_from_gallery', | 336 | + onNewCameraSelected( |
267 | - child: FloatingActionButton( | 337 | + controller!.description); |
268 | - onPressed: () { | ||
269 | - isVideo = false; | ||
270 | - _onImageButtonPressed(ImageSource.gallery, context: context); | ||
271 | }, | 338 | }, |
272 | - heroTag: 'image0', | 339 | + hint: const Text("Select item"), |
273 | - tooltip: 'Pick Image from gallery', | ||
274 | - child: const Icon(Icons.photo), | ||
275 | ), | 340 | ), |
276 | ), | 341 | ), |
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 | ), | 342 | ), |
292 | ), | 343 | ), |
344 | + // Spacer(), | ||
293 | Padding( | 345 | Padding( |
294 | - padding: const EdgeInsets.only(top: 16.0), | 346 | + padding: const EdgeInsets.only( |
295 | - child: FloatingActionButton( | 347 | + right: 8.0, top: 16.0), |
296 | - onPressed: () { | 348 | + child: Container( |
297 | - isVideo = false; | 349 | + decoration: BoxDecoration( |
298 | - _onImageButtonPressed(ImageSource.camera, context: context); | 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); | ||
299 | }, | 382 | }, |
300 | - heroTag: 'image2', | ||
301 | - tooltip: 'Take a Photo', | ||
302 | - child: const Icon(Icons.camera_alt), | ||
303 | ), | 383 | ), |
304 | ), | 384 | ), |
305 | - Padding( | 385 | + ), |
306 | - padding: const EdgeInsets.only(top: 16.0), | 386 | + ), |
307 | - child: FloatingActionButton( | 387 | + Row( |
308 | - backgroundColor: Colors.red, | 388 | + children: [ |
309 | - onPressed: () { | 389 | + Expanded( |
310 | - isVideo = true; | 390 | + child: Slider( |
311 | - _onImageButtonPressed(ImageSource.gallery); | 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); | ||
312 | }, | 401 | }, |
313 | - heroTag: 'video0', | ||
314 | - tooltip: 'Pick Video from gallery', | ||
315 | - child: const Icon(Icons.video_library), | ||
316 | ), | 402 | ), |
317 | ), | 403 | ), |
318 | Padding( | 404 | Padding( |
319 | - padding: const EdgeInsets.only(top: 16.0), | 405 | + padding: const EdgeInsets.only(right: 8.0), |
320 | - child: FloatingActionButton( | 406 | + child: Container( |
321 | - backgroundColor: Colors.red, | 407 | + decoration: BoxDecoration( |
322 | - onPressed: () { | 408 | + color: Colors.black87, |
323 | - isVideo = true; | 409 | + borderRadius: |
324 | - _onImageButtonPressed(ImageSource.camera); | 410 | + BorderRadius.circular(10.0), |
325 | - }, | 411 | + ), |
326 | - heroTag: 'video1', | 412 | + child: Padding( |
327 | - tooltip: 'Take a Video', | 413 | + padding: const EdgeInsets.all(8.0), |
328 | - child: const Icon(Icons.videocam), | 414 | + child: Text( |
415 | + _currentZoomLevel.toStringAsFixed(1) + | ||
416 | + 'x', | ||
417 | + style: const TextStyle( | ||
418 | + color: Colors.white), | ||
419 | + ), | ||
420 | + ), | ||
329 | ), | 421 | ), |
330 | ), | 422 | ), |
331 | ], | 423 | ], |
332 | ), | 424 | ), |
333 | - ); | 425 | + Row( |
334 | - } | 426 | + mainAxisAlignment: |
335 | - | 427 | + MainAxisAlignment.spaceBetween, |
336 | - Text? _getRetrieveErrorWidget() { | 428 | + children: [ |
337 | - if (_retrieveDataError != null) { | 429 | + InkWell( |
338 | - final Text result = Text(_retrieveDataError!); | 430 | + onTap: _isRecordingInProgress |
339 | - _retrieveDataError = null; | 431 | + ? () async { |
340 | - return result; | 432 | + if (controller! |
433 | + .value.isRecordingPaused) { | ||
434 | + await resumeVideoRecording(); | ||
435 | + } else { | ||
436 | + await pauseVideoRecording(); | ||
341 | } | 437 | } |
342 | - return null; | ||
343 | } | 438 | } |
344 | - | 439 | + : () { |
345 | - Future<void> _displayPickImageDialog( | 440 | + setState(() { |
346 | - BuildContext context, OnPickImageCallback onPick) async { | 441 | + _isCameraInitialized = false; |
347 | - return showDialog( | 442 | + }); |
348 | - context: context, | 443 | + onNewCameraSelected(cameras![ |
349 | - builder: (context) { | 444 | + _isRearCameraSelected ? 1 : 0]); |
350 | - return AlertDialog( | 445 | + setState(() { |
351 | - title: const Text('Add optional parameters'), | 446 | + _isRearCameraSelected = |
352 | - content: Column( | 447 | + !_isRearCameraSelected; |
353 | - children: <Widget>[ | 448 | + }); |
354 | - TextField( | 449 | + }, |
355 | - controller: maxWidthController, | 450 | + child: Stack( |
356 | - keyboardType: | 451 | + alignment: Alignment.center, |
357 | - const TextInputType.numberWithOptions(decimal: true), | 452 | + children: [ |
358 | - decoration: const InputDecoration( | 453 | + const Icon( |
359 | - hintText: "Enter maxWidth if desired"), | 454 | + Icons.circle, |
360 | - ), | 455 | + color: Colors.black38, |
361 | - TextField( | 456 | + size: 60, |
362 | - controller: maxHeightController, | 457 | + ), |
363 | - keyboardType: | 458 | + _isRecordingInProgress |
364 | - const TextInputType.numberWithOptions(decimal: true), | 459 | + ? controller! |
365 | - decoration: const InputDecoration( | 460 | + .value.isRecordingPaused |
366 | - hintText: "Enter maxHeight if desired"), | 461 | + ? const Icon( |
367 | - ), | 462 | + Icons.play_arrow, |
368 | - TextField( | 463 | + color: Colors.white, |
369 | - controller: qualityController, | 464 | + size: 30, |
370 | - keyboardType: TextInputType.number, | 465 | + ) |
371 | - decoration: const InputDecoration( | 466 | + : const Icon( |
372 | - hintText: "Enter quality if desired"), | 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, | ||
373 | ), | 477 | ), |
374 | ], | 478 | ], |
375 | ), | 479 | ), |
376 | - actions: <Widget>[ | ||
377 | - TextButton( | ||
378 | - child: const Text('CANCEL'), | ||
379 | - onPressed: () { | ||
380 | - Navigator.of(context).pop(); | ||
381 | - }, | ||
382 | ), | 480 | ), |
383 | - TextButton( | 481 | + InkWell( |
384 | - child: const Text('PICK'), | 482 | + onTap: _isVideoCameraSelected |
385 | - onPressed: () { | 483 | + ? () async { |
386 | - double? width = maxWidthController.text.isNotEmpty | 484 | + if (_isRecordingInProgress) { |
387 | - ? double.parse(maxWidthController.text) | 485 | + XFile? rawVideo = |
388 | - : null; | 486 | + await stopVideoRecording(); |
389 | - double? height = maxHeightController.text.isNotEmpty | 487 | + File videoFile = |
390 | - ? double.parse(maxHeightController.text) | 488 | + File(rawVideo!.path); |
391 | - : null; | 489 | + |
392 | - int? quality = qualityController.text.isNotEmpty | 490 | + int currentUnix = DateTime.now() |
393 | - ? int.parse(qualityController.text) | 491 | + .millisecondsSinceEpoch; |
394 | - : null; | 492 | + |
395 | - onPick(width, height, quality); | 493 | + final directory = |
396 | - Navigator.of(context).pop(); | 494 | + await getApplicationDocumentsDirectory(); |
397 | - }), | 495 | + |
398 | - ], | 496 | + String fileFormat = videoFile.path |
497 | + .split('.') | ||
498 | + .last; | ||
499 | + | ||
500 | + _videoFile = await videoFile.copy( | ||
501 | + '${directory.path}/$currentUnix.$fileFormat', | ||
399 | ); | 502 | ); |
400 | - }); | ||
401 | - } | ||
402 | -} | ||
403 | - | ||
404 | -typedef OnPickImageCallback = void Function( | ||
405 | - double? maxWidth, double? maxHeight, int? quality); | ||
406 | 503 | ||
407 | -class AspectRatioVideo extends StatefulWidget { | 504 | + _startVideoPlayer(); |
408 | - const AspectRatioVideo(this.controller, {Key? key}) : super(key: key); | 505 | + } else { |
506 | + await startVideoRecording(); | ||
507 | + } | ||
508 | + } | ||
509 | + : () async { | ||
510 | + XFile? rawImage = | ||
511 | + await takePicture(); | ||
512 | + File imageFile = | ||
513 | + File(rawImage!.path); | ||
409 | 514 | ||
410 | - final VideoPlayerController? controller; | 515 | + int currentUnix = DateTime.now() |
516 | + .millisecondsSinceEpoch; | ||
411 | 517 | ||
412 | - @override | 518 | + final directory = |
413 | - AspectRatioVideoState createState() => AspectRatioVideoState(); | 519 | + await getApplicationDocumentsDirectory(); |
414 | -} | ||
415 | 520 | ||
416 | -class AspectRatioVideoState extends State<AspectRatioVideo> { | 521 | + String fileFormat = |
417 | - VideoPlayerController? get controller => widget.controller; | 522 | + imageFile.path.split('.').last; |
418 | - bool initialized = false; | 523 | + await imageFile.copy( |
524 | + '${directory.path}/$currentUnix.$fileFormat', | ||
525 | + ); | ||
419 | 526 | ||
420 | - void _onVideoControllerUpdate() { | 527 | + refreshAlreadyCapturedImages(); |
421 | - if (!mounted) { | 528 | + }, |
422 | - return; | 529 | + child: Stack( |
423 | - } | 530 | + alignment: Alignment.center, |
424 | - if (initialized != controller!.value.isInitialized) { | 531 | + children: [ |
425 | - initialized = controller!.value.isInitialized; | 532 | + Icon( |
426 | - setState(() {}); | 533 | + Icons.circle, |
427 | - } | 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 | + ); | ||
428 | } | 570 | } |
429 | - | 571 | + : null, |
430 | - @override | 572 | + child: Container( |
431 | - void initState() { | 573 | + width: 60, |
432 | - super.initState(); | 574 | + height: 60, |
433 | - controller!.addListener(_onVideoControllerUpdate); | 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 | + }); | ||
434 | } | 638 | } |
435 | - | 639 | + }, |
436 | - @override | 640 | + style: TextButton.styleFrom( |
437 | - void dispose() { | 641 | + primary: _isVideoCameraSelected |
438 | - controller!.removeListener(_onVideoControllerUpdate); | 642 | + ? Colors.black54 |
439 | - super.dispose(); | 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 | + }); | ||
440 | } | 662 | } |
441 | - | 663 | + }, |
442 | - @override | 664 | + style: TextButton.styleFrom( |
443 | - Widget build(BuildContext context) { | 665 | + primary: _isVideoCameraSelected |
444 | - if (initialized) { | 666 | + ? Colors.black |
445 | - return Center( | 667 | + : Colors.black54, |
446 | - child: AspectRatio( | 668 | + backgroundColor: _isVideoCameraSelected |
447 | - aspectRatio: controller!.value.aspectRatio, | 669 | + ? Colors.white |
448 | - child: VideoPlayer(controller!), | 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 | + ), | ||
449 | ), | 764 | ), |
450 | ); | 765 | ); |
451 | - } else { | ||
452 | - return Container(); | ||
453 | - } | ||
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