Related
I am building WhatsApp clone for demo. I want to hide AppBar and Tabbar when I click on Camera tab and I want CameraScreen in full screen. I hope, I could made very clear. I have also included whole code of the CameraScreen page thus you can understand(just edited to add whole code).
Sorry for uploading code late.
Thank you in advance.
Here is scree shot:Click here
class _CameraScreenState extends State<CameraScreen> {
CameraController? _cameraController;
// bool _isvideoRecording = false;
Future<void>? cameravalue;
bool isrecording = false;
String videopath = '';
XFile? videorecording;
bool flash = false;
bool isCameraFront = false;
double trasform = 0;
#override
void initState() {
// TODO: implement initState
super.initState();
_cameraController = CameraController(camera![0], ResolutionPreset.high);
cameravalue = _cameraController!.initialize();
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
_cameraController!.dispose();
}
#override
Widget build(BuildContext context) {
return Stack(
children: [
FutureBuilder(
future: cameravalue,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return CameraPreview(_cameraController!);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
),
Positioned(
bottom: 0,
child: Container(
padding: const EdgeInsets.only(
top: 5,
bottom: 5,
),
color: Colors.black,
width: MediaQuery.of(context).size.width,
child: Column(
children: [
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
onPressed: () {
setState(() {
flash = !flash;
});
flash
? _cameraController!.setFlashMode(FlashMode.torch)
: _cameraController!.setFlashMode(FlashMode.off);
},
icon: Icon(
flash ? Icons.flash_on : Icons.flash_off,
color: Colors.white,
size: 28,
),
),
GestureDetector(
onLongPress: () async {
await _cameraController!.startVideoRecording();
setState(() {
isrecording = true;
});
},
onLongPressUp: () async {
XFile videopath =
await _cameraController!.stopVideoRecording();
setState(() {
isrecording = false;
});
Navigator.push(
context,
MaterialPageRoute(
builder: (builder) => VideoView(
path: videopath.path,
)));
},
onTap: () {
if (!isrecording) {
takePhoto(context);
}
},
child: isrecording
? const Icon(
Icons.radio_button_on,
color: Colors.red,
size: 80,
)
: const Icon(
Icons.panorama_fish_eye,
color: Colors.white,
size: 70,
),
),
IconButton(
onPressed: () async {
setState(() {
isCameraFront = !isCameraFront;
trasform = trasform + pi;
});
int cameraPos = isCameraFront ? 0 : 1;
_cameraController = CameraController(
camera![cameraPos], ResolutionPreset.high);
cameravalue = _cameraController!.initialize();
},
icon: Transform.rotate(
angle: trasform,
child: const Icon(
Icons.flip_camera_ios,
color: Colors.white,
size: 30,
),
),
),
],
),
const SizedBox(
height: 4,
),
const Text(
'Hold for Video, tap for photo',
style: TextStyle(color: Colors.white),
textAlign: TextAlign.center,
),
],
),
),
)
],
);
}
void takePhoto(BuildContext context) async {
final path =
join((await getTemporaryDirectory()).path, "${DateTime.now()}.png");
XFile picture = await _cameraController!.takePicture();
picture.saveTo(path);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CameraView(
path: path,
),
),
);
}
You can implement this features by doing the following simple steps. There will be another possible solutions.
First of all, create a new route ( new widget ) that contains camera view in fullscreen.
When you tap the camera tab, go to that route with fade-in or scale animation with faster duration that will make UI more realistic.
hello guys!
i am using flutter story view package to implement story functionality in my app and i am using camera plugin to record video and upload, however the content type of the uploaded video is application/octet-stream when i receive it on my server and only when it is sent from ios. for android everything is fine and i get the content type as video/mp4.
can you guys please help me.
i have not done anything fancy i have just used the basic functionality of start recording and stop recording of the camra plugin.
// ignore_for_file: use_build_context_synchronously
List<CameraDescription>? cameras;
class CameraScreen extends StatefulWidget {
const CameraScreen({Key? key}) : super(key: key);
#override
State<CameraScreen> createState() => _CameraScreenState();
}
class _CameraScreenState extends State<CameraScreen> {
CameraController? _cameraController;
Future<void>? cameraValue;
bool flash = false;
bool iscamerafront = true;
static const _maxSeconds = 30;
ValueNotifier<bool> visible = ValueNotifier(false);
ValueNotifier<bool> isRecoring = ValueNotifier(false);
TimerProvider? _timerProvider;
#override
void initState() {
super.initState();
_timerProvider = Provider.of(context, listen: false);
_cameraController = CameraController(cameras![0], ResolutionPreset.veryHigh,
imageFormatGroup: ImageFormatGroup.bgra8888);
cameraValue = _cameraController?.initialize();
_cameraController?.prepareForVideoRecording();
lockCamera();
}
lockCamera() async {
await _cameraController!.lockCaptureOrientation();
}
#override
void dispose() {
super.dispose();
visible.dispose();
isRecoring.dispose();
_cameraController?.dispose();
}
#override
Widget build(BuildContext context) {
print("camera_screen");
final size = MediaQuery.of(context).size;
return Scaffold(
extendBodyBehindAppBar: true,
appBar: AppBar(
centerTitle: true,
title: ValueListenableBuilder<bool>(
valueListenable: visible,
builder: (context, value, child) {
return Visibility(
visible: value,
child: Container(
padding: const EdgeInsets.all(10),
width: 50,
height: 50,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.black.withOpacity(0.5)),
child: Center(
child: Consumer<TimerProvider>(
builder: (context, value, child) {
if (value.seconds == 0) {
if (_cameraController!.value.isRecordingVideo) {
stopRecording();
}
}
return Text(
value.seconds.toString(),
style: CustomStyles.fixAppBarTextStyle
.copyWith(color: Colors.white),
);
}),
),
));
}),
backgroundColor: Colors.transparent,
leadingWidth: 100,
leading: InkWell(
onTap: () {
Navigator.pop(context);
},
child: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"Cancel",
style: CustomStyles.fixMediumBodyTextStyle
.copyWith(color: Colors.white),
),
)),
),
),
body: SafeArea(
top: false,
child: Stack(
children: [
FutureBuilder(
future: cameraValue,
builder: (context, snapshot) {
_cameraController?.lockCaptureOrientation();
if (snapshot.connectionState == ConnectionState.done) {
return Transform.scale(
scale: size.aspectRatio *
_cameraController!.value.aspectRatio <
1
? 1 /
(size.aspectRatio *
_cameraController!.value.aspectRatio)
: size.aspectRatio *
_cameraController!.value.aspectRatio,
child: Center(child: CameraPreview(_cameraController!)),
);
} else {
return const Center(
child: CircularProgressIndicator.adaptive(),
);
}
}),
Positioned(
bottom: 0.0,
child: Container(
padding: const EdgeInsets.only(top: 5, bottom: 5),
width: Dimensions.screenWidth,
child: Column(
children: [
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.black.withOpacity(0.5)),
child: Center(
child: IconButton(
icon: Icon(
flash ? Icons.flash_on : Icons.flash_off,
color: Colors.white,
size: 28,
),
onPressed: () {
setState(() {
flash = !flash;
});
flash
? _cameraController!
.setFlashMode(FlashMode.torch)
: _cameraController!
.setFlashMode(FlashMode.off);
}),
),
),
ValueListenableBuilder<bool>(
valueListenable: isRecoring,
builder: (context, value, child) {
return GestureDetector(
onLongPress: () {
startRecording();
},
onLongPressUp: () {
stopRecording();
},
onTap: () {
if (value == false) {
takePhoto(context);
}
},
child: value == true
? const CircleAvatar(
radius: 50,
backgroundColor: Colors.white30,
child: CircleAvatar(
radius: 35,
backgroundColor: Colors.red,
),
)
: const CircleAvatar(
radius: 35,
backgroundColor: Colors.white30,
child: CircleAvatar(
radius: 25,
backgroundColor: Colors.white,
),
));
}),
Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.black.withOpacity(0.5)),
child: IconButton(
icon: const Icon(
Icons.flip_camera_ios,
color: Colors.white,
size: 28,
),
onPressed: () async {
setState(() {
iscamerafront = !iscamerafront;
});
int cameraPos = iscamerafront ? 0 : 1;
_cameraController = CameraController(
cameras![cameraPos], ResolutionPreset.high);
cameraValue = _cameraController?.initialize();
}),
),
],
),
addVerticalSpace(6),
const Text(
"Hold for Video, tap for photo",
style: TextStyle(
color: Colors.white,
),
textAlign: TextAlign.center,
),
addVerticalSpace(5),
],
),
),
),
],
),
),
);
}
void startRecording() async {
visible.value = true;
_timerProvider?.startCountDown(_maxSeconds);
isRecoring.value = true;
await _cameraController?.startVideoRecording();
}
void stopRecording() async {
XFile videopath = await _cameraController!.stopVideoRecording();
_timerProvider?.stopTimer();
isRecoring.value = false;
Navigator.push(
context,
MaterialPageRoute(
builder: (builder) => CameraResult(
image: File(videopath.path),
isImage: false,
))).then((value) {
visible.value = false;
});
}
void takePhoto(BuildContext context) async {
XFile file = await _cameraController!.takePicture();
SystemSound.play(SystemSoundType.click);
final img.Image? capturedImage =
img.decodeImage(await File(file.path).readAsBytes());
final img.Image orientedImage = img.flipHorizontal(capturedImage!);
File finalImage =
await File(file.path).writeAsBytes(img.encodeJpg(orientedImage));
Navigator.push(
context,
CupertinoPageRoute(
builder: (builder) => CameraResult(
image: finalImage,
isImage: true,
))).then((value) {
visible.value = false;
});
}
}
the upload function is as bellow
{File? shouts,
File? thumbnail,
int? duration,
bool? isImage,
required String userId,
required OnUploadProgressCallback onUploadProgress}) async {
assert(shouts != null);
try {
var url = Constants.POSTSHOUTURL;
final httpClient = FileService.getHttpClient();
final request = await httpClient.postUrl(Uri.parse(url));
int byteCount = 0;
var multipart;
if (isImage == false) {
multipart = await http.MultipartFile.fromPath(
'story-media', shouts!.path,
contentType: MediaType("video", "mp4"));
} else {
multipart = await http.MultipartFile.fromPath(
'story-media',
shouts!.path,
);
}
var multipart2 =
await http.MultipartFile.fromPath('thumbnail', thumbnail!.path);
var requestMultipart = http.MultipartRequest('POST', Uri.parse(url));
requestMultipart.fields["userid"] = userId.toString();
requestMultipart.fields["duration"] = duration.toString();
requestMultipart.headers['Content-type'] = 'multipart/form-data';
requestMultipart.files.add(multipart);
requestMultipart.files.add(multipart2);
var msStream = requestMultipart.finalize();
var totalByteLength = requestMultipart.contentLength;
request.contentLength = totalByteLength;
request.headers.add(HttpHeaders.authorizationHeader,
"Bearer ${Hive.box<UserData>(Constants.userDb).get(Hive.box<UserData>(Constants.userDb).keyAt(0))!.token}");
request.headers.set(HttpHeaders.contentTypeHeader,
requestMultipart.headers[HttpHeaders.contentTypeHeader]!);
Stream<List<int>> streamUpload = msStream.transform(
StreamTransformer.fromHandlers(
handleData: (data, sink) {
sink.add(data);
byteCount += data.length;
if (onUploadProgress != null) {
onUploadProgress(byteCount, totalByteLength);
// CALL STATUS CALLBACK;
}
},
handleError: (error, stack, sink) {
throw error;
},
handleDone: (sink) {
sink.close();
// UPLOAD DONE;
},
),
);
await request.addStream(streamUpload);
final httpResponse = await request.close();
var statusCode = httpResponse.statusCode;
if (statusCode ~/ 100 == 2) {
onUploadProgress(0, 0);
return await FileService.readResponseAsString(httpResponse);
}
return "";
} on SocketException {
throw FetchDataException("No internet to upload Shout!");
}
}```
i have used necessary info.plist setting also
Hello i would like to show only percentage of the photo I take with my camera. This code allows me to take several photos with the camera of my phone. I can also film. What I would like to do is show me only a percentage of the photos I take. Example 70% on the sides and 80% above and below
Here is the code:
class NoGluecamera extends StatefulWidget {
#override
_CameraScreenState createState() => _CameraScreenState();
}
class _CameraScreenState extends State<NoGluecamera>
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.medium;
refreshAlreadyCapturedImages() async {
final directory = await getApplicationDocumentsDirectory();
List<FileSystemEntity> fileList = await directory.list().toList();
allFileList.clear();
List<Map<int, dynamic>> fileNames = [];
fileList.forEach((file) {
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});
}
});
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 {
_imageFile = File('${directory.path}/$recentFileName');
_videoFile = null;
}
setState(() {});
}
}
Future<XFile?> takePicture(
) async {
final CameraController? cameraController = controller;
if (cameraController!.value.isTakingPicture) {
// A capture is already pending, do nothing.
return null;
}
try {
XFile file = await cameraController.takePicture();
return file;
} on CameraException catch (e) {
print('Error occured while taking picture: $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(() {});
});
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 {
await cameraController!.startVideoRecording();
setState(() {
_isRecordingInProgress = true;
print(_isRecordingInProgress);
});
} 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;
}
try {
XFile file = await controller!.stopVideoRecording();
setState(() {
_isRecordingInProgress = false;
});
return file;
} on CameraException catch (e) {
print('Error stopping video recording: $e');
return null;
}
}
Future<void> pauseVideoRecording() async {
if (!controller!.value.isRecordingVideo) {
// Video recording is not in progress
return;
}
try {
await controller!.pauseVideoRecording();
} on CameraException catch (e) {
print('Error pausing video recording: $e');
}
}
Future<void> resumeVideoRecording() async {
if (!controller!.value.isRecordingVideo) {
// No video recording was in progress
return;
}
try {
await controller!.resumeVideoRecording();
} on CameraException catch (e) {
print('Error resuming video recording: $e');
}
}
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;
});
}
// 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;
});
}
}
#override
void initState() {
// Hide the status bar in Android
SystemChrome.setEnabledSystemUIOverlays([]);
// Set and initialize the new camera
onNewCameraSelected(cameras[0]);
refreshAlreadyCapturedImages();
super.initState();
}
#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 (state == AppLifecycleState.inactive) {
cameraController.dispose();
} else if (state == AppLifecycleState.resumed) {
onNewCameraSelected(cameraController.description);
}
}
#override
void dispose() {
controller?.dispose();
videoController?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: Colors.black,
body: _isCameraInitialized
? Column(
children: [
AspectRatio(
aspectRatio: 3 / 5,
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: 4.0,
right: 4.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: TextStyle(
color: Colors.white),
),
value: preset,
)
],
onChanged: (value) {
setState(() {
currentResolutionPreset = value!;
_isCameraInitialized = false;
});
onNewCameraSelected(
controller!.description);
},
hint: Text("Select item"),
),
),
),
),
// Spacer(),
Padding(
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: TextStyle(color: Colors.black),
),
),
),
),
Expanded(
child: RotatedBox(
quarterTurns: 3,
child: Container(
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);
},
),
),
),
),
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);
},
),
),
Padding(
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: TextStyle(color: Colors.white),
),
),
),
),
],
),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
InkWell(
onTap: _isRecordingInProgress
? () async {
if (controller!
.value.isRecordingPaused) {
await resumeVideoRecording();
} else {
await pauseVideoRecording();
}
}
: () {
setState(() {
_isCameraInitialized = false;
});
onNewCameraSelected(cameras[
_isRearCameraSelected ? 1 : 0]);
setState(() {
_isRearCameraSelected =
!_isRearCameraSelected;
});
},
child: Stack(
alignment: Alignment.center,
children: [
Icon(
Icons.circle,
color: Colors.black38,
size: 60,
),
_isRecordingInProgress
? controller!
.value.isRecordingPaused
? Icon(
Icons.play_arrow,
color: Colors.white,
size: 30,
)
: Icon(
Icons.pause,
color: Colors.white,
size: 30,
)
: Icon(
_isRearCameraSelected
? Icons.camera_front
: Icons.camera_rear,
color: Colors.white,
size: 30,
),
],
),
),
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',
);
_startVideoPlayer();
} else {
await startVideoRecording();
}
}
: () async {
XFile? rawImage =
await takePicture();
File imageFile =
File(rawImage!.path);
int currentUnix = DateTime.now()
.millisecondsSinceEpoch;
final directory =
await getApplicationDocumentsDirectory();
String fileFormat =
imageFile.path.split('.').last;
print(fileFormat);
await imageFile.copy(
'${directory.path}/$currentUnix.$fileFormat',
);
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
? 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,
),
),
);
}
: 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: 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;
});
}
},
style: TextButton.styleFrom(
primary: _isVideoCameraSelected
? Colors.black54
: Colors.black,
backgroundColor: _isVideoCameraSelected
? Colors.white30
: Colors.white,
),
child: Text('IMAGE'),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(
left: 4.0, right: 8.0),
child: TextButton(
onPressed: () {
if (!_isVideoCameraSelected) {
setState(() {
_isVideoCameraSelected = true;
});
I am trying to build an app that lets users select videos and upload them, so for this, I am using the image_picker package. However, I am currently running tests and whenever I try to select a video from the gallery, instead of getting a file back I get null. I don't know if the problem is with how I am using the package or with permissions, although according to the package you don't need permissions for android.
Below the function in my code that is giving me trouble:
handleChooseFromGallery() async {
Navigator.pop(context);
File filePicked = await ImagePicker.pickVideo(
source: ImageSource.gallery,
maxDuration: Duration(seconds: 90),
);
//This line always returns false
print({'is file not null': filePicked != null});
setState(() {
if (filePicked != null) {
this.files.add(filePicked);
this.selected = true;
} else {
showError();
}
});
}
The complete code (minus completely irrelevant things):
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:golfapp/data/course_data.dart';
import 'package:golfapp/data/user_data.dart';
import 'package:provider/provider.dart';
import 'package:image_picker/image_picker.dart';
import 'package:uuid/uuid.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:golfapp/data/semana_data.dart';
import 'package:golfapp/widgets/chewie_item.dart';
final StorageReference storageRef = FirebaseStorage.instance.ref();
final postsRef = Firestore.instance.collection('posts');
final DateTime timestamp = DateTime.now();
class DetailScreen extends StatefulWidget {
final int index;
final String photoUrl;
final String videoUrl;
DetailScreen({this.index, this.photoUrl, this.videoUrl});
#override
_DetailScreenState createState() => _DetailScreenState();
}
class _DetailScreenState extends State<DetailScreen> {
bool isUploading = false;
bool selected = false;
List<File> files = [];
String postId = Uuid().v4();
TextEditingController captionController = TextEditingController();
showError() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Row(
children: [
Padding(
padding: EdgeInsets.only(right: 10.0),
child: Text('Error'),
),
Icon(Icons.error_outline, size: 60.0),
],
),
content: SingleChildScrollView(
child: ListBody(
children: [
Text(
'Hubo un error con el video seleccionado.',
style: TextStyle(
fontFamily: 'Montserrat',
fontWeight: FontWeight.w600,
fontSize: 17.0,
),
),
],
),
),
);
},
);
}
clearImage() {
setState(() {
this.selected = false;
});
}
handleTakePhoto() async {
Navigator.pop(context);
File fileTaken = await ImagePicker.pickVideo(
source: ImageSource.camera,
maxDuration: Duration(seconds: 90),
);
setState(() {
if (fileTaken != null) {
this.files.add(fileTaken);
this.selected = true;
} else {
showError();
}
});
}
handleChooseFromGallery() async {
Navigator.pop(context);
File filePicked = await ImagePicker.pickVideo(
source: ImageSource.gallery,
maxDuration: Duration(seconds: 90),
);
print({'is file not null': filePicked != null});
setState(() {
if (filePicked != null) {
this.files.add(filePicked);
this.selected = true;
} else {
showError();
}
});
}
selectImage(parentContext) {
return showDialog(
context: parentContext,
builder: (context) {
return SimpleDialog(
title: Text("Seleccionar video"),
children: <Widget>[
SimpleDialogOption(
child: Text("Tomar video"),
onPressed: handleTakePhoto,
),
SimpleDialogOption(
child: Text("Video de la galerĂa"),
onPressed: handleChooseFromGallery,
),
SimpleDialogOption(
child: Text("Cancelar"),
onPressed: () => Navigator.pop(context),
)
],
);
},
);
}
Future<String> uploadVideo(imageFile) async {
StorageUploadTask uploadTask = storageRef
.child(Provider.of<UserData>(context, listen: false).user.id)
.child(Provider.of<CourseData>(context, listen: false).course.uid)
.child(Provider.of<SemanaData>(context, listen: false).semana.uid)
.putFile(imageFile, StorageMetadata(contentType: 'video/mp4'));
StorageTaskSnapshot storageSnap = await uploadTask.onComplete;
String downloadUrl = await storageSnap.ref.getDownloadURL();
return downloadUrl;
}
createPostInFirestore({List<String> mediaUrl, String description}) {
postsRef
.document(Provider.of<UserData>(context, listen: false).user.id)
.collection("userPosts")
.document(Provider.of<CourseData>(context, listen: false).course.uid)
.setData({
"semana": Provider.of<SemanaData>(context, listen: false).semana.uid,
"postId": postId,
"ownerId": Provider.of<UserData>(context, listen: false).user.id,
"username": Provider.of<UserData>(context, listen: false).user.username,
"mediaUrl": mediaUrl,
"description": description,
"timestamp": timestamp,
"likes": {},
});
}
handleSubmit() async {
setState(() {
isUploading = true;
});
List<String> mediaUrlS = [];
for (File fileLoop in files) {
String mediaUrl = await uploadVideo(fileLoop);
mediaUrlS.add(mediaUrl);
}
createPostInFirestore(
mediaUrl: mediaUrlS,
description: captionController.text,
);
captionController.clear();
setState(() {
files = [];
isUploading = false;
postId = Uuid().v4();
selected = false;
});
}
Scaffold buildUploadForm() {
return Scaffold(
appBar: AppBar(
title: Text(
"Tu video",
style: TextStyle(color: Colors.black),
),
actions: [
FlatButton(
onPressed: files.length < 2 ? selectImage(context) : null,
child: Text(
'Seleccionar otro video',
),
),
FlatButton(
onPressed: isUploading ? null : () => handleSubmit(),
child: Text(
"Mandar",
),
),
],
),
body: ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: files.length,
itemBuilder: (BuildContext context, int index) {
final File fileBuilder = files[index];
return Container(
height: 220.0,
width: MediaQuery.of(context).size.width * 0.8,
child: Center(
child: AspectRatio(
aspectRatio: 16 / 9,
child: Container(
child: ChewieListItem(
videoUrl: fileBuilder.path,
network: false,
file: fileBuilder,
),
),
),
),
);
}),
);
}
Widget buildNormalScreen () {
return Container(
child: GestureDetector(
onTap: () {
selectImage(context);
},
child: Container(
height: 50.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30.0),
color: Theme.of(context).accentColor,
),
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Subir videos',
style: TextStyle(
fontFamily: 'Comfortaa',
fontSize: 17.0,
color: Colors.white,
),
),
],
),
),
),
),
);
}
#override
Widget build(BuildContext context) {
return !selected
? buildNormalScreen()
: buildUploadForm();
}
}
In the function at the top, I also tried doing something like ImagePicker.pickVideo().then((file){...}) but it didn't work.
I also added <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> to my "android/app/src/main/AndroidManifest.xml" file.
I also tried adding android:requestLegacyExternalStorage="true" inside the <application> tag in my "android/app/src/main/AndroidManifest.xml" file, but then I get an error:
I don't know what I should be changing either in my code or in one of the files in the android folder, any help is deeply appreciated.
Thank you very much in advance
Put the condition in the same function.
handleChooseFromGallery() async {
Navigator.pop(context);
File filePicked = await ImagePicker.pickVideo(
source: ImageSource.gallery,
maxDuration: Duration(seconds: 90),
//This line always returns false
print({'is file not null': filePicked != null});
setState(() {
if (filePicked != null) {
this.files.add(filePicked);
this.selected = true;
} else {
showError();
}
});
}
);
I wanted to change a value in firestore by a button onPressed. I know there might be seconds delay and I wanted to show a CircularProgressIndicator widget while waiting. But It's not not working.
Here is my widget:
Widget save(String id) {
return new FutureBuilder(
future: PostController().isAlreadySaved(id),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
if (snapshot.hasData) {
if (snapshot.data) {
return FlatButton(
onPressed: () {
setState(() {
isSaveLoading = true;
});
PostController().deleteSaved(id);
setState(() {
isSaveLoading = false;
});
},
child: !isSaveLoading
? Icon(
MyFlutterApp.star,
size: 30,
color: Colors.redAccent,
)
: SizedBox(
height: 15,
width: 15,
child: CircularProgressIndicator(
strokeWidth: 1,
),
),
);
} else {
return FlatButton(
onPressed: () {
setState(() {
isSaveLoading = true;
});
PostController().save(context, id);
setState(() {
isSaveLoading = false;
});
},
child: !isSaveLoading
? Icon(
MyFlutterApp.star,
size: 30,
color: Colors.grey,
)
: SizedBox(
height: 15,
width: 15,
child: CircularProgressIndicator(
strokeWidth: 1,
),
),
);
}
} else {
return Container(
//color: Colors.white,
width: Adapt.screenW(),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Flexible(
child: Shimmer.fromColors(
baseColor: Colors.grey[400],
highlightColor: Colors.grey[50],
enabled: true,
child: Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
child: new Icon(
MyFlutterApp.star,
size: 30,
color: Colors.white,
),
),
],
),
),
),
],
),
);
}
},
);
}
Here is my isAlreadySaved() function:
Future<bool> isAlreadySaved(String id) async {
bool isSaved = false;
QuerySnapshot snapshot =
await databaseReference.collection('saved').getDocuments();
snapshot.documents.forEach((a) async {
if (a.data['postID'] == id&&
a.data['saver'] == globals.currentUser.uid) {
isSaved = true;
}
});
return isSaved;
}
The delete function actually deletes the document from my saved collection in firestore and the save function creates a document and saves.
Thank you in advance!
I think the problem is because you update the widget tree too fastly, or the flutter UI thread is locked by waiting the PostController job to finish...
In these lines:
onPressed: () {
setState(() {
isSaveLoading = true;
});
PostController().deleteSaved(id);
setState(() {
isSaveLoading = false;
});
},
Here I see that you want to update the loading state of the button. But the problem is that when you set isSaveLoading to true you don't wait for the PostController().deleteSaved(id) to finish before resetting isSaveLoading to false.
On another side, if PostController().deleteSaved() is doing a long job, since it's not async, it can lock the UI thread for a time, so you will never see your circular progress bar.
You can make the onPressed callback async and await for the PostController job.
onPressed: () async {
setState(() {
isSaveLoading = true;
});
// The deleteSaved function have to be async too
await PostController().deleteSaved(id);
setState(() {
isSaveLoading = false;
});
},
Hope this will help!