From 3c9253c1c1a73bb126c17e7616f8f2a488da622b Mon Sep 17 00:00:00 2001 From: Sas Andy Date: Tue, 16 Jan 2024 08:44:11 +0700 Subject: [PATCH 1/3] init --- .vscode/settings.json | 2 ++ app_petty_cash/pubspec.lock | 50 ++++++++++++++++++------------------- 2 files changed, 27 insertions(+), 25 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/app_petty_cash/pubspec.lock b/app_petty_cash/pubspec.lock index ba1a4c2..4813d03 100644 --- a/app_petty_cash/pubspec.lock +++ b/app_petty_cash/pubspec.lock @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.18.0" convert: dependency: transitive description: @@ -224,14 +224,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.17.0" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" lints: dependency: transitive description: @@ -244,26 +236,26 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" open_file: dependency: "direct main" description: @@ -433,18 +425,18 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" state_notifier: dependency: transitive description: @@ -457,10 +449,10 @@ packages: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" string_scanner: dependency: transitive description: @@ -481,10 +473,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.1" top_snackbar_flutter: dependency: "direct main" description: @@ -573,6 +565,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + url: "https://pub.dev" + source: hosted + version: "0.3.0" win32: dependency: transitive description: @@ -590,5 +590,5 @@ packages: source: hosted version: "1.0.4" sdks: - dart: ">=3.0.6 <4.0.0" + dart: ">=3.2.0-194.0.dev <4.0.0" flutter: ">=3.10.0" From 157ad9a34cb12bce96e9dcf15dad9d0391d2d6ce Mon Sep 17 00:00:00 2001 From: Sas Andy Date: Tue, 16 Jan 2024 13:36:23 +0700 Subject: [PATCH 2/3] coba camera --- app_petty_cash/android/app/build.gradle | 2 + .../android/app/src/main/AndroidManifest.xml | 1 + app_petty_cash/lib/app/route.dart | 29 +- .../lib/screen/camera/coba_camera.dart | 128 +++++ app_petty_cash/lib/screen/camera/example.dart | 542 ++++++++++++++++++ .../lib/screen/report/report_screen.dart | 251 ++++---- app_petty_cash/lib/widget/custom_drawer.dart | 23 +- .../flutter/generated_plugin_registrant.cc | 4 + .../linux/flutter/generated_plugins.cmake | 1 + .../Flutter/GeneratedPluginRegistrant.swift | 6 + app_petty_cash/pubspec.lock | 212 ++++++- app_petty_cash/pubspec.yaml | 5 + .../flutter/generated_plugin_registrant.cc | 3 + .../windows/flutter/generated_plugins.cmake | 1 + 14 files changed, 1074 insertions(+), 134 deletions(-) create mode 100644 app_petty_cash/lib/screen/camera/coba_camera.dart create mode 100644 app_petty_cash/lib/screen/camera/example.dart diff --git a/app_petty_cash/android/app/build.gradle b/app_petty_cash/android/app/build.gradle index ed29a90..a97d4f8 100644 --- a/app_petty_cash/android/app/build.gradle +++ b/app_petty_cash/android/app/build.gradle @@ -52,6 +52,7 @@ android { targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName + multiDexEnabled true } buildTypes { @@ -69,4 +70,5 @@ flutter { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'com.android.support:multidex:1.0.3' } diff --git a/app_petty_cash/android/app/src/main/AndroidManifest.xml b/app_petty_cash/android/app/src/main/AndroidManifest.xml index b54eb58..7144690 100644 --- a/app_petty_cash/android/app/src/main/AndroidManifest.xml +++ b/app_petty_cash/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,5 @@ + (null); + final photoBase64 = useState(""); + final previewLoading = useState(false); + getBase64() async { + // List imageBytes = await photo.value?.readAsBytes(); + if (photo.value != null) { + final bytes = io.File(photo.value!.path).readAsBytesSync(); + String base64Image = base64Encode(bytes); + photoBase64.value = base64Image; + final file = File(photo.value!.path); + print(file.lengthSync() / 1000000); + print(await photo.value!.length()); + + // await getExternalStorageDirectory(); + // print(await photo.value!.saveTo(path)); + print(base64Image); + } + } + + final ImagePicker _picker = ImagePicker(); + pickImage() async { + previewLoading.value = true; + final XFile? pickedFile = await _picker.pickImage( + source: ImageSource.camera, + ); + photo.value = pickedFile; + // final Directory appDocumentsDir = + // await getApplicationDocumentsDirectory(); + // print(appDocumentsDir); + // await pickedFile!.saveTo(appDocumentsDir.path); + await getBase64(); + previewLoading.value = false; + } + + browseImage() async { + previewLoading.value = true; + + final XFile? pickedFile = await _picker.pickImage( + source: ImageSource.gallery, + ); + photo.value = pickedFile; + getBase64(); + previewLoading.value = false; + } + + return Scaffold( + body: SingleChildScrollView( + child: SafeArea( + minimum: const EdgeInsets.all(20), + child: Column( + children: [ + Container( + height: Constant.getActualYPhone(context: context, y: 500), + width: MediaQuery.of(context).size.width, + color: Colors.red, + child: LayoutBuilder( + builder: (context, constraints) { + final String? mime = + lookupMimeType(photo.value?.path ?? ""); + + return Semantics( + label: 'image_picker_example_picked_image', + child: (mime != null + ? (mime.startsWith('image/')) + ? Image.file( + // image: AssetImage(photo.value!.path), + File(photo.value!.path), + frameBuilder: (context, child, frame, + wasSynchronouslyLoaded) { + return (wasSynchronouslyLoaded) + ? Center( + child: Text("Loadinga"), + ) + : Container( + child: child, + ); + }, + errorBuilder: (BuildContext context, + Object error, StackTrace? stackTrace) { + return const Center( + child: Text( + 'This image type is not supported')); + }, + ) + : null + : null)); + }, + ), + ), + // photo.value != null + // ? PhotoView(imageProvider: AssetImage(photo.value!.path)) + // : Container(), + Row( + children: [ + ElevatedButton( + onPressed: () { + pickImage(); + }, + child: Text("Take a picture")), + ElevatedButton( + onPressed: () { + browseImage(); + }, + child: Text("Browse a picture")) + ], + ), + ], + ), + ), + ), + ); + } +} diff --git a/app_petty_cash/lib/screen/camera/example.dart b/app_petty_cash/lib/screen/camera/example.dart new file mode 100644 index 0000000..e792072 --- /dev/null +++ b/app_petty_cash/lib/screen/camera/example.dart @@ -0,0 +1,542 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:mime/mime.dart'; +import 'package:video_player/video_player.dart'; + +class CamerExample extends StatefulWidget { + const CamerExample({super.key}); + + @override + State createState() => _CamerExampleState(); +} + +class _CamerExampleState extends State { + List? _mediaFileList; + + void _setImageFileListFromFile(XFile? value) { + _mediaFileList = value == null ? null : [value]; + } + + dynamic _pickImageError; + bool isVideo = false; + + VideoPlayerController? _controller; + VideoPlayerController? _toBeDisposed; + String? _retrieveDataError; + + final ImagePicker _picker = ImagePicker(); + final TextEditingController maxWidthController = TextEditingController(); + final TextEditingController maxHeightController = TextEditingController(); + final TextEditingController qualityController = TextEditingController(); + + Future _playVideo(XFile? file) async { + if (file != null && mounted) { + await _disposeVideoController(); + late VideoPlayerController controller; + if (kIsWeb) { + controller = VideoPlayerController.networkUrl(Uri.parse(file.path)); + } 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). + const double volume = kIsWeb ? 0.0 : 1.0; + await controller.setVolume(volume); + await controller.initialize(); + await controller.setLooping(true); + await controller.play(); + setState(() {}); + } + } + + Future _onImageButtonPressed( + ImageSource source, { + required BuildContext context, + bool isMultiImage = false, + bool isMedia = false, + }) async { + if (_controller != null) { + await _controller!.setVolume(0.0); + } + if (context.mounted) { + 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 List pickedFileList = isMedia + ? await _picker.pickMultipleMedia( + maxWidth: maxWidth, + maxHeight: maxHeight, + imageQuality: quality, + ) + : await _picker.pickMultiImage( + maxWidth: maxWidth, + maxHeight: maxHeight, + imageQuality: quality, + ); + setState(() { + _mediaFileList = pickedFileList; + }); + } catch (e) { + setState(() { + _pickImageError = e; + }); + } + }); + } else if (isMedia) { + await _displayPickImageDialog(context, + (double? maxWidth, double? maxHeight, int? quality) async { + try { + final List pickedFileList = []; + final XFile? media = await _picker.pickMedia( + maxWidth: maxWidth, + maxHeight: maxHeight, + imageQuality: quality, + ); + if (media != null) { + pickedFileList.add(media); + setState(() { + _mediaFileList = pickedFileList; + }); + } + } catch (e) { + setState(() { + _pickImageError = e; + }); + } + }); + } else { + await _displayPickImageDialog(context, + (double? maxWidth, double? maxHeight, int? quality) async { + try { + final XFile? pickedFile = await _picker.pickImage( + source: source, + maxWidth: maxWidth, + maxHeight: maxHeight, + imageQuality: quality, + ); + setState(() { + _setImageFileListFromFile(pickedFile); + }); + } catch (e) { + setState(() { + _pickImageError = e; + }); + } + }); + } + } + } + + @override + void deactivate() { + if (_controller != null) { + _controller!.setVolume(0.0); + _controller!.pause(); + } + super.deactivate(); + } + + @override + void dispose() { + _disposeVideoController(); + maxWidthController.dispose(); + maxHeightController.dispose(); + qualityController.dispose(); + super.dispose(); + } + + Future _disposeVideoController() async { + if (_toBeDisposed != null) { + await _toBeDisposed!.dispose(); + } + _toBeDisposed = _controller; + _controller = null; + } + + Widget _previewVideo() { + final Text? retrieveError = _getRetrieveErrorWidget(); + if (retrieveError != null) { + return retrieveError; + } + if (_controller == null) { + return const Text( + 'You have not yet picked a video', + textAlign: TextAlign.center, + ); + } + return Padding( + padding: const EdgeInsets.all(10.0), + child: AspectRatioVideo(_controller), + ); + } + + Widget _previewImages() { + final Text? retrieveError = _getRetrieveErrorWidget(); + if (retrieveError != null) { + return retrieveError; + } + if (_mediaFileList != null) { + return Semantics( + label: 'image_picker_example_picked_images', + child: ListView.builder( + key: UniqueKey(), + itemBuilder: (BuildContext context, int index) { + final String? mime = lookupMimeType(_mediaFileList![index].path); + + // Why network for web? + // See https://pub.dev/packages/image_picker_for_web#limitations-on-the-web-platform + return Semantics( + label: 'image_picker_example_picked_image', + child: kIsWeb + ? Image.network(_mediaFileList![index].path) + : (mime == null || mime.startsWith('image/') + ? Image.file( + File(_mediaFileList![index].path), + errorBuilder: (BuildContext context, Object error, + StackTrace? stackTrace) { + return const Center( + child: + Text('This image type is not supported')); + }, + ) + : _buildInlineVideoPlayer(index)), + ); + }, + itemCount: _mediaFileList!.length, + ), + ); + } 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, + ); + } + } + + Widget _buildInlineVideoPlayer(int index) { + final VideoPlayerController controller = + VideoPlayerController.file(File(_mediaFileList![index].path)); + const double volume = kIsWeb ? 0.0 : 1.0; + controller.setVolume(volume); + controller.initialize(); + controller.setLooping(true); + controller.play(); + return Center(child: AspectRatioVideo(controller)); + } + + Widget _handlePreview() { + if (isVideo) { + return _previewVideo(); + } else { + return _previewImages(); + } + } + + Future retrieveLostData() async { + final LostDataResponse response = await _picker.retrieveLostData(); + if (response.isEmpty) { + return; + } + if (response.file != null) { + if (response.type == RetrieveType.video) { + isVideo = true; + await _playVideo(response.file); + } else { + isVideo = false; + setState(() { + if (response.files == null) { + _setImageFileListFromFile(response.file); + } else { + _mediaFileList = response.files; + } + }); + } + } else { + _retrieveDataError = response.exception!.code; + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("Camera example"), + ), + body: Center( + child: !kIsWeb && defaultTargetPlatform == TargetPlatform.android + ? FutureBuilder( + future: retrieveLostData(), + builder: (BuildContext context, AsyncSnapshot 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(); + case ConnectionState.active: + 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, + ); + } + } + }, + ) + : _handlePreview(), + ), + floatingActionButton: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Semantics( + label: 'image_picker_example_from_gallery', + child: FloatingActionButton( + onPressed: () { + isVideo = false; + _onImageButtonPressed(ImageSource.gallery, context: context); + }, + heroTag: 'image0', + tooltip: 'Pick Image from gallery', + child: const Icon(Icons.photo), + ), + ), + Padding( + padding: const EdgeInsets.only(top: 16.0), + child: FloatingActionButton( + onPressed: () { + isVideo = false; + _onImageButtonPressed( + ImageSource.gallery, + context: context, + isMultiImage: true, + isMedia: true, + ); + }, + heroTag: 'multipleMedia', + tooltip: 'Pick Multiple Media from gallery', + child: const Icon(Icons.photo_library), + ), + ), + Padding( + padding: const EdgeInsets.only(top: 16.0), + child: FloatingActionButton( + onPressed: () { + isVideo = false; + _onImageButtonPressed( + ImageSource.gallery, + context: context, + isMedia: true, + ); + }, + heroTag: 'media', + tooltip: 'Pick Single Media from gallery', + child: const Icon(Icons.photo_library), + ), + ), + 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), + ), + ), + if (_picker.supportsImageSource(ImageSource.camera)) + Padding( + padding: const EdgeInsets.only(top: 16.0), + child: FloatingActionButton( + onPressed: () { + isVideo = false; + _onImageButtonPressed(ImageSource.camera, context: context); + }, + 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, context: context); + }, + heroTag: 'video0', + tooltip: 'Pick Video from gallery', + child: const Icon(Icons.video_library), + ), + ), + if (_picker.supportsImageSource(ImageSource.camera)) + Padding( + padding: const EdgeInsets.only(top: 16.0), + child: FloatingActionButton( + backgroundColor: Colors.red, + onPressed: () { + isVideo = true; + _onImageButtonPressed(ImageSource.camera, context: context); + }, + heroTag: 'video1', + tooltip: 'Take a Video', + child: const Icon(Icons.videocam), + ), + ), + ], + ), + ); + } + + Text? _getRetrieveErrorWidget() { + if (_retrieveDataError != null) { + final Text result = Text(_retrieveDataError!); + _retrieveDataError = null; + return result; + } + return null; + } + + Future _displayPickImageDialog( + BuildContext context, OnPickImageCallback onPick) async { + return showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Add optional parameters'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + 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'), + ), + ], + ), + actions: [ + TextButton( + child: const Text('CANCEL'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('PICK'), + onPressed: () { + final double? width = maxWidthController.text.isNotEmpty + ? double.parse(maxWidthController.text) + : null; + final double? height = maxHeightController.text.isNotEmpty + ? double.parse(maxHeightController.text) + : null; + final int? quality = qualityController.text.isNotEmpty + ? int.parse(qualityController.text) + : null; + onPick(width, height, quality); + Navigator.of(context).pop(); + }), + ], + ); + }); + } +} + +typedef OnPickImageCallback = void Function( + double? maxWidth, double? maxHeight, int? quality); + +class AspectRatioVideo extends StatefulWidget { + const AspectRatioVideo(this.controller, {super.key}); + + final VideoPlayerController? controller; + + @override + AspectRatioVideoState createState() => AspectRatioVideoState(); +} + +class AspectRatioVideoState extends State { + VideoPlayerController? get controller => widget.controller; + bool initialized = false; + + void _onVideoControllerUpdate() { + if (!mounted) { + return; + } + if (initialized != controller!.value.isInitialized) { + initialized = controller!.value.isInitialized; + setState(() {}); + } + } + + @override + void initState() { + super.initState(); + controller!.addListener(_onVideoControllerUpdate); + } + + @override + void dispose() { + controller!.removeListener(_onVideoControllerUpdate); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (initialized) { + return Center( + child: AspectRatio( + aspectRatio: controller!.value.aspectRatio, + child: VideoPlayer(controller!), + ), + ); + } else { + return Container(); + } + } +} diff --git a/app_petty_cash/lib/screen/report/report_screen.dart b/app_petty_cash/lib/screen/report/report_screen.dart index 75318aa..cf8b5c5 100644 --- a/app_petty_cash/lib/screen/report/report_screen.dart +++ b/app_petty_cash/lib/screen/report/report_screen.dart @@ -254,149 +254,140 @@ class ReportScreen extends HookConsumerWidget { ), ), ), - bottomNavigationBar: Padding( - padding: EdgeInsets.only( - // right: Constant.getActualXPhone(context: context, x: 27), - // left: Constant.getActualXPhone(context: context, x: 27), - // bottom: Constant.getActualYPhone(context: context, y: 32), - top: Constant.getActualYPhone(context: context, y: 10), - ), - child: BottomAppBar( - child: Container( - height: 150, - child: Padding( - padding: EdgeInsets.only( + bottomNavigationBar: BottomAppBar( + height: 150, + child: Container( + child: Padding( + padding: EdgeInsets.only( // right: Constant.getActualXPhone(context: context, x: 27), // left: Constant.getActualXPhone(context: context, x: 27), // bottom: Constant.getActualYPhone(context: context, y: 32), - top: Constant.getActualYPhone(context: context, y: 24), - ), - child: Column( - children: [ - // Excel - Container( - width: Constant.getActualXPhone(context: context, x: 336), - height: Constant.getActualYPhone(context: context, y: 42), - child: ElevatedButton( - style: ButtonStyle( - backgroundColor: MaterialStateColor.resolveWith( - (states) => Colors.white), - // side: MaterialStateBorderSide.resolveWith( - // (states) => BorderSide(color: Colors.green), - // ), + // top: Constant.getActualYPhone(context: context, y: 24), + ), + child: Column( + children: [ + // Excel + Container( + width: Constant.getActualXPhone(context: context, x: 336), + height: Constant.getActualYPhone(context: context, y: 42), + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: MaterialStateColor.resolveWith( + (states) => Colors.white), + // side: MaterialStateBorderSide.resolveWith( + // (states) => BorderSide(color: Colors.green), + // ), - // backgroundColor: MaterialStateColor.resolveWith( - // (st) => Constant.pcBtnBackgroundColor), - shape: - MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: BorderSide( - color: Colors.green, - ), + // backgroundColor: MaterialStateColor.resolveWith( + // (st) => Constant.pcBtnBackgroundColor), + shape: + MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: BorderSide( + color: Colors.green, ), ), - shadowColor: - MaterialStateProperty.all(Color(0xffff48423d)), - elevation: MaterialStateProperty.all(4.0), ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - width: Constant.getActualXPhone( - context: context, x: 16), - height: Constant.getActualYPhone( - context: context, y: 16), - // decoration: BoxDecoration(color: Colors.grey), - child: Image.asset( - "images/logo_excel.png", - fit: BoxFit.fill, - // scale: 1, - ), + shadowColor: + MaterialStateProperty.all(Color(0xffff48423d)), + elevation: MaterialStateProperty.all(4.0), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: Constant.getActualXPhone( + context: context, x: 16), + height: Constant.getActualYPhone( + context: context, y: 16), + // decoration: BoxDecoration(color: Colors.grey), + child: Image.asset( + "images/logo_excel.png", + fit: BoxFit.fill, + // scale: 1, ), - SizedBox( - height: Constant.getActualXPhone( - context: context, x: 8), + ), + SizedBox( + height: Constant.getActualXPhone( + context: context, x: 8), + ), + Text( + 'Download Report (xls) ', + style: Constant.body1(context: context).copyWith( + fontWeight: FontWeight.w600, + color: Colors.green, ), - Text( - 'Download Report (xls) ', - style: Constant.body1(context: context).copyWith( + ), + ], + ), + onPressed: () {}, + ), + ), + + SizedBox( + height: Constant.getActualYPhone(context: context, y: 20), + ), + + // PDF + Container( + width: Constant.getActualXPhone(context: context, x: 336), + height: Constant.getActualYPhone(context: context, y: 42), + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: MaterialStateColor.resolveWith( + (st) => Constant.pcBtnBackgroundColor), + shape: + MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: BorderSide( + color: Constant.pcBtnBackgroundColor, + ), + ), + ), + shadowColor: + MaterialStateProperty.all(Color(0xffff48423d)), + elevation: MaterialStateProperty.all(4.0), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: Constant.getActualXPhone( + context: context, x: 16), + height: Constant.getActualYPhone( + context: context, y: 16), + // decoration: BoxDecoration(color: Colors.grey), + child: Image.asset( + "images/logo_pdf.png", + fit: BoxFit.fill, + // scale: 1, + ), + ), + SizedBox( + height: Constant.getActualXPhone( + context: context, x: 8), + ), + Text( + 'Download Report (PDF)', + style: Constant.body1(context: context).copyWith( fontWeight: FontWeight.w600, - color: Colors.green, - ), - ), - ], - ), - onPressed: () {}, - ), - ), - - SizedBox( - height: Constant.getActualYPhone(context: context, y: 20), - ), - - // PDF - Container( - width: Constant.getActualXPhone(context: context, x: 336), - height: Constant.getActualYPhone(context: context, y: 42), - child: ElevatedButton( - style: ButtonStyle( - backgroundColor: MaterialStateColor.resolveWith( - (st) => Constant.pcBtnBackgroundColor), - shape: - MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: BorderSide( - color: Constant.pcBtnBackgroundColor, - ), - ), + color: Constant.white), ), - shadowColor: - MaterialStateProperty.all(Color(0xffff48423d)), - elevation: MaterialStateProperty.all(4.0), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - width: Constant.getActualXPhone( - context: context, x: 16), - height: Constant.getActualYPhone( - context: context, y: 16), - // decoration: BoxDecoration(color: Colors.grey), - child: Image.asset( - "images/logo_pdf.png", - fit: BoxFit.fill, - // scale: 1, - ), - ), - SizedBox( - height: Constant.getActualXPhone( - context: context, x: 8), - ), - Text( - 'Download Report (PDF)', - style: Constant.body1(context: context).copyWith( - fontWeight: FontWeight.w600, - color: Constant.white), - ), - ], - ), - onPressed: () async { - String url = - "https://pub.dev/packages?q=url+launcher"; - if (!await launchUrl(Uri.parse(url))) { - // throw Exception('Could not launch $url'); - SanckbarWidget(context, 'Could not launch $url', - snackbarType.error); - } - }, + ], ), + onPressed: () async { + String url = "https://pub.dev/packages?q=url+launcher"; + if (!await launchUrl(Uri.parse(url))) { + // throw Exception('Could not launch $url'); + SanckbarWidget(context, 'Could not launch $url', + snackbarType.error); + } + }, ), - ], - ), + ), + ], ), ), ), diff --git a/app_petty_cash/lib/widget/custom_drawer.dart b/app_petty_cash/lib/widget/custom_drawer.dart index 2c352b6..bfbfc94 100644 --- a/app_petty_cash/lib/widget/custom_drawer.dart +++ b/app_petty_cash/lib/widget/custom_drawer.dart @@ -102,7 +102,28 @@ class CustomDrawer extends HookConsumerWidget { // Handle navigation to Home screen Navigator.pop(context); ref.read(currentPageProvider.state).update((state) => 0); - Navigator.pushNamed(context, homeRoute); + // Navigator.pushNamed(context, homeRoute); + Navigator.pushNamed(context, cameraExampleRoute); + }, + ), + ListTile( + title: Text( + 'Coba Camera', + style: TextStyle( + color: (currentMenu == 10) + ? Constant.textWhite + : Constant.textBlack, + ), + ), + tileColor: (currentMenu == 10) + ? Constant.pcBtnBackgroundColor + : Colors.transparent, + onTap: () { + // Handle navigation to Home screen + Navigator.pop(context); + ref.read(currentPageProvider.state).update((state) => 10); + // Navigator.pushNamed(context, homeRoute); + Navigator.pushNamed(context, cobaCameraRoute); }, ), ListTile( diff --git a/app_petty_cash/linux/flutter/generated_plugin_registrant.cc b/app_petty_cash/linux/flutter/generated_plugin_registrant.cc index f6f23bf..7299b5c 100644 --- a/app_petty_cash/linux/flutter/generated_plugin_registrant.cc +++ b/app_petty_cash/linux/flutter/generated_plugin_registrant.cc @@ -6,9 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/app_petty_cash/linux/flutter/generated_plugins.cmake b/app_petty_cash/linux/flutter/generated_plugins.cmake index f16b4c3..786ff5c 100644 --- a/app_petty_cash/linux/flutter/generated_plugins.cmake +++ b/app_petty_cash/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_linux url_launcher_linux ) diff --git a/app_petty_cash/macos/Flutter/GeneratedPluginRegistrant.swift b/app_petty_cash/macos/Flutter/GeneratedPluginRegistrant.swift index 997e35d..a7158a1 100644 --- a/app_petty_cash/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/app_petty_cash/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,10 +5,16 @@ import FlutterMacOS import Foundation +import file_selector_macos +import path_provider_foundation import shared_preferences_foundation import url_launcher_macos +import video_player_avfoundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) } diff --git a/app_petty_cash/pubspec.lock b/app_petty_cash/pubspec.lock index 4813d03..ec6781f 100644 --- a/app_petty_cash/pubspec.lock +++ b/app_petty_cash/pubspec.lock @@ -65,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e + url: "https://pub.dev" + source: hosted + version: "0.3.3+8" crypto: dependency: transitive description: @@ -73,6 +81,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + csslib: + dependency: transitive + description: + name: csslib + sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" + url: "https://pub.dev" + source: hosted + version: "1.0.0" cupertino_icons: dependency: "direct main" description: @@ -145,6 +161,38 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.1" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + url: "https://pub.dev" + source: hosted + version: "0.9.2+1" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: b15c3da8bd4908b9918111fa486903f5808e388b8d1c559949f584725a6594d6 + url: "https://pub.dev" + source: hosted + version: "0.9.3+3" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0 + url: "https://pub.dev" + source: hosted + version: "0.9.3+1" flutter: dependency: "direct main" description: flutter @@ -208,6 +256,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + html: + dependency: transitive + description: + name: html + sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" + url: "https://pub.dev" + source: hosted + version: "0.15.4" + http: + dependency: transitive + description: + name: http + sha256: d4872660c46d929f6b8a9ef4e7a7eff7e49bbf0c4ec3f385ee32df5119175139 + url: "https://pub.dev" + source: hosted + version: "1.1.2" http_parser: dependency: transitive description: @@ -216,6 +280,70 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "26222b01a0c9a2c8fe02fc90b8208bd3325da5ed1f4a2acabf75939031ac0bdd" + url: "https://pub.dev" + source: hosted + version: "1.0.7" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "39f2bfe497e495450c81abcd44b62f56c2a36a37a175da7d137b4454977b51b1" + url: "https://pub.dev" + source: hosted + version: "0.8.9+3" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: e2423c53a68b579a7c37a1eda967b8ae536c3d98518e5db95ca1fe5719a730a3 + url: "https://pub.dev" + source: hosted + version: "3.0.2" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: fadafce49e8569257a0cad56d24438a6fa1f0cbd7ee0af9b631f7492818a4ca3 + url: "https://pub.dev" + source: hosted + version: "0.8.9+1" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: fa4e815e6fcada50e35718727d83ba1c92f1edf95c0b4436554cec301b56233b + url: "https://pub.dev" + source: hosted + version: "2.9.3" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" intl: dependency: "direct main" description: @@ -256,6 +384,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + mime: + dependency: "direct main" + description: + name: mime + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + url: "https://pub.dev" + source: hosted + version: "1.0.4" open_file: dependency: "direct main" description: @@ -272,6 +408,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.3" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + url: "https://pub.dev" + source: hosted + version: "2.3.2" path_provider_linux: dependency: transitive description: @@ -336,6 +496,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.3" + photo_view: + dependency: "direct main" + description: + name: photo_view + sha256: "8036802a00bae2a78fc197af8a158e3e2f7b500561ed23b4c458107685e645bb" + url: "https://pub.dev" + source: hosted + version: "0.14.0" platform: dependency: transitive description: @@ -565,6 +733,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + video_player: + dependency: "direct main" + description: + name: video_player + sha256: fbf28ce8bcfe709ad91b5789166c832cb7a684d14f571a81891858fefb5bb1c2 + url: "https://pub.dev" + source: hosted + version: "2.8.2" + video_player_android: + dependency: transitive + description: + name: video_player_android + sha256: "7f8f25d7ad56819a82b2948357f3c3af071f6a678db33833b26ec36bbc221316" + url: "https://pub.dev" + source: hosted + version: "2.4.11" + video_player_avfoundation: + dependency: transitive + description: + name: video_player_avfoundation + sha256: "309e3962795e761be010869bae65c0b0e45b5230c5cee1bec72197ca7db040ed" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + sha256: "236454725fafcacf98f0f39af0d7c7ab2ce84762e3b63f2cbb3ef9a7e0550bc6" + url: "https://pub.dev" + source: hosted + version: "6.2.2" + video_player_web: + dependency: transitive + description: + name: video_player_web + sha256: "34beb3a07d4331a24f7e7b2f75b8e2b103289038e07e65529699a671b6a6e2cb" + url: "https://pub.dev" + source: hosted + version: "2.1.3" web: dependency: transitive description: @@ -590,5 +798,5 @@ packages: source: hosted version: "1.0.4" sdks: - dart: ">=3.2.0-194.0.dev <4.0.0" - flutter: ">=3.10.0" + dart: ">=3.2.0 <4.0.0" + flutter: ">=3.16.0" diff --git a/app_petty_cash/pubspec.yaml b/app_petty_cash/pubspec.yaml index 93edb0c..c8047c0 100644 --- a/app_petty_cash/pubspec.yaml +++ b/app_petty_cash/pubspec.yaml @@ -50,6 +50,11 @@ dependencies: flutter_multi_formatter: ^2.12.4 top_snackbar_flutter: ^3.1.0 url_launcher: ^6.1.13 + image_picker: ^1.0.7 + video_player: ^2.7.2 + photo_view: ^0.14.0 + mime: ^1.0.4 + path_provider: ^2.1.2 dev_dependencies: flutter_test: diff --git a/app_petty_cash/windows/flutter/generated_plugin_registrant.cc b/app_petty_cash/windows/flutter/generated_plugin_registrant.cc index a0d0bbe..4e586f9 100644 --- a/app_petty_cash/windows/flutter/generated_plugin_registrant.cc +++ b/app_petty_cash/windows/flutter/generated_plugin_registrant.cc @@ -6,10 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/app_petty_cash/windows/flutter/generated_plugins.cmake b/app_petty_cash/windows/flutter/generated_plugins.cmake index c20a586..1119879 100644 --- a/app_petty_cash/windows/flutter/generated_plugins.cmake +++ b/app_petty_cash/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_windows permission_handler_windows url_launcher_windows ) From 73cbde7f3e0e3b53b090a456e0007186a9650822 Mon Sep 17 00:00:00 2001 From: Sas Andy Date: Tue, 16 Jan 2024 16:39:29 +0700 Subject: [PATCH 3/3] penggabungan browse file & pick picture dan pemecahan file --- .../transaksi/insert_transaksi_provider.dart | 1 - .../transaksi/transaksi_pick_date_widget.dart | 100 ++++++ .../screen/transaksi/transaksi_screen.dart | 336 ++++++------------ .../transaksi_select_kategori_widget.dart | 117 ++++++ .../transaksi_upload_area_widget.dart | 162 +++++++++ app_petty_cash/lib/widget/custom_drawer.dart | 4 +- 6 files changed, 497 insertions(+), 223 deletions(-) create mode 100644 app_petty_cash/lib/screen/transaksi/transaksi_pick_date_widget.dart create mode 100644 app_petty_cash/lib/screen/transaksi/transaksi_select_kategori_widget.dart create mode 100644 app_petty_cash/lib/screen/transaksi/transaksi_upload_area_widget.dart diff --git a/app_petty_cash/lib/screen/transaksi/insert_transaksi_provider.dart b/app_petty_cash/lib/screen/transaksi/insert_transaksi_provider.dart index c38e916..e9a741d 100644 --- a/app_petty_cash/lib/screen/transaksi/insert_transaksi_provider.dart +++ b/app_petty_cash/lib/screen/transaksi/insert_transaksi_provider.dart @@ -1,4 +1,3 @@ - import 'package:equatable/equatable.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/app_petty_cash/lib/screen/transaksi/transaksi_pick_date_widget.dart b/app_petty_cash/lib/screen/transaksi/transaksi_pick_date_widget.dart new file mode 100644 index 0000000..f9e4641 --- /dev/null +++ b/app_petty_cash/lib/screen/transaksi/transaksi_pick_date_widget.dart @@ -0,0 +1,100 @@ +import 'package:app_petty_cash/app/constant.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +class TransaksiPickDateWidget extends StatelessWidget { + const TransaksiPickDateWidget({ + super.key, + required this.ctrlTglAwal, + required this.tglAwal, + required this.tglAwalTmp, + }); + + final TextEditingController ctrlTglAwal; + final ValueNotifier tglAwal; + final ValueNotifier tglAwalTmp; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: TextField( + readOnly: true, + controller: ctrlTglAwal, + decoration: InputDecoration( + hintStyle: Constant.body2_400(context: context).copyWith( + color: Constant.textGreyv2, + ), + labelStyle: Constant.body2_400(context: context).copyWith( + color: Constant.textGreyv2, + ), + border: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.orange, + width: 1, + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Constant.textGreyv2, + width: 1, + ), + ), + + // labelText: "Tanggal Awal", + hintText: 'Tanggal Transaksi', + // suffixIcon: isLoadingFilterScope.value + // ? SizedBox( + // width: Constant.getActualXPhone( + // context: context, + // x: 4, + // ), + // height: Constant.getActualYPhone( + // context: context, + // y: 4, + // ), + // child: CircularProgressIndicator( + // color: Constant.textRed, + // ), + // ) + // : Icon( + // Icons.calendar_month_sharp, + // color: Constant.colorIconDate, + // ), + ), + onTap: () async { + final selectedDateAwal = await showDatePicker( + keyboardType: TextInputType.none, + // locale: const Locale("en-CA"), + // locale: , + context: context, + initialEntryMode: DatePickerEntryMode.calendarOnly, + firstDate: DateTime(2000), + lastDate: DateTime(2100), + + initialDate: + (ctrlTglAwal.text.isEmpty) ? DateTime.now() : tglAwal.value, + ); + + if (selectedDateAwal != null) { + String formattedDate = + DateFormat('dd-MM-yyyy').format(selectedDateAwal); + // ctrlTglAwal.text = + // selectedDateAwal.toString().split(' ')[0]; + ctrlTglAwal.text = formattedDate; + tglAwal.value = selectedDateAwal; + tglAwalTmp.value = selectedDateAwal.toString(); + } + + if (selectedDateAwal == null) { + print('cancel button'); + return; + } + }, + ), + ), + ], + ); + } +} diff --git a/app_petty_cash/lib/screen/transaksi/transaksi_screen.dart b/app_petty_cash/lib/screen/transaksi/transaksi_screen.dart index 3dfedab..e3158d4 100644 --- a/app_petty_cash/lib/screen/transaksi/transaksi_screen.dart +++ b/app_petty_cash/lib/screen/transaksi/transaksi_screen.dart @@ -1,15 +1,25 @@ +import 'dart:convert'; +import 'dart:io' as io; +import 'dart:io'; + import 'package:app_petty_cash/app/app_extension.dart'; import 'package:app_petty_cash/app/route.dart'; import 'package:app_petty_cash/model/list_type_model.dart'; import 'package:app_petty_cash/screen/transaksi/insert_transaksi_provider.dart'; import 'package:app_petty_cash/screen/transaksi/list_category_provider.dart'; import 'package:app_petty_cash/screen/transaksi/list_type_provider.dart'; +import 'package:app_petty_cash/screen/transaksi/transaksi_pick_date_widget.dart'; +import 'package:app_petty_cash/screen/transaksi/transaksi_select_kategori_widget.dart'; +import 'package:app_petty_cash/screen/transaksi/transaksi_upload_area_widget.dart'; import 'package:dropdown_button2/dropdown_button2.dart'; +import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:intl/intl.dart'; +import 'package:mime/mime.dart'; import '../../app/constant.dart'; import '../../model/list_category_model.dart'; @@ -43,6 +53,10 @@ class TransaksiScreen extends HookConsumerWidget { final ctrlJumlah = useTextEditingController(text: ""); final ctrlCatatan = useTextEditingController(text: ""); final ctrlNamaPengirim = useTextEditingController(text: ""); + final fileData = useState(null); + final fileDataBase64 = useState(""); + final isImage = useState(false); + final fileEkstension = useState(""); String formattedDate = DateFormat('dd-MM-yyyy').format(DateTime.now()); @@ -178,6 +192,95 @@ class TransaksiScreen extends HookConsumerWidget { final userIDLogin = ref.read(currentUserProvider)?.model.M_UserID ?? "0"; + getBase64() async { + // List imageBytes = await fileData.value?.readAsBytes(); + if (fileData.value != null) { + final bytes = io.File(fileData.value!.path).readAsBytesSync(); + String base64Image = base64Encode(bytes); + fileDataBase64.value = base64Image; + final file = File(fileData.value!.path); + print(file.lengthSync() / 1000000); + print(await fileData.value!.length()); + + // await getExternalStorageDirectory(); + // print(await fileData.value!.saveTo(path)); + print(base64Image); + } + } + + final ImagePicker _picker = ImagePicker(); + pickImage() async { + final XFile? pickedFile = await _picker.pickImage( + source: ImageSource.camera, + ); + if (pickedFile != null) { + if (await pickedFile.length() > 10000000) { + SanckbarWidget(context, "File tidak boleh lebih dari 10 MB", + snackbarType.warning); + } else { + fileData.value = pickedFile; + isImage.value = true; + } + // final Directory appDocumentsDir = + // await getApplicationDocumentsDirectory(); + // print(appDocumentsDir); + // await pickedFile!.saveTo(appDocumentsDir.path); + await getBase64(); + } + } + + browseImage() async { + final XFile? pickedFile = await _picker.pickImage( + source: ImageSource.gallery, + ); + fileData.value = pickedFile; + getBase64(); + } + + pickFile() async { + List imgExt = [ + 'jpg', + 'png', + 'jpeg', + ]; + FilePickerResult? result = await FilePicker.platform.pickFiles( + allowMultiple: false, + type: FileType.custom, + dialogTitle: "Pick a file", + allowedExtensions: [ + ...imgExt, + 'pdf', + 'doc', + 'docx', + 'xls', + 'xlsx', + 'ppt', + 'pptx', + 'txt' + ], + ); + + if (result != null) { + if (result.files.single.size > 10000000) { + SanckbarWidget(context, "File tidak boleh lebih dari 10 MB", + snackbarType.warning); + } else { + File files = File(result.files.single.path!); + print(result.files.single.extension); + XFile fl = XFile(result.files.single.path!); + + isImage.value = imgExt.contains(result.files.single.extension); + + print("ini xfile"); + fileData.value = fl; + await getBase64(); + fileEkstension.value = result.files.single.extension ?? ""; + } + } else { + // User canceled the picker + } + } + return Padding( padding: EdgeInsets.only( top: Constant.getActualYPhone(context: context, y: 30), @@ -211,87 +314,10 @@ class TransaksiScreen extends HookConsumerWidget { height: Constant.getActualYPhone(context: context, y: 10), ), // Tanggal Transaksi - Row( - children: [ - Expanded( - child: TextField( - controller: ctrlTglAwal, - decoration: InputDecoration( - hintStyle: - Constant.body2_400(context: context).copyWith( - color: Constant.textGreyv2, - ), - labelStyle: - Constant.body2_400(context: context).copyWith( - color: Constant.textGreyv2, - ), - border: OutlineInputBorder( - borderSide: BorderSide( - color: Colors.orange, - width: 1, - ), - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Constant.textGreyv2, - width: 1, - ), - ), - // labelText: "Tanggal Awal", - hintText: 'Tanggal Transaksi', - // suffixIcon: isLoadingFilterScope.value - // ? SizedBox( - // width: Constant.getActualXPhone( - // context: context, - // x: 4, - // ), - // height: Constant.getActualYPhone( - // context: context, - // y: 4, - // ), - // child: CircularProgressIndicator( - // color: Constant.textRed, - // ), - // ) - // : Icon( - // Icons.calendar_month_sharp, - // color: Constant.colorIconDate, - // ), - ), - onTap: () async { - final selectedDateAwal = await showDatePicker( - // locale: const Locale("en-CA"), - // locale: , - context: context, - initialEntryMode: - DatePickerEntryMode.calendarOnly, - firstDate: DateTime(2000), - lastDate: DateTime(2100), - - initialDate: (ctrlTglAwal.text.isEmpty) - ? DateTime.now() - : tglAwal.value, - ); - - if (selectedDateAwal != null) { - String formattedDate = DateFormat('dd-MM-yyyy') - .format(selectedDateAwal); - // ctrlTglAwal.text = - // selectedDateAwal.toString().split(' ')[0]; - ctrlTglAwal.text = formattedDate; - tglAwal.value = selectedDateAwal; - tglAwalTmp.value = selectedDateAwal.toString(); - } - - if (selectedDateAwal == null) { - print('cancel button'); - return; - } - }, - ), - ), - ], - ), + TransaksiPickDateWidget( + ctrlTglAwal: ctrlTglAwal, + tglAwal: tglAwal, + tglAwalTmp: tglAwalTmp), SizedBox( height: Constant.getActualYPhone(context: context, y: 20), @@ -390,124 +416,9 @@ class TransaksiScreen extends HookConsumerWidget { child: CircularProgressIndicator(), ), ) - : SizedBox( - width: Constant.getActualXPhone( - context: context, x: 390), - child: DropdownButtonHideUnderline( - child: DropdownButton2( - isExpanded: true, - hint: Row( - children: [ - Expanded( - child: Text( - 'Select Item', - style: Constant.body1(context: context) - .copyWith( - fontWeight: FontWeight.w600, - color: Constant.textBlack), - overflow: TextOverflow.ellipsis, - ), - ), - ], - ), - items: listCategoryData.value - .map((ListCategory option) { - return DropdownMenuItem( - value: option, - child: Text( - option.categoryname ?? "", - style: Constant.body1(context: context) - .copyWith( - color: Constant.textBlack, - fontWeight: FontWeight.w600), - ), - ); - }).toList(), - value: selectedListCategory.value, - onChanged: (ListCategory? newValue) { - // if (newValue) { - selectedListCategory.value = newValue!; - print(selectedListCategory.value.categoryid); - // } - }, - buttonStyleData: ButtonStyleData( - height: Constant.getActualY( - context: context, y: 56), - width: Constant.getActualX( - context: context, x: 320), - padding: EdgeInsets.only( - left: Constant.getActualX( - context: context, x: 10), - right: Constant.getActualX( - context: context, x: 10), - ), - decoration: BoxDecoration( - color: Constant.white, - border: Border.all( - color: Constant.textBlack, width: 1), - borderRadius: BorderRadius.circular(8), - ), - elevation: 2, - ), - iconStyleData: IconStyleData( - icon: Icon( - Icons.keyboard_arrow_down_outlined, - ), - iconSize: 24, - iconEnabledColor: Constant.textBlack, - iconDisabledColor: Colors.grey, - ), - dropdownStyleData: DropdownStyleData( - maxHeight: Constant.getActualY( - context: context, y: 200), - // width: Constant.getActualX(context: context, x: 320), - padding: EdgeInsets.only( - top: Constant.getActualY( - context: context, y: 10), - left: Constant.getActualX( - context: context, x: 10), - right: Constant.getActualX( - context: context, x: 10), - bottom: Constant.getActualY( - context: context, y: 10), - ), - decoration: BoxDecoration( - color: Constant.white, - borderRadius: BorderRadius.circular(8), - boxShadow: [ - BoxShadow( - color: Colors.grey, - blurRadius: 20.0, - spreadRadius: 2.0, - offset: Offset(0.0, 0.0), - ), - ], - ), - elevation: 8, - offset: const Offset(0, -10), - scrollbarTheme: ScrollbarThemeData( - radius: const Radius.circular(40), - thickness: - MaterialStateProperty.all(6), - thumbVisibility: - MaterialStateProperty.all(true), - ), - ), - menuItemStyleData: MenuItemStyleData( - height: Constant.getActualY( - context: context, y: 56), - padding: EdgeInsets.only( - top: Constant.getActualY( - context: context, y: 10), - left: Constant.getActualX( - context: context, x: 10), - right: Constant.getActualX( - context: context, x: 10), - ), - ), - ), - ), - ), + : TransaksiSelectKategoriWidget( + listCategoryData: listCategoryData, + selectedListCategory: selectedListCategory), SizedBox( height: Constant.getActualYPhone(context: context, y: 20), @@ -639,27 +550,12 @@ class TransaksiScreen extends HookConsumerWidget { ), // Upload File - Container( - width: Constant.getActualXPhone(context: context, x: 390), - height: Constant.getActualYPhone(context: context, y: 83), - decoration: BoxDecoration(color: Constant.bgUploadFile), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Icon( - Icons.upload_outlined, - color: Constant.pcBtnBackgroundColor, - ), - Text( - 'Upload File', - style: Constant.body1(context: context).copyWith( - fontWeight: FontWeight.w600, - color: Constant.pcBtnBackgroundColor), - ) - ], - ), - ), + TransaksiUploadAreaWidget( + isImage: isImage, + fileData: fileData, + fileDataBase64: fileDataBase64, + pickFile: pickFile, + pickImage: pickImage), ], ), ), diff --git a/app_petty_cash/lib/screen/transaksi/transaksi_select_kategori_widget.dart b/app_petty_cash/lib/screen/transaksi/transaksi_select_kategori_widget.dart new file mode 100644 index 0000000..4b609ae --- /dev/null +++ b/app_petty_cash/lib/screen/transaksi/transaksi_select_kategori_widget.dart @@ -0,0 +1,117 @@ +import 'package:app_petty_cash/app/constant.dart'; +import 'package:app_petty_cash/model/list_category_model.dart'; +import 'package:dropdown_button2/dropdown_button2.dart'; +import 'package:flutter/material.dart'; + +class TransaksiSelectKategoriWidget extends StatelessWidget { + const TransaksiSelectKategoriWidget({ + super.key, + required this.listCategoryData, + required this.selectedListCategory, + }); + + final ValueNotifier> listCategoryData; + final ValueNotifier selectedListCategory; + + @override + Widget build(BuildContext context) { + return SizedBox( + width: Constant.getActualXPhone(context: context, x: 390), + child: DropdownButtonHideUnderline( + child: DropdownButton2( + isExpanded: true, + hint: Row( + children: [ + Expanded( + child: Text( + 'Select Item', + style: Constant.body1(context: context).copyWith( + fontWeight: FontWeight.w400, color: Constant.textBlack), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + items: listCategoryData.value.map((ListCategory option) { + return DropdownMenuItem( + value: option, + child: Text( + option.categoryname ?? "", + style: Constant.body1(context: context).copyWith( + color: Constant.textBlack, fontWeight: FontWeight.w400), + ), + ); + }).toList(), + style: Constant.body1(context: context) + .copyWith(color: Constant.textBlack, fontWeight: FontWeight.w400), + value: selectedListCategory.value, + onChanged: (ListCategory? newValue) { + // if (newValue) { + selectedListCategory.value = newValue!; + print(selectedListCategory.value.categoryid); + // } + }, + buttonStyleData: ButtonStyleData( + height: Constant.getActualY(context: context, y: 80), + width: Constant.getActualX(context: context, x: 320), + padding: EdgeInsets.only( + left: Constant.getActualX(context: context, x: 20), + right: Constant.getActualX(context: context, x: 20), + ), + decoration: BoxDecoration( + color: Constant.white, + border: Border.all(color: Constant.textGrey, width: 1), + borderRadius: BorderRadius.circular(8), + ), + // elevation: 2, + ), + iconStyleData: IconStyleData( + icon: Icon( + Icons.keyboard_arrow_down_outlined, + ), + iconSize: 24, + iconEnabledColor: Constant.textBlack, + iconDisabledColor: Colors.grey, + ), + dropdownStyleData: DropdownStyleData( + maxHeight: Constant.getActualY(context: context, y: 200), + // width: Constant.getActualX(context: context, x: 320), + padding: EdgeInsets.only( + top: Constant.getActualY(context: context, y: 10), + left: Constant.getActualX(context: context, x: 20), + right: Constant.getActualX(context: context, x: 20), + bottom: Constant.getActualY(context: context, y: 10), + ), + decoration: BoxDecoration( + color: Constant.white, + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: Colors.grey, + blurRadius: 20.0, + spreadRadius: 2.0, + offset: Offset(0.0, 0.0), + ), + ], + ), + elevation: 8, + offset: const Offset(0, -10), + scrollbarTheme: ScrollbarThemeData( + radius: const Radius.circular(40), + thickness: MaterialStateProperty.all(6), + thumbVisibility: MaterialStateProperty.all(true), + ), + ), + menuItemStyleData: MenuItemStyleData( + height: Constant.getActualY(context: context, y: 56), + padding: EdgeInsets.only( + top: Constant.getActualY(context: context, y: 10), + left: Constant.getActualX(context: context, x: 20), + right: Constant.getActualX(context: context, x: 20), + ), + ), + ), + ), + ); + } +} diff --git a/app_petty_cash/lib/screen/transaksi/transaksi_upload_area_widget.dart b/app_petty_cash/lib/screen/transaksi/transaksi_upload_area_widget.dart new file mode 100644 index 0000000..03264fd --- /dev/null +++ b/app_petty_cash/lib/screen/transaksi/transaksi_upload_area_widget.dart @@ -0,0 +1,162 @@ +import 'dart:io'; + +import 'package:app_petty_cash/app/constant.dart'; +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:mime/mime.dart'; + +class TransaksiUploadAreaWidget extends StatelessWidget { + const TransaksiUploadAreaWidget({ + super.key, + required this.isImage, + required this.fileData, + required this.fileDataBase64, + required this.pickFile, + required this.pickImage, + }); + + final ValueNotifier isImage; + final ValueNotifier fileData; + final ValueNotifier fileDataBase64; + final Function pickImage; + final Function pickFile; + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () { + print("tapped"); + showModalBottomSheet( + context: context, + builder: (context) { + return Container( + height: Constant.getActualY(context: context, y: 200), + padding: EdgeInsets.all(20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Column( + children: [ + IconButton( + onPressed: () { + Navigator.pop(context); + pickFile(); + }, + icon: Icon( + Icons.folder_copy_rounded, + size: 50, + color: Constant.pcBtnBackgroundColor, + )), + Text("Browse a file", + style: Constant.body1(context: context).copyWith( + fontWeight: FontWeight.w600, + color: Constant.textBlack)) + ], + ), + Column( + children: [ + IconButton( + onPressed: () { + Navigator.pop(context); + pickImage(); + }, + icon: Icon( + Icons.add_a_photo_rounded, + size: 50, + color: Constant.pcBtnBackgroundColor, + )), + Text("Take a picture", + style: Constant.body1(context: context).copyWith( + fontWeight: FontWeight.w600, + color: Constant.textBlack)) + ], + ), + ], + ), + ); + }, + ); + }, + child: Stack( + alignment: AlignmentDirectional.topEnd, + children: [ + Container( + width: Constant.getActualXPhone(context: context, x: 390), + height: Constant.getActualYPhone( + context: context, y: isImage.value ? 200 : 83), + decoration: BoxDecoration(color: Constant.bgUploadFile), + child: Builder(builder: (context) { + final String? mime = lookupMimeType(fileData.value?.path ?? ""); + return Semantics( + label: 'image_picker_example_picked_image', + child: (mime != null + ? (mime.startsWith('image/')) + ? Image.file( + // image: AssetImage(photo.value!.path), + File(fileData.value!.path), + frameBuilder: (context, child, frame, + wasSynchronouslyLoaded) { + return (wasSynchronouslyLoaded) + ? Center( + child: Text("Loadinga"), + ) + : Container( + child: child, + ); + }, + errorBuilder: (BuildContext context, Object error, + StackTrace? stackTrace) { + return const Center( + child: Text( + 'This image type is not supported')); + }, + ) + : Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.file_present_rounded, + size: 40, + color: Constant.pcBtnBackgroundColor, + ), + Text( + fileData.value?.name ?? '', + style: Constant.body2_400(context: context), + ) + ], + ), + ) + : Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Icons.upload_outlined, + color: Constant.pcBtnBackgroundColor, + ), + Text( + 'Upload File', + style: Constant.body1(context: context).copyWith( + fontWeight: FontWeight.w600, + color: Constant.pcBtnBackgroundColor), + ) + ], + ))); + }), + ), + if (fileData.value != null) + IconButton( + onPressed: () { + fileData.value = null; + fileDataBase64.value = ''; + isImage.value = false; + }, + icon: Icon(Icons.cancel_outlined)), + ], + ), + ); + } +} diff --git a/app_petty_cash/lib/widget/custom_drawer.dart b/app_petty_cash/lib/widget/custom_drawer.dart index bfbfc94..c802481 100644 --- a/app_petty_cash/lib/widget/custom_drawer.dart +++ b/app_petty_cash/lib/widget/custom_drawer.dart @@ -102,8 +102,8 @@ class CustomDrawer extends HookConsumerWidget { // Handle navigation to Home screen Navigator.pop(context); ref.read(currentPageProvider.state).update((state) => 0); - // Navigator.pushNamed(context, homeRoute); - Navigator.pushNamed(context, cameraExampleRoute); + Navigator.pushNamed(context, homeRoute); + // Navigator.pushNamed(context, cameraExampleRoute); }, ), ListTile(