Reason Pun

clear and repair

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"/>
......

72.3 KB | W: | H:

22.4 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

72.3 KB | W: | H:

22.4 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

2.63 KB | W: | H:

2.63 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.67 KB | W: | H:

1.67 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

3.59 KB | W: | H:

3.59 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

5.53 KB | W: | H:

5.5 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

7.26 KB | W: | H:

7.21 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

122 KB | W: | H:

41.2 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

190 KB | W: | H:

64.6 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

202 KB | W: | H:

71.4 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

13 KB | W: | H:

4.19 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

888 KB | W: | H:

231 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

72.3 KB | W: | H:

22.4 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

611 Bytes | W: | H:

610 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin

938 Bytes | W: | H:

939 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
...@@ -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 }
......
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 +}
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/
......