From a7d83956eceab3ad7045fff27f05b8eb24719cef Mon Sep 17 00:00:00 2001 From: sindhu Date: Tue, 27 Aug 2024 14:13:00 +0700 Subject: [PATCH] step 9 : clock in & clock out selfie sudah bisa, need fix at clear camera controller after take photo --- lib/provider/camera_controller_provider.dart | 68 +++-- lib/screen/presensi/camera_page.dart | 269 +++++++++++++----- lib/screen/presensi/camera_page_v1.dart | 92 ++++++ .../presensi/presensi_selfie_screen.dart | 213 ++++++++------ .../presensi_selfie_upload_area_web.dart | 11 +- 5 files changed, 475 insertions(+), 178 deletions(-) create mode 100644 lib/screen/presensi/camera_page_v1.dart diff --git a/lib/provider/camera_controller_provider.dart b/lib/provider/camera_controller_provider.dart index 0dc0c42..e17e22f 100644 --- a/lib/provider/camera_controller_provider.dart +++ b/lib/provider/camera_controller_provider.dart @@ -1,33 +1,63 @@ +import 'dart:convert'; + import 'package:camera/camera.dart'; +import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:path/path.dart' as p; +import 'package:shared_preferences/shared_preferences.dart'; + +final imgPhotoWebProvider = StateProvider((ref) => ""); final cameraControllerProvider = - StateNotifierProvider( - (ref) => CameraControllerNotifier()); + StateNotifierProvider((ref) { + return CameraControllerNotifier(); +}); +// StateNotifier to manage CameraController class CameraControllerNotifier extends StateNotifier { - CameraControllerNotifier() : super(null); - - Future initializeCamera(CameraDescription camera) async { - final controller = CameraController(camera, ResolutionPreset.medium); - await controller.initialize(); - state = controller; + CameraControllerNotifier() : super(null) { + _initializeCamera(); } - Future takePicture() async { - if (state == null || !state!.value.isInitialized) { - throw Exception('Camera is not initialized'); + Future _initializeCamera() async { + WidgetsFlutterBinding.ensureInitialized(); + try { + final cameras = await availableCameras(); + if (cameras.isNotEmpty) { + final controller = CameraController(cameras[0], ResolutionPreset.max); + await controller.initialize(); + state = controller; + } + } catch (e) { + print('Error initializing camera: $e'); } + } - final path = p.join( - (await getTemporaryDirectory()).path, - '${DateTime.now()}.png', - ); + Future takePicture( + // ValueNotifier imageFile, + ValueNotifier base64ImageString, + ValueNotifier imagePath, + ) async { + // final flag = ValueNotifier(false); + if (state != null) { + try { + final image = await state!.takePicture(); + final bytes = await image.readAsBytes(); + String base64Image = base64Encode(bytes); - await state!.takePicture(); - return path; + // imageFile.value = image; + base64ImageString.value = base64Image; + imagePath.value = image.path; + + // final shared = await SharedPreferences.getInstance(); + // shared.setString("base64Image", base64Image); + } catch (e) { + print('Error taking picture: $e'); + // flag.value = false; + // RespErr(flag: false, message: 'Error taking picture: $e'); + } + } else { + print('CameraController is not initialized.'); + } } @override diff --git a/lib/screen/presensi/camera_page.dart b/lib/screen/presensi/camera_page.dart index a4f1551..4b366ca 100644 --- a/lib/screen/presensi/camera_page.dart +++ b/lib/screen/presensi/camera_page.dart @@ -1,90 +1,223 @@ +import 'dart:convert'; import 'dart:io'; +import 'package:absensi_sas/widget/sankbar_widget.dart'; import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shared_preferences/shared_preferences.dart'; -class CameraPage extends StatefulWidget { - const CameraPage({Key? key}) : super(key: key); +import '../../app/constant.dart'; +import '../../provider/camera_controller_provider.dart'; + +class CameraPage extends HookConsumerWidget { + const CameraPage({ + Key? key, + required this.filePathParam, + required this.fileDataBase64Param, + }) : super(key: key); + + final ValueNotifier filePathParam; + final ValueNotifier fileDataBase64Param; @override - State createState() => _CameraPageState(); -} + Widget build(BuildContext context, WidgetRef ref) { + final controller = ref.watch(cameraControllerProvider); + // final imagePath = useState(""); + // final fileDataBase64 = useState(""); -class _CameraPageState extends State { - CameraController? controller; - String imagePath = ""; - List? cameras; + // final fileData = useState(null); + final filePath = useState(""); + final fileDataBase64 = useState(""); + final cameraController = useState(null); - @override - void initState() { - super.initState(); - initializeCamera(); - } - - Future initializeCamera() async { - // Initialize cameras - WidgetsFlutterBinding.ensureInitialized(); - try { - cameras = await availableCameras(); - if (cameras != null && cameras!.isNotEmpty) { - // Use the first available camera - controller = CameraController(cameras![0], ResolutionPreset.max); - await controller?.initialize(); - if (mounted) { - setState(() {}); + useEffect(() { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + try { + final cameras = await availableCameras(); + if (cameras.isNotEmpty) { + final controller = + CameraController(cameras[0], ResolutionPreset.max); + await controller.initialize(); + cameraController.value = controller; + } + } catch (e) { + print('Error initializing camera: $e'); } + }); + return () {}; + }, []); + + Future initializeCamera() async { + try { + final cameras = await availableCameras(); + if (cameras.isNotEmpty) { + final controller = CameraController(cameras[0], ResolutionPreset.max); + await controller.initialize(); + cameraController.value = controller; + } + } catch (e) { + print('Error initializing camera: $e'); } - } catch (e) { - print('Error initializing camera: $e'); } - } - @override - void dispose() { - controller?.dispose(); - super.dispose(); - } + Future takePicture( + // ValueNotifier imageFile, + ValueNotifier base64ImageString, + ValueNotifier imagePath, + ) async { + // final flag = ValueNotifier(false); + if (cameraController.value != null) { + try { + final image = await cameraController.value!.takePicture(); + final bytes = await image.readAsBytes(); + String base64Image = base64Encode(bytes); - @override - Widget build(BuildContext context) { - if (controller == null || !controller!.value.isInitialized) { - return Center(child: CircularProgressIndicator()); + // imageFile.value = image; + base64ImageString.value = base64Image; + imagePath.value = image.path; + + fileDataBase64Param.value = base64Image; + filePathParam.value = image.path; + + // final shared = await SharedPreferences.getInstance(); + // shared.setString("base64Image", base64Image); + } catch (e) { + print('Error taking picture: $e'); + // flag.value = false; + // RespErr(flag: false, message: 'Error taking picture: $e'); + } + } else { + print('CameraController is not initialized.'); + } + } + + processFoto(BuildContext context) async { + await takePicture( + // fileData, + fileDataBase64, + filePath, + ); + + print("fileDataBase64 : ${fileDataBase64.value}"); + + if (fileDataBase64.value != "") { + // ref.read(imgPhotoWebProvider.notifier).state = fileDataBase64.value; + Navigator.of(context).pop(); + } else { + print('Error: Image result is null'); + } + + // proses base64 + // final shared = await SharedPreferences.getInstance(); + // final imageBase64 = shared.getString("base64Image"); + + // if (imageBase64 != "") { + // fileDataBase64.value = imageBase64; + // ref.read(imgPhotoWebProvider.notifier).state = imageBase64; + // Navigator.of(context).pop(); + // } else { + // print('Error: Image result is null'); + // } } return Scaffold( + appBar: AppBar( + title: const Text('Take Photo'), + ), body: SafeArea( child: Center( - child: Column( - children: [ - SizedBox(height: 50), - Container( - width: 200, - height: 200, - child: AspectRatio( - aspectRatio: controller!.value.aspectRatio, - child: CameraPreview(controller!), + child: controller == null || !controller.value.isInitialized + ? const CircularProgressIndicator() + : Padding( + padding: EdgeInsets.all(20), + child: Column( + children: [ + // const SizedBox(height: 50), + Expanded( + child: AspectRatio( + aspectRatio: controller.value.aspectRatio, + child: CameraPreview(controller), + ), + ), + SizedBox( + height: + Constant.getActualYPhone(context: context, y: 20), + ), + TextButton( + onPressed: () async { + fileDataBase64Param.value = ""; + filePathParam.value = ""; + // cameraController.dispose(); + // await controller.dispose(); + // await initializeCamera(); + }, + child: const Text("Clear Foto"), + ), + + Spacer(), + ElevatedButton( + // onPressed: () { + // Navigator.of(context).pushNamed(homeRoute); + // }, + onPressed: () async { + processFoto(context); + }, + style: ButtonStyle( + backgroundColor: MaterialStateColor.resolveWith( + (st) => (fileDataBase64.value != "") + ? Constant.textDarkGrey + : Constant.textOrange), + shape: + MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: BorderSide( + color: Constant.textOrange, + ), + ), + ), + shadowColor: + MaterialStateProperty.all(Color(0xffff48423d)), + elevation: MaterialStateProperty.all(4.0), + ), + child: Stack( + children: [ + (fileDataBase64.value != "") + ? SizedBox( + width: Constant.getActualXPhone( + context: context, x: 24), + height: Constant.getActualYPhone( + context: context, y: 32), + child: Center( + child: CircularProgressIndicator( + color: Constant.textOrange, + ), + ), + ) + : Align( + alignment: Alignment.center, + child: Text( + 'Process Photo', + style: Constant.titleH1_500_18( + context: context) + .copyWith( + color: Constant.textWhite, + ), + ), + ), + ], + ), + ), + // if (imagePath.value.isNotEmpty) + // Container( + // width: 300, + // height: 300, + // child: Image.network(imagePath.value), + // ), + ], + ), ), - ), - TextButton( - onPressed: () async { - try { - final image = await controller!.takePicture(); - setState(() { - imagePath = image.path; - }); - } catch (e) { - print('Error taking picture: $e'); - } - }, - child: Text("Take Photo"), - ), - if (imagePath.isNotEmpty) - Container( - width: 300, - height: 300, - child: Image.file(File(imagePath)), - ), - ], - ), ), ), ); diff --git a/lib/screen/presensi/camera_page_v1.dart b/lib/screen/presensi/camera_page_v1.dart new file mode 100644 index 0000000..ed2f44b --- /dev/null +++ b/lib/screen/presensi/camera_page_v1.dart @@ -0,0 +1,92 @@ +import 'dart:io'; +import 'package:camera/camera.dart'; +import 'package:flutter/material.dart'; + +class CameraPageV1 extends StatefulWidget { + const CameraPageV1({Key? key}) : super(key: key); + + @override + State createState() => _CameraPageState(); +} + +class _CameraPageState extends State { + CameraController? controller; + String imagePath = ""; + List? cameras; + + @override + void initState() { + super.initState(); + initializeCamera(); + } + + Future initializeCamera() async { + // Initialize cameras + WidgetsFlutterBinding.ensureInitialized(); + try { + cameras = await availableCameras(); + if (cameras != null && cameras!.isNotEmpty) { + // Use the first available camera + controller = CameraController(cameras![0], ResolutionPreset.max); + await controller?.initialize(); + if (mounted) { + setState(() {}); + } + } + } catch (e) { + print('Error initializing camera: $e'); + } + } + + @override + void dispose() { + controller?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (controller == null || !controller!.value.isInitialized) { + return Center(child: CircularProgressIndicator()); + } + + return Scaffold( + body: SafeArea( + child: Center( + child: Column( + children: [ + SizedBox(height: 50), + Container( + width: 200, + height: 200, + child: AspectRatio( + aspectRatio: controller!.value.aspectRatio, + child: CameraPreview(controller!), + ), + ), + TextButton( + onPressed: () async { + try { + final image = await controller!.takePicture(); + setState(() { + imagePath = image.path; + }); + } catch (e) { + print('Error taking picture: $e'); + } + }, + child: Text("Take Photo"), + ), + if (imagePath.isNotEmpty) + Container( + width: 300, + height: 300, + child: Image.file(File(imagePath)), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screen/presensi/presensi_selfie_screen.dart b/lib/screen/presensi/presensi_selfie_screen.dart index 78d1249..a8f3ce2 100644 --- a/lib/screen/presensi/presensi_selfie_screen.dart +++ b/lib/screen/presensi/presensi_selfie_screen.dart @@ -16,9 +16,11 @@ import 'package:flutter_image_compress/flutter_image_compress.dart'; // import 'package:geolocator/geolocator.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:image_picker_web/image_picker_web.dart'; import 'package:latlong2/latlong.dart'; import 'package:location/location.dart'; import 'package:mobkit_dashed_border/mobkit_dashed_border.dart'; +import 'package:shared_preferences/shared_preferences.dart'; // import 'package:permission_handler/permission_handler.dart'; import '../../app/constant.dart'; @@ -35,7 +37,6 @@ import 'camera_page.dart'; import 'check_distance_provider.dart'; import 'check_presensi_jam_provider.dart'; import 'googleapis_provider.dart'; -import 'presensi_selfie_upload_area.dart'; import 'dart:io' as io; import 'presensi_selfie_upload_area_web.dart'; @@ -55,22 +56,19 @@ class PresensiSelfieScreen extends HookConsumerWidget { final positionLatitude = useState(""); final positionLongitude = useState(""); - // final tTransactionCurrentDistance = useState("NULL"); final fileData = useState(null); final fileDataBase64 = useState(""); + final filePath = useState(""); final isImage = useState(false); final fileEkstension = useState(""); final fileSize = useState(0); final fileName = useState(""); - List cameras; Location location = new Location(); LocationData _locationData; - final controller = ref.watch(cameraControllerProvider); - final isInited = useState(false); - final imageUrl = useState(null); + // final imgPhotoWeb = ref.read(imgPhotoWebProvider); getBase64() async { // List imageBytes = await fileData.value?.readAsBytes(); @@ -94,47 +92,23 @@ class PresensiSelfieScreen extends HookConsumerWidget { pickImage() async { if (kIsWeb) { + // final shared = await SharedPreferences.getInstance(); + // final imageBase64 = shared.getString("base64Image"); + // final imagePathNew = shared.getString("imagePath"); try { - final path = - await ref.read(cameraControllerProvider.notifier).takePicture(); - imageUrl.value = path; + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => CameraPage( + fileDataBase64Param: fileDataBase64, + filePathParam: filePath, + ), + ), + ); } catch (e) { SanckbarWidget( context, "Failed to take picture", snackbarType.warning); } - } else { - final XFile? pickedFile = await _picker.pickImage( - source: ImageSource.camera, - // maxWidth set untuk width image - maxWidth: 640, - // maxHeight set untuk width image - maxHeight: 480); - if (pickedFile != null) { - final tmpFile = FilePickerResult([ - PlatformFile( - name: pickedFile.name, - size: await pickedFile.length(), - path: pickedFile.path, - ) - ]); - if (await pickedFile.length() > 10000000) { - SanckbarWidget(context, "File tidak boleh lebih dari 10 MB", - snackbarType.warning); - } else { - fileData.value = pickedFile; - isImage.value = true; - fileEkstension.value = tmpFile.files.single.extension ?? ""; - DateTime now = new DateTime.now(); - fileName.value = - "IMG-${now.year}${now.month}${now.day}.${tmpFile.files.single.extension ?? ''}"; - } - - // final Directory appDocumentsDir = - // await getApplicationDocumentsDirectory(); - // print(appDocumentsDir); - // await pickedFile!.saveTo(appDocumentsDir.path); - await getBase64(); - } } } @@ -782,46 +756,51 @@ class PresensiSelfieScreen extends HookConsumerWidget { child: CircularProgressIndicator(), ) : InkWell( - onTap: () async { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => - CameraPage(), - ), - ); + onTap: () { + pickImage(); }, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - // Icon( - // Icons.upload_outlined, - // color: Constant.textOrange, - // ), + child: Container( + width: Constant.getActualXPhone( + context: context, x: 390), + height: Constant.getActualYPhone( + context: context, + y: isImage.value ? 200 : 83), + decoration: BoxDecoration( + color: Constant.bgUploadFile, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // Icon( + // Icons.upload_outlined, + // color: Constant.textOrange, + // ), - Image.asset( - 'images/camera_selfie.png', // Path gambar untuk "Check In" - width: Constant.getActualXPhone( - context: context, x: 24), - height: Constant.getActualYPhone( - context: context, y: 24), - ), - SizedBox( - height: Constant.getActualYPhone( - context: context, - y: 4, + Image.asset( + 'images/camera_selfie.png', // Path gambar untuk "Check In" + width: Constant.getActualXPhone( + context: context, x: 24), + height: Constant.getActualYPhone( + context: context, y: 24), ), - ), - Text( - 'Upload File', - style: Constant.titleH2_400_12( - context: context) - .copyWith( - fontWeight: FontWeight.w600, - color: Constant.textOrange), - ) - ], + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 4, + ), + ), + Text( + 'Upload File', + style: Constant.titleH2_400_12( + context: context) + .copyWith( + fontWeight: FontWeight.w600, + color: Constant.textOrange), + ) + ], + ), ), ), ] else ...[ @@ -833,18 +812,75 @@ class PresensiSelfieScreen extends HookConsumerWidget { ? Center( child: CircularProgressIndicator(), ) - : PresensiSelfieUploadAreaWebWidget( - isLoading: isLoadingAddressUserLocation, - isImage: isImage, - fileData: fileData, - fileDataBase64: fileDataBase64, - pickFile: pickFile, - pickImage: pickImage, + : InkWell( + onTap: () { + pickImage(); + }, + child: Container( + width: Constant.getActualXPhone( + context: context, x: 390), + height: Constant.getActualYPhone( + context: context, + y: isImage.value ? 200 : 83), + decoration: BoxDecoration( + color: Constant.bgUploadFile, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + // Icon( + // Icons.upload_outlined, + // color: Constant.textOrange, + // ), + + Image.asset( + 'images/camera_selfie.png', // Path gambar untuk "Check In" + width: Constant.getActualXPhone( + context: context, x: 24), + height: Constant.getActualYPhone( + context: context, y: 24), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 4, + ), + ), + Text( + 'Upload File', + style: Constant.titleH2_400_12( + context: context) + .copyWith( + fontWeight: FontWeight.w600, + color: Constant.textOrange), + ) + ], + ), + ), ), ] ], ], + // gambar selfie + if (fileDataBase64.value.isNotEmpty) ...[ + SizedBox( + height: Constant.getActualYPhone(context: context, y: 40), + ), + SizedBox( + width: double.infinity, + height: + Constant.getActualYPhone(context: context, y: 200), + child: Image.network( + filePath.value, + fit: BoxFit.cover, + ), + ), + ], + Spacer(), // button clock in dan clock out @@ -924,9 +960,8 @@ class PresensiSelfieScreen extends HookConsumerWidget { token, "Clock In"); - // print( - // fileDataBase64.value, - // ); + print( + "proses imageX: ${fileDataBase64.value}"); }, style: ButtonStyle( backgroundColor: diff --git a/lib/screen/presensi/presensi_selfie_upload_area_web.dart b/lib/screen/presensi/presensi_selfie_upload_area_web.dart index 9d3b6fa..0750cd4 100644 --- a/lib/screen/presensi/presensi_selfie_upload_area_web.dart +++ b/lib/screen/presensi/presensi_selfie_upload_area_web.dart @@ -1,11 +1,16 @@ import 'dart:io'; +import 'package:flutter/foundation.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + import '../../app/constant.dart'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:mime/mime.dart'; -class PresensiSelfieUploadAreaWebWidget extends StatelessWidget { +import '../../provider/camera_controller_provider.dart'; + +class PresensiSelfieUploadAreaWebWidget extends HookConsumerWidget { const PresensiSelfieUploadAreaWebWidget({ super.key, required this.isImage, @@ -24,7 +29,9 @@ class PresensiSelfieUploadAreaWebWidget extends StatelessWidget { final Function pickFile; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final imgPhotoWeb = ref.read(imgPhotoWebProvider); + return InkWell( onTap: !isLoading.value ? () {