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 be1089b..f396d67 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'; @@ -44,6 +54,10 @@ class TransaksiScreen extends HookConsumerWidget { final ctrlCatatan = useTextEditingController(text: ""); final ctrlNamaPengirim = useTextEditingController(text: ""); final ctrlCompanyName = 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()); @@ -182,6 +196,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), @@ -259,87 +362,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), @@ -441,126 +467,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: @@ -700,29 +609,14 @@ 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), - Spacer(), + // Spacer(), Container( width: Constant.getActualXPhone(context: context, x: 390), @@ -776,7 +670,7 @@ class TransaksiScreen extends HookConsumerWidget { // validasi form } } - + DateTime parsedDate = DateFormat('dd-MM-yyyy').parse( ctrlTglAwal.value.text.toString(), ); @@ -786,8 +680,8 @@ class TransaksiScreen extends HookConsumerWidget { "tgltransaksi": formattedDateTransaksi, "typeid": selectedListTypeData.value.typeid.toString(), - "categoryid": - selectedListCategory.value.categoryid.toString(), + "categoryid": selectedListCategory.value.categoryid + .toString(), "jumlah": ctrlJumlah.value.text.toString(), "catatan": ctrlCatatan.value.text.toString(), // "userid": "1", @@ -802,7 +696,8 @@ class TransaksiScreen extends HookConsumerWidget { .insertTransaksi( formattedDateTransaksi, selectedListTypeData.value.typeid.toString(), - selectedListCategory.value.categoryid.toString(), + selectedListCategory.value.categoryid + .toString(), ctrlJumlah.value.text.toString(), ctrlCatatan.value.text.toString(), // "1", 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)), + ], + ), + ); + } +}