Reason Pun

add video record page

...@@ -150,7 +150,10 @@ class _PoemDetailPageState extends State<PoemDetailPage> { ...@@ -150,7 +150,10 @@ class _PoemDetailPageState extends State<PoemDetailPage> {
150 size: 36.px, 150 size: 36.px,
151 ), 151 ),
152 onPressed: () { 152 onPressed: () {
153 - Toast.show("不要着急吖,正在开发ing...."); 153 + NavigatorUtils.push(
154 + context,
155 + '${PoemRouter.poemRecordVideoPage}?data=100',
156 + );
154 }, 157 },
155 ) 158 )
156 ], 159 ],
......
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';
9 +
10 +import 'package:flutter/foundation.dart';
11 +import 'package:flutter/material.dart';
12 +import 'package:image_picker/image_picker.dart';
13 +import 'package:one_poem/widgets/my_app_bar.dart';
14 +import 'package:video_player/video_player.dart';
15 +
16 +class PoemRecordVideoPage extends StatefulWidget {
17 + const PoemRecordVideoPage({Key? key, this.title}) : super(key: key);
18 +
19 + final String? title;
20 +
21 + @override
22 + _PoemRecordVideoPageState createState() => _PoemRecordVideoPageState();
23 +}
24 +
25 +class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> {
26 + List<XFile>? _imageFileList;
27 +
28 + set _imageFile(XFile? value) {
29 + _imageFileList = value == null ? null : [value];
30 + }
31 +
32 + dynamic _pickImageError;
33 + bool isVideo = false;
34 +
35 + VideoPlayerController? _controller;
36 + VideoPlayerController? _toBeDisposed;
37 + String? _retrieveDataError;
38 +
39 + final ImagePicker _picker = ImagePicker();
40 + final TextEditingController maxWidthController = TextEditingController();
41 + final TextEditingController maxHeightController = TextEditingController();
42 + final TextEditingController qualityController = TextEditingController();
43 +
44 + Future<void> _playVideo(XFile? file) async {
45 + if (file != null && mounted) {
46 + await _disposeVideoController();
47 + late VideoPlayerController controller;
48 + if (kIsWeb) {
49 + controller = VideoPlayerController.network(file.path);
50 + } else {
51 + controller = VideoPlayerController.file(File(file.path));
52 + }
53 + _controller = controller;
54 + // In web, most browsers won't honor a programmatic call to .play
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(() {});
65 + }
66 + }
67 +
68 + void _onImageButtonPressed(ImageSource source,
69 + {BuildContext? context, bool isMultiImage = false}) async {
70 + if (_controller != null) {
71 + await _controller!.setVolume(0.0);
72 + }
73 + if (isVideo) {
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 {
81 + final pickedFileList = await _picker.pickMultiImage(
82 + maxWidth: maxWidth,
83 + maxHeight: maxHeight,
84 + imageQuality: quality,
85 + );
86 + setState(() {
87 + _imageFileList = pickedFileList;
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 + }
115 + }
116 +
117 + @override
118 + void deactivate() {
119 + if (_controller != null) {
120 + _controller!.setVolume(0.0);
121 + _controller!.pause();
122 + }
123 + super.deactivate();
124 + }
125 +
126 + @override
127 + void dispose() {
128 + _disposeVideoController();
129 + maxWidthController.dispose();
130 + maxHeightController.dispose();
131 + qualityController.dispose();
132 + super.dispose();
133 + }
134 +
135 + Future<void> _disposeVideoController() async {
136 + if (_toBeDisposed != null) {
137 + await _toBeDisposed!.dispose();
138 + }
139 + _toBeDisposed = _controller;
140 + _controller = null;
141 + }
142 +
143 + Widget _previewVideo() {
144 + final Text? retrieveError = _getRetrieveErrorWidget();
145 + if (retrieveError != null) {
146 + return retrieveError;
147 + }
148 + if (_controller == null) {
149 + return const Text(
150 + 'You have not yet picked a video',
151 + textAlign: TextAlign.center,
152 + );
153 + }
154 + return Padding(
155 + padding: const EdgeInsets.all(10.0),
156 + child: AspectRatioVideo(_controller),
157 + );
158 + }
159 +
160 + Widget _previewImages() {
161 + final Text? retrieveError = _getRetrieveErrorWidget();
162 + if (retrieveError != null) {
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 + }
193 + }
194 +
195 + Widget _handlePreview() {
196 + if (isVideo) {
197 + return _previewVideo();
198 + } else {
199 + return _previewImages();
200 + }
201 + }
202 +
203 + Future<void> retrieveLostData() async {
204 + final LostDataResponse response = await _picker.retrieveLostData();
205 + if (response.isEmpty) {
206 + return;
207 + }
208 + if (response.file != null) {
209 + if (response.type == RetrieveType.video) {
210 + isVideo = true;
211 + await _playVideo(response.file);
212 + } else {
213 + isVideo = false;
214 + setState(() {
215 + _imageFile = response.file;
216 + _imageFileList = response.files;
217 + });
218 + }
219 + } else {
220 + _retrieveDataError = response.exception!.code;
221 + }
222 + }
223 +
224 + @override
225 + Widget build(BuildContext context) {
226 + return Scaffold(
227 + appBar: const MyAppBar(
228 + isBack: true,
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 +
336 + Text? _getRetrieveErrorWidget() {
337 + if (_retrieveDataError != null) {
338 + final Text result = Text(_retrieveDataError!);
339 + _retrieveDataError = null;
340 + return result;
341 + }
342 + return null;
343 + }
344 +
345 + Future<void> _displayPickImageDialog(
346 + BuildContext context, OnPickImageCallback onPick) async {
347 + return showDialog(
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 + }
402 +}
403 +
404 +typedef OnPickImageCallback = void Function(
405 + double? maxWidth, double? maxHeight, int? quality);
406 +
407 +class AspectRatioVideo extends StatefulWidget {
408 + const AspectRatioVideo(this.controller, {Key? key}) : super(key: key);
409 +
410 + final VideoPlayerController? controller;
411 +
412 + @override
413 + AspectRatioVideoState createState() => AspectRatioVideoState();
414 +}
415 +
416 +class AspectRatioVideoState extends State<AspectRatioVideo> {
417 + VideoPlayerController? get controller => widget.controller;
418 + bool initialized = false;
419 +
420 + void _onVideoControllerUpdate() {
421 + if (!mounted) {
422 + return;
423 + }
424 + if (initialized != controller!.value.isInitialized) {
425 + initialized = controller!.value.isInitialized;
426 + setState(() {});
427 + }
428 + }
429 +
430 + @override
431 + void initState() {
432 + super.initState();
433 + controller!.addListener(_onVideoControllerUpdate);
434 + }
435 +
436 + @override
437 + void dispose() {
438 + controller!.removeListener(_onVideoControllerUpdate);
439 + super.dispose();
440 + }
441 +
442 + @override
443 + Widget build(BuildContext context) {
444 + if (initialized) {
445 + return Center(
446 + child: AspectRatio(
447 + aspectRatio: controller!.value.aspectRatio,
448 + child: VideoPlayer(controller!),
449 + ),
450 + );
451 + } else {
452 + return Container();
453 + }
454 + }
455 +}
......
...@@ -4,13 +4,15 @@ import 'package:one_poem/routers/i_router.dart'; ...@@ -4,13 +4,15 @@ import 'package:one_poem/routers/i_router.dart';
4 import 'page/poem_detail.dart'; 4 import 'page/poem_detail.dart';
5 import 'page/poem_page.dart'; 5 import 'page/poem_page.dart';
6 import 'page/poem_publish.dart'; 6 import 'page/poem_publish.dart';
7 +import 'page/poem_record_video.dart';
7 import 'page/poem_video_player.dart'; 8 import 'page/poem_video_player.dart';
8 9
9 class PoemRouter implements IRouterProvider { 10 class PoemRouter implements IRouterProvider {
10 static String poemPage = '/poem'; 11 static String poemPage = '/poem';
11 static String poemDetailPage = '/detail'; 12 static String poemDetailPage = '/detail';
12 static String poemRecordAudioPage = '/poem/record/audio'; 13 static String poemRecordAudioPage = '/poem/record/audio';
13 - static String poemVidePlayer = '/poem/video/player'; 14 + static String poemRecordVideoPage = '/poem/record/video';
15 + static String poemVideoPlayer = '/poem/video/player';
14 static String poemPublish = '/poem/publish'; 16 static String poemPublish = '/poem/publish';
15 17
16 @override 18 @override
...@@ -32,6 +34,7 @@ class PoemRouter implements IRouterProvider { ...@@ -32,6 +34,7 @@ class PoemRouter implements IRouterProvider {
32 }, 34 },
33 ), 35 ),
34 ); 36 );
37 +
35 router.define( 38 router.define(
36 poemRecordAudioPage, 39 poemRecordAudioPage,
37 handler: Handler( 40 handler: Handler(
...@@ -43,8 +46,21 @@ class PoemRouter implements IRouterProvider { ...@@ -43,8 +46,21 @@ class PoemRouter implements IRouterProvider {
43 }, 46 },
44 ), 47 ),
45 ); 48 );
49 +
50 + router.define(
51 + poemRecordVideoPage,
52 + handler: Handler(
53 + handlerFunc: (_, Map<String, List<String>> params) {
54 + String? id = params['id']?.first;
55 + return const PoemRecordVideoPage(
56 + // poemId: int.parse(id!),
57 + );
58 + },
59 + ),
60 + );
61 +
46 router.define( 62 router.define(
47 - poemVidePlayer, 63 + poemVideoPlayer,
48 handler: Handler( 64 handler: Handler(
49 handlerFunc: (_, Map<String, List<String>> params) { 65 handlerFunc: (_, Map<String, List<String>> params) {
50 String? url = params['url']?.first; 66 String? url = params['url']?.first;
......
...@@ -108,7 +108,7 @@ class FriendCellState extends State<FriendCell> { ...@@ -108,7 +108,7 @@ class FriendCellState extends State<FriendCell> {
108 onTap: () { 108 onTap: () {
109 NavigatorUtils.push( 109 NavigatorUtils.push(
110 context, 110 context,
111 - '${PoemRouter.poemVidePlayer}?url=100', 111 + '${PoemRouter.poemVideoPlayer}?url=100',
112 ); 112 );
113 }, 113 },
114 child: Text( 114 child: Text(
......
...@@ -238,21 +238,21 @@ packages: ...@@ -238,21 +238,21 @@ packages:
238 name: device_info_plus 238 name: device_info_plus
239 url: "https://pub.dartlang.org" 239 url: "https://pub.dartlang.org"
240 source: hosted 240 source: hosted
241 - version: "3.2.0" 241 + version: "3.2.1"
242 device_info_plus_linux: 242 device_info_plus_linux:
243 dependency: transitive 243 dependency: transitive
244 description: 244 description:
245 name: device_info_plus_linux 245 name: device_info_plus_linux
246 url: "https://pub.dartlang.org" 246 url: "https://pub.dartlang.org"
247 source: hosted 247 source: hosted
248 - version: "2.1.0" 248 + version: "2.1.1"
249 device_info_plus_macos: 249 device_info_plus_macos:
250 dependency: transitive 250 dependency: transitive
251 description: 251 description:
252 name: device_info_plus_macos 252 name: device_info_plus_macos
253 url: "https://pub.dartlang.org" 253 url: "https://pub.dartlang.org"
254 source: hosted 254 source: hosted
255 - version: "2.2.0" 255 + version: "2.2.1"
256 device_info_plus_platform_interface: 256 device_info_plus_platform_interface:
257 dependency: transitive 257 dependency: transitive
258 description: 258 description:
...@@ -273,7 +273,7 @@ packages: ...@@ -273,7 +273,7 @@ packages:
273 name: device_info_plus_windows 273 name: device_info_plus_windows
274 url: "https://pub.dartlang.org" 274 url: "https://pub.dartlang.org"
275 source: hosted 275 source: hosted
276 - version: "2.1.0" 276 + version: "2.1.1"
277 dio: 277 dio:
278 dependency: "direct main" 278 dependency: "direct main"
279 description: 279 description:
...@@ -506,7 +506,7 @@ packages: ...@@ -506,7 +506,7 @@ packages:
506 name: image_picker_platform_interface 506 name: image_picker_platform_interface
507 url: "https://pub.dartlang.org" 507 url: "https://pub.dartlang.org"
508 source: hosted 508 source: hosted
509 - version: "2.4.1" 509 + version: "2.4.2"
510 integration_test: 510 integration_test:
511 dependency: "direct dev" 511 dependency: "direct dev"
512 description: flutter 512 description: flutter
...@@ -672,7 +672,7 @@ packages: ...@@ -672,7 +672,7 @@ packages:
672 name: path_provider_platform_interface 672 name: path_provider_platform_interface
673 url: "https://pub.dartlang.org" 673 url: "https://pub.dartlang.org"
674 source: hosted 674 source: hosted
675 - version: "2.0.1" 675 + version: "2.0.2"
676 path_provider_windows: 676 path_provider_windows:
677 dependency: transitive 677 dependency: transitive
678 description: 678 description:
...@@ -707,7 +707,7 @@ packages: ...@@ -707,7 +707,7 @@ packages:
707 name: plugin_platform_interface 707 name: plugin_platform_interface
708 url: "https://pub.dartlang.org" 708 url: "https://pub.dartlang.org"
709 source: hosted 709 source: hosted
710 - version: "2.0.2" 710 + version: "2.1.2"
711 pool: 711 pool:
712 dependency: transitive 712 dependency: transitive
713 description: 713 description:
...@@ -763,7 +763,7 @@ packages: ...@@ -763,7 +763,7 @@ packages:
763 name: quick_actions_platform_interface 763 name: quick_actions_platform_interface
764 url: "https://pub.dartlang.org" 764 url: "https://pub.dartlang.org"
765 source: hosted 765 source: hosted
766 - version: "1.0.0" 766 + version: "1.0.1"
767 rational: 767 rational:
768 dependency: transitive 768 dependency: transitive
769 description: 769 description:
...@@ -1083,7 +1083,7 @@ packages: ...@@ -1083,7 +1083,7 @@ packages:
1083 name: url_launcher_platform_interface 1083 name: url_launcher_platform_interface
1084 url: "https://pub.dartlang.org" 1084 url: "https://pub.dartlang.org"
1085 source: hosted 1085 source: hosted
1086 - version: "2.0.4" 1086 + version: "2.0.5"
1087 url_launcher_web: 1087 url_launcher_web:
1088 dependency: transitive 1088 dependency: transitive
1089 description: 1089 description:
...@@ -1146,7 +1146,7 @@ packages: ...@@ -1146,7 +1146,7 @@ packages:
1146 name: video_player_platform_interface 1146 name: video_player_platform_interface
1147 url: "https://pub.dartlang.org" 1147 url: "https://pub.dartlang.org"
1148 source: hosted 1148 source: hosted
1149 - version: "5.0.0" 1149 + version: "5.0.1"
1150 video_player_web: 1150 video_player_web:
1151 dependency: transitive 1151 dependency: transitive
1152 description: 1152 description:
...@@ -1209,7 +1209,7 @@ packages: ...@@ -1209,7 +1209,7 @@ packages:
1209 name: webview_flutter_platform_interface 1209 name: webview_flutter_platform_interface
1210 url: "https://pub.dartlang.org" 1210 url: "https://pub.dartlang.org"
1211 source: hosted 1211 source: hosted
1212 - version: "1.8.0" 1212 + version: "1.8.1"
1213 webview_flutter_wkwebview: 1213 webview_flutter_wkwebview:
1214 dependency: transitive 1214 dependency: transitive
1215 description: 1215 description:
......