Reason Pun

clear and repair

Showing 100 changed files with 826 additions and 380 deletions
......@@ -50,7 +50,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.mofunsky.one_poem"
minSdkVersion 20
minSdkVersion 21
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
......
......@@ -17,19 +17,6 @@
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<!-- Displays an Android View that continues showing the launch screen
Drawable until Flutter paints its first frame, then this splash
screen fades out. A splash screen is useful to avoid any visual
gap between the end of Android's launch screen and the painting of
Flutter's first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<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';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:one_poem/poem/widgets/poem_content.dart';
import 'package:one_poem/recorder/widgets/poem_voice_widget.dart';
import 'package:one_poem/recorder/audio/widgets/poem_voice_widget.dart';
import 'package:one_poem/routers/fluro_navigator.dart';
import 'package:one_poem/util/toast_utils.dart';
import 'package:one_poem/widgets/bars/home_action_bar.dart';
......
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// ignore_for_file: public_member_api_docs
import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:one_poem/widgets/my_app_bar.dart';
import 'package:flutter/services.dart';
import 'package:one_poem/recorder/video/preview_screen.dart';
import 'package:one_poem/util/toast_utils.dart';
import 'package:path_provider/path_provider.dart';
import 'package:video_player/video_player.dart';
class PoemRecordVideoPage extends StatefulWidget {
const PoemRecordVideoPage({Key? key, this.title}) : super(key: key);
final String? title;
const PoemRecordVideoPage({Key? key}) : super(key: key);
@override
_PoemRecordVideoPageState createState() => _PoemRecordVideoPageState();
}
class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> {
List<XFile>? _imageFileList;
class _PoemRecordVideoPageState extends State<PoemRecordVideoPage>
with WidgetsBindingObserver {
CameraController? controller;
VideoPlayerController? videoController;
File? _imageFile;
File? _videoFile;
// Initial values
bool _isCameraInitialized = false;
bool _isRearCameraSelected = true;
bool _isVideoCameraSelected = false;
bool _isRecordingInProgress = false;
double _minAvailableExposureOffset = 0.0;
double _maxAvailableExposureOffset = 0.0;
double _minAvailableZoom = 1.0;
double _maxAvailableZoom = 1.0;
// Current values
double _currentZoomLevel = 1.0;
double _currentExposureOffset = 0.0;
FlashMode? _currentFlashMode;
List<File> allFileList = [];
final resolutionPresets = ResolutionPreset.values;
ResolutionPreset currentResolutionPreset = ResolutionPreset.high;
set _imageFile(XFile? value) {
_imageFileList = value == null ? null : [value];
List<CameraDescription>? cameras = [];
@override
void initState() {
// Hide the status bar in Android
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
initCameras().then((value) {
cameras = value;
if (cameras != null) {
onNewCameraSelected(cameras![0]);
refreshAlreadyCapturedImages();
} else {
Toast.show("摄像头出错啦~");
}
});
super.initState();
}
dynamic _pickImageError;
bool isVideo = false;
Future<List<CameraDescription>?> initCameras() async {
try {
WidgetsFlutterBinding.ensureInitialized();
List<CameraDescription> cameras = await availableCameras();
return cameras;
} on CameraException catch (e) {
return null;
}
}
VideoPlayerController? _controller;
VideoPlayerController? _toBeDisposed;
String? _retrieveDataError;
refreshAlreadyCapturedImages() async {
final directory = await getApplicationDocumentsDirectory();
List<FileSystemEntity> fileList = await directory.list().toList();
allFileList.clear();
List<Map<int, dynamic>> fileNames = [];
final ImagePicker _picker = ImagePicker();
final TextEditingController maxWidthController = TextEditingController();
final TextEditingController maxHeightController = TextEditingController();
final TextEditingController qualityController = TextEditingController();
for (var file in fileList) {
if (file.path.contains('.jpg') || file.path.contains('.mp4')) {
allFileList.add(File(file.path));
String name = file.path.split('/').last.split('.').first;
fileNames.add({0: int.parse(name), 1: file.path.split('/').last});
}
}
Future<void> _playVideo(XFile? file) async {
if (file != null && mounted) {
await _disposeVideoController();
late VideoPlayerController controller;
if (kIsWeb) {
controller = VideoPlayerController.network(file.path);
if (fileNames.isNotEmpty) {
final recentFile =
fileNames.reduce((curr, next) => curr[0] > next[0] ? curr : next);
String recentFileName = recentFile[1];
if (recentFileName.contains('.mp4')) {
_videoFile = File('${directory.path}/$recentFileName');
_imageFile = null;
_startVideoPlayer();
} else {
controller = VideoPlayerController.file(File(file.path));
}
_controller = controller;
// In web, most browsers won't honor a programmatic call to .play
// if the video has a sound track (and is not muted).
// Mute the video so it auto-plays in web!
// This is not needed if the call to .play is the result of user
// interaction (clicking on a "play" button, for example).
final double volume = kIsWeb ? 0.0 : 1.0;
await controller.setVolume(volume);
await controller.initialize();
await controller.setLooping(true);
await controller.play();
_imageFile = File('${directory.path}/$recentFileName');
_videoFile = null;
}
setState(() {});
}
}
void _onImageButtonPressed(ImageSource source,
{BuildContext? context, bool isMultiImage = false}) async {
if (_controller != null) {
await _controller!.setVolume(0.0);
Future<XFile?> takePicture() async {
final CameraController? cameraController = controller;
if (cameraController!.value.isTakingPicture) {
// A capture is already pending, do nothing.
return null;
}
if (isVideo) {
final XFile? file = await _picker.pickVideo(
source: source, maxDuration: const Duration(seconds: 10));
await _playVideo(file);
} else if (isMultiImage) {
await _displayPickImageDialog(context!,
(double? maxWidth, double? maxHeight, int? quality) async {
try {
final pickedFileList = await _picker.pickMultiImage(
maxWidth: maxWidth,
maxHeight: maxHeight,
imageQuality: quality,
);
setState(() {
_imageFileList = pickedFileList;
});
} catch (e) {
setState(() {
_pickImageError = e;
});
XFile file = await cameraController.takePicture();
return file;
} on CameraException catch (e) {
return null;
}
}
Future<void> _startVideoPlayer() async {
if (_videoFile != null) {
videoController = VideoPlayerController.file(_videoFile!);
await videoController!.initialize().then((_) {
// Ensure the first frame is shown after the video is initialized,
// even before the play button has been pressed.
setState(() {});
});
} else {
await _displayPickImageDialog(context!,
(double? maxWidth, double? maxHeight, int? quality) async {
await videoController!.setLooping(true);
await videoController!.play();
}
}
Future<void> startVideoRecording() async {
final CameraController? cameraController = controller;
if (controller!.value.isRecordingVideo) {
// A recording has already started, do nothing.
return;
}
try {
final pickedFile = await _picker.pickImage(
source: source,
maxWidth: maxWidth,
maxHeight: maxHeight,
imageQuality: quality,
);
setState(() {
_imageFile = pickedFile;
});
} catch (e) {
await cameraController!.startVideoRecording();
setState(() {
_pickImageError = e;
_isRecordingInProgress = true;
});
} on CameraException catch (e) {
// print('Error starting to record video: $e');
}
});
}
Future<XFile?> stopVideoRecording() async {
if (!controller!.value.isRecordingVideo) {
// Recording is already is stopped state
return null;
}
@override
void deactivate() {
if (_controller != null) {
_controller!.setVolume(0.0);
_controller!.pause();
try {
XFile file = await controller!.stopVideoRecording();
setState(() {
_isRecordingInProgress = false;
});
return file;
} on CameraException catch (e) {
return null;
}
super.deactivate();
}
@override
void dispose() {
_disposeVideoController();
maxWidthController.dispose();
maxHeightController.dispose();
qualityController.dispose();
super.dispose();
Future<void> pauseVideoRecording() async {
if (!controller!.value.isRecordingVideo) {
// Video recording is not in progress
return;
}
Future<void> _disposeVideoController() async {
if (_toBeDisposed != null) {
await _toBeDisposed!.dispose();
try {
await controller!.pauseVideoRecording();
} on CameraException catch (e) {
// print('Error pausing video recording: $e');
}
_toBeDisposed = _controller;
_controller = null;
}
Widget _previewVideo() {
final Text? retrieveError = _getRetrieveErrorWidget();
if (retrieveError != null) {
return retrieveError;
Future<void> resumeVideoRecording() async {
if (!controller!.value.isRecordingVideo) {
// No video recording was in progress
return;
}
if (_controller == null) {
return const Text(
'You have not yet picked a video',
textAlign: TextAlign.center,
);
try {
await controller!.resumeVideoRecording();
} on CameraException catch (e) {
// print('Error resuming video recording: $e');
}
return Padding(
padding: const EdgeInsets.all(10.0),
child: AspectRatioVideo(_controller),
);
}
Widget _previewImages() {
final Text? retrieveError = _getRetrieveErrorWidget();
if (retrieveError != null) {
return retrieveError;
}
if (_imageFileList != null) {
return Semantics(
child: ListView.builder(
key: UniqueKey(),
itemBuilder: (context, index) {
// Why network for web?
// See https://pub.dev/packages/image_picker#getting-ready-for-the-web-platform
return Semantics(
label: 'image_picker_example_picked_image',
child: kIsWeb
? Image.network(_imageFileList![index].path)
: Image.file(File(_imageFileList![index].path)),
);
},
itemCount: _imageFileList!.length,
),
label: 'image_picker_example_picked_images');
} else if (_pickImageError != null) {
return Text(
'Pick image error: $_pickImageError',
textAlign: TextAlign.center,
);
} else {
return const Text(
'You have not yet picked an image.',
textAlign: TextAlign.center,
);
void resetCameraValues() async {
_currentZoomLevel = 1.0;
_currentExposureOffset = 0.0;
}
void onNewCameraSelected(CameraDescription cameraDescription) async {
final previousCameraController = controller;
final CameraController cameraController = CameraController(
cameraDescription,
currentResolutionPreset,
imageFormatGroup: ImageFormatGroup.jpeg,
);
await previousCameraController?.dispose();
resetCameraValues();
if (mounted) {
setState(() {
controller = cameraController;
});
}
Widget _handlePreview() {
if (isVideo) {
return _previewVideo();
} else {
return _previewImages();
// Update UI if controller updated
cameraController.addListener(() {
if (mounted) setState(() {});
});
try {
await cameraController.initialize();
await Future.wait([
cameraController
.getMinExposureOffset()
.then((value) => _minAvailableExposureOffset = value),
cameraController
.getMaxExposureOffset()
.then((value) => _maxAvailableExposureOffset = value),
cameraController
.getMaxZoomLevel()
.then((value) => _maxAvailableZoom = value),
cameraController
.getMinZoomLevel()
.then((value) => _minAvailableZoom = value),
]);
_currentFlashMode = controller!.value.flashMode;
} on CameraException catch (e) {
// print('Error initializing camera: $e');
}
if (mounted) {
setState(() {
_isCameraInitialized = controller!.value.isInitialized;
});
}
}
Future<void> retrieveLostData() async {
final LostDataResponse response = await _picker.retrieveLostData();
if (response.isEmpty) {
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
final CameraController? cameraController = controller;
// App state changed before we got the chance to initialize.
if (cameraController == null || !cameraController.value.isInitialized) {
return;
}
if (response.file != null) {
if (response.type == RetrieveType.video) {
isVideo = true;
await _playVideo(response.file);
} else {
isVideo = false;
setState(() {
_imageFile = response.file;
_imageFileList = response.files;
});
if (state == AppLifecycleState.inactive) {
cameraController.dispose();
} else if (state == AppLifecycleState.resumed) {
onNewCameraSelected(cameraController.description);
}
} else {
_retrieveDataError = response.exception!.code;
}
@override
void dispose() {
controller?.dispose();
videoController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const MyAppBar(
isBack: true,
isTransparent: true,
),
body: Center(
child: !kIsWeb && defaultTargetPlatform == TargetPlatform.android
? FutureBuilder<void>(
future: retrieveLostData(),
builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return const Text(
'You have not yet picked an image.',
textAlign: TextAlign.center,
);
case ConnectionState.done:
return _handlePreview();
default:
if (snapshot.hasError) {
return Text(
'Pick image/video error: ${snapshot.error}}',
textAlign: TextAlign.center,
);
} else {
return const Text(
'You have not yet picked an image.',
textAlign: TextAlign.center,
);
}
}
},
return SafeArea(
child: Scaffold(
backgroundColor: Colors.black,
body: _isCameraInitialized
? Column(
children: [
AspectRatio(
aspectRatio: 1 / controller!.value.aspectRatio,
child: Stack(
children: [
controller!.buildPreview(),
Padding(
padding: const EdgeInsets.fromLTRB(
16.0,
8.0,
16.0,
8.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Align(
alignment: Alignment.topRight,
child: Container(
decoration: BoxDecoration(
color: Colors.black87,
borderRadius: BorderRadius.circular(10.0),
),
child: Padding(
padding: const EdgeInsets.only(
left: 8.0,
right: 8.0,
),
child: DropdownButton<ResolutionPreset>(
dropdownColor: Colors.black87,
underline: Container(),
value: currentResolutionPreset,
items: [
for (ResolutionPreset preset
in resolutionPresets)
DropdownMenuItem(
child: Text(
preset
.toString()
.split('.')[1]
.toUpperCase(),
style: const TextStyle(
color: Colors.white),
),
value: preset,
)
: _handlePreview(),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Semantics(
label: 'image_picker_example_from_gallery',
child: FloatingActionButton(
onPressed: () {
isVideo = false;
_onImageButtonPressed(ImageSource.gallery, context: context);
],
onChanged: (value) {
setState(() {
currentResolutionPreset = value!;
_isCameraInitialized = false;
});
onNewCameraSelected(
controller!.description);
},
heroTag: 'image0',
tooltip: 'Pick Image from gallery',
child: const Icon(Icons.photo),
hint: const Text("Select item"),
),
),
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: FloatingActionButton(
onPressed: () {
isVideo = false;
_onImageButtonPressed(
ImageSource.gallery,
context: context,
isMultiImage: true,
);
},
heroTag: 'image1',
tooltip: 'Pick Multiple Image from gallery',
child: const Icon(Icons.photo_library),
),
),
// Spacer(),
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: FloatingActionButton(
onPressed: () {
isVideo = false;
_onImageButtonPressed(ImageSource.camera, context: context);
padding: const EdgeInsets.only(
right: 8.0, top: 16.0),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10.0),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
_currentExposureOffset
.toStringAsFixed(1) +
'x',
style:
const TextStyle(color: Colors.black),
),
),
),
),
Expanded(
child: RotatedBox(
quarterTurns: 3,
child: SizedBox(
height: 30,
child: Slider(
value: _currentExposureOffset,
min: _minAvailableExposureOffset,
max: _maxAvailableExposureOffset,
activeColor: Colors.white,
inactiveColor: Colors.white30,
onChanged: (value) async {
setState(() {
_currentExposureOffset = value;
});
await controller!
.setExposureOffset(value);
},
heroTag: 'image2',
tooltip: 'Take a Photo',
child: const Icon(Icons.camera_alt),
),
),
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: FloatingActionButton(
backgroundColor: Colors.red,
onPressed: () {
isVideo = true;
_onImageButtonPressed(ImageSource.gallery);
),
),
Row(
children: [
Expanded(
child: Slider(
value: _currentZoomLevel,
min: _minAvailableZoom,
max: _maxAvailableZoom,
activeColor: Colors.white,
inactiveColor: Colors.white30,
onChanged: (value) async {
setState(() {
_currentZoomLevel = value;
});
await controller!.setZoomLevel(value);
},
heroTag: 'video0',
tooltip: 'Pick Video from gallery',
child: const Icon(Icons.video_library),
),
),
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: FloatingActionButton(
backgroundColor: Colors.red,
onPressed: () {
isVideo = true;
_onImageButtonPressed(ImageSource.camera);
},
heroTag: 'video1',
tooltip: 'Take a Video',
child: const Icon(Icons.videocam),
padding: const EdgeInsets.only(right: 8.0),
child: Container(
decoration: BoxDecoration(
color: Colors.black87,
borderRadius:
BorderRadius.circular(10.0),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
_currentZoomLevel.toStringAsFixed(1) +
'x',
style: const TextStyle(
color: Colors.white),
),
),
),
),
],
),
);
}
Text? _getRetrieveErrorWidget() {
if (_retrieveDataError != null) {
final Text result = Text(_retrieveDataError!);
_retrieveDataError = null;
return result;
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
InkWell(
onTap: _isRecordingInProgress
? () async {
if (controller!
.value.isRecordingPaused) {
await resumeVideoRecording();
} else {
await pauseVideoRecording();
}
return null;
}
Future<void> _displayPickImageDialog(
BuildContext context, OnPickImageCallback onPick) async {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Add optional parameters'),
content: Column(
children: <Widget>[
TextField(
controller: maxWidthController,
keyboardType:
const TextInputType.numberWithOptions(decimal: true),
decoration: const InputDecoration(
hintText: "Enter maxWidth if desired"),
),
TextField(
controller: maxHeightController,
keyboardType:
const TextInputType.numberWithOptions(decimal: true),
decoration: const InputDecoration(
hintText: "Enter maxHeight if desired"),
),
TextField(
controller: qualityController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
hintText: "Enter quality if desired"),
: () {
setState(() {
_isCameraInitialized = false;
});
onNewCameraSelected(cameras![
_isRearCameraSelected ? 1 : 0]);
setState(() {
_isRearCameraSelected =
!_isRearCameraSelected;
});
},
child: Stack(
alignment: Alignment.center,
children: [
const Icon(
Icons.circle,
color: Colors.black38,
size: 60,
),
_isRecordingInProgress
? controller!
.value.isRecordingPaused
? const Icon(
Icons.play_arrow,
color: Colors.white,
size: 30,
)
: const Icon(
Icons.pause,
color: Colors.white,
size: 30,
)
: Icon(
_isRearCameraSelected
? Icons.camera_front
: Icons.camera_rear,
color: Colors.white,
size: 30,
),
],
),
actions: <Widget>[
TextButton(
child: const Text('CANCEL'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: const Text('PICK'),
onPressed: () {
double? width = maxWidthController.text.isNotEmpty
? double.parse(maxWidthController.text)
: null;
double? height = maxHeightController.text.isNotEmpty
? double.parse(maxHeightController.text)
: null;
int? quality = qualityController.text.isNotEmpty
? int.parse(qualityController.text)
: null;
onPick(width, height, quality);
Navigator.of(context).pop();
}),
],
InkWell(
onTap: _isVideoCameraSelected
? () async {
if (_isRecordingInProgress) {
XFile? rawVideo =
await stopVideoRecording();
File videoFile =
File(rawVideo!.path);
int currentUnix = DateTime.now()
.millisecondsSinceEpoch;
final directory =
await getApplicationDocumentsDirectory();
String fileFormat = videoFile.path
.split('.')
.last;
_videoFile = await videoFile.copy(
'${directory.path}/$currentUnix.$fileFormat',
);
});
}
}
typedef OnPickImageCallback = void Function(
double? maxWidth, double? maxHeight, int? quality);
class AspectRatioVideo extends StatefulWidget {
const AspectRatioVideo(this.controller, {Key? key}) : super(key: key);
_startVideoPlayer();
} else {
await startVideoRecording();
}
}
: () async {
XFile? rawImage =
await takePicture();
File imageFile =
File(rawImage!.path);
final VideoPlayerController? controller;
int currentUnix = DateTime.now()
.millisecondsSinceEpoch;
@override
AspectRatioVideoState createState() => AspectRatioVideoState();
}
final directory =
await getApplicationDocumentsDirectory();
class AspectRatioVideoState extends State<AspectRatioVideo> {
VideoPlayerController? get controller => widget.controller;
bool initialized = false;
String fileFormat =
imageFile.path.split('.').last;
await imageFile.copy(
'${directory.path}/$currentUnix.$fileFormat',
);
void _onVideoControllerUpdate() {
if (!mounted) {
return;
}
if (initialized != controller!.value.isInitialized) {
initialized = controller!.value.isInitialized;
setState(() {});
}
refreshAlreadyCapturedImages();
},
child: Stack(
alignment: Alignment.center,
children: [
Icon(
Icons.circle,
color: _isVideoCameraSelected
? Colors.white
: Colors.white38,
size: 80,
),
Icon(
Icons.circle,
color: _isVideoCameraSelected
? Colors.red
: Colors.white,
size: 65,
),
_isVideoCameraSelected &&
_isRecordingInProgress
? const Icon(
Icons.stop_rounded,
color: Colors.white,
size: 32,
)
: Container(),
],
),
),
InkWell(
onTap:
_imageFile != null || _videoFile != null
? () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
PreviewScreen(
imageFile: _imageFile!,
fileList: allFileList,
),
),
);
}
@override
void initState() {
super.initState();
controller!.addListener(_onVideoControllerUpdate);
: null,
child: Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.black,
borderRadius:
BorderRadius.circular(10.0),
border: Border.all(
color: Colors.white,
width: 2,
),
image: _imageFile != null
? DecorationImage(
image: FileImage(_imageFile!),
fit: BoxFit.cover,
)
: null,
),
child: videoController != null &&
videoController!
.value.isInitialized
? ClipRRect(
borderRadius:
BorderRadius.circular(8.0),
child: AspectRatio(
aspectRatio: videoController!
.value.aspectRatio,
child: VideoPlayer(
videoController!),
),
)
: Container(),
),
),
],
),
],
),
),
],
),
),
Expanded(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.only(
left: 8.0,
right: 4.0,
),
child: TextButton(
onPressed: _isRecordingInProgress
? null
: () {
if (_isVideoCameraSelected) {
setState(() {
_isVideoCameraSelected =
false;
});
}
@override
void dispose() {
controller!.removeListener(_onVideoControllerUpdate);
super.dispose();
},
style: TextButton.styleFrom(
primary: _isVideoCameraSelected
? Colors.black54
: Colors.black,
backgroundColor: _isVideoCameraSelected
? Colors.white30
: Colors.white,
),
child: const Text('IMAGE'),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(
left: 4.0, right: 8.0),
child: TextButton(
onPressed: () {
if (!_isVideoCameraSelected) {
setState(() {
_isVideoCameraSelected = true;
});
}
@override
Widget build(BuildContext context) {
if (initialized) {
return Center(
child: AspectRatio(
aspectRatio: controller!.value.aspectRatio,
child: VideoPlayer(controller!),
},
style: TextButton.styleFrom(
primary: _isVideoCameraSelected
? Colors.black
: Colors.black54,
backgroundColor: _isVideoCameraSelected
? Colors.white
: Colors.white30,
),
child: const Text('VIDEO'),
),
),
),
],
),
),
Padding(
padding:
const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
InkWell(
onTap: () async {
setState(() {
_currentFlashMode = FlashMode.off;
});
await controller!.setFlashMode(
FlashMode.off,
);
},
child: Icon(
Icons.flash_off,
color: _currentFlashMode == FlashMode.off
? Colors.amber
: Colors.white,
),
),
InkWell(
onTap: () async {
setState(() {
_currentFlashMode = FlashMode.auto;
});
await controller!.setFlashMode(
FlashMode.auto,
);
},
child: Icon(
Icons.flash_auto,
color: _currentFlashMode == FlashMode.auto
? Colors.amber
: Colors.white,
),
),
InkWell(
onTap: () async {
setState(() {
_currentFlashMode = FlashMode.always;
});
await controller!.setFlashMode(
FlashMode.always,
);
},
child: Icon(
Icons.flash_on,
color: _currentFlashMode == FlashMode.always
? Colors.amber
: Colors.white,
),
),
InkWell(
onTap: () async {
setState(() {
_currentFlashMode = FlashMode.torch;
});
await controller!.setFlashMode(
FlashMode.torch,
);
},
child: Icon(
Icons.highlight,
color: _currentFlashMode == FlashMode.torch
? Colors.amber
: Colors.white,
),
),
],
),
)
],
),
),
),
],
)
: const Center(
child: Text(
'LOADING',
style: TextStyle(color: Colors.white),
),
),
),
);
} else {
return Container();
}
}
}
......
import 'dart:io';
import 'package:flutter/material.dart';
import 'preview_screen.dart';
class CapturesScreen extends StatelessWidget {
final List<File> imageFileList;
const CapturesScreen({required this.imageFileList});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: SingleChildScrollView(
physics: BouncingScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'Captures',
style: TextStyle(
fontSize: 32.0,
color: Colors.white,
),
),
),
GridView.count(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
crossAxisCount: 2,
children: [
for (File imageFile in imageFileList)
Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.black,
width: 2,
),
),
child: InkWell(
onTap: () {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => PreviewScreen(
fileList: imageFileList,
imageFile: imageFile,
),
),
);
},
child: Image.file(
imageFile,
fit: BoxFit.cover,
),
),
),
],
),
],
),
),
);
}
}
import 'dart:io';
import 'package:flutter/material.dart';
import 'captures_screen.dart';
class PreviewScreen extends StatelessWidget {
final File imageFile;
final List<File> fileList;
const PreviewScreen({
required this.imageFile,
required this.fileList,
});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: TextButton(
onPressed: () {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => CapturesScreen(
imageFileList: fileList,
),
),
);
},
child: Text('Go to all captures'),
style: TextButton.styleFrom(
primary: Colors.black,
backgroundColor: Colors.white,
),
),
),
Expanded(
child: Image.file(imageFile),
),
],
),
);
}
}
......@@ -31,7 +31,7 @@ class _AccountManagerPageState extends State<AccountManagerPage> {
children: <Widget>[
Stack(
children: <Widget>[
ClickItem(title: '店铺logo', onTap: () {}),
ClickItem(title: '头像', onTap: () {}),
Positioned(
top: 8.px,
bottom: 8.px,
......
......@@ -120,6 +120,27 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
camera:
dependency: "direct main"
description:
name: camera
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.4+5"
camera_platform_interface:
dependency: transitive
description:
name: camera_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.4"
camera_web:
dependency: transitive
description:
name: camera_web
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.1+1"
characters:
dependency: transitive
description:
......@@ -632,7 +653,7 @@ packages:
source: hosted
version: "1.8.0"
path_provider:
dependency: transitive
dependency: "direct main"
description:
name: path_provider
url: "https://pub.dartlang.org"
......@@ -764,6 +785,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
quiver:
dependency: transitive
description:
name: quiver
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1+1"
rational:
dependency: transitive
description:
......
......@@ -89,12 +89,16 @@ dependencies:
tapped: ^2.0.0-nullsafety.0
# 加载动画库
flutter_spinkit: ^5.0.0
# fijkplayer (Video player plugin for Flutter) Flutter 媒体播放器
fijkplayer: ^0.10.1
json_annotation: ^4.4.0
flutter_plugin_record: ^1.0.1
# fijkplayer (Video player plugin for Flutter) Flutter 媒体播放器
fijkplayer: ^0.10.1
camera: ^0.9.4+5
path_provider: ^2.0.8
dependency_overrides:
decimal: 1.5.0
......@@ -169,9 +173,7 @@ flutter:
assets:
- assets/data/
- assets/images/
- assets/images/home/
- assets/images/login/
- assets/images/account/
- assets/images/state/
- assets/images/poem/
- assets/images/category/
......