From 521c54dfa80427d80f98c7cff16148761dcea1f1 Mon Sep 17 00:00:00 2001 From: Golek Date: Wed, 7 Dec 2022 13:41:00 +0700 Subject: [PATCH] debug: input file --- android/app/build.gradle | 2 +- .../example/android/app/build.gradle | 2 +- .../example/lib/stories.dart | 19 ++- .../component_library/lib/src/input_file.dart | 106 ++++++++++------ .../landing_menu/lib/src/landing_page.dart | 118 ++++++------------ packages/form_fields/lib/form_fields.dart | 4 +- .../form_fields/lib/src/error_messages.dart | 13 +- .../lib/src/reactive_input_file.dart | 51 ++++++++ .../lib/src/validators/count_of_file.dart | 0 .../lib/src/validators/required_file.dart | 7 ++ .../lib/src/validators/size_of_file.dart | 21 ++++ 11 files changed, 220 insertions(+), 123 deletions(-) create mode 100644 packages/form_fields/lib/src/reactive_input_file.dart create mode 100644 packages/form_fields/lib/src/validators/count_of_file.dart create mode 100644 packages/form_fields/lib/src/validators/required_file.dart create mode 100644 packages/form_fields/lib/src/validators/size_of_file.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 646c5f0..33b5581 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion flutter.compileSdkVersion + compileSdkVersion 33 ndkVersion flutter.ndkVersion compileOptions { diff --git a/packages/component_library/example/android/app/build.gradle b/packages/component_library/example/android/app/build.gradle index 0833ecf..ef7cb5a 100644 --- a/packages/component_library/example/android/app/build.gradle +++ b/packages/component_library/example/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion flutter.compileSdkVersion + compileSdkVersion 33 ndkVersion flutter.ndkVersion compileOptions { diff --git a/packages/component_library/example/lib/stories.dart b/packages/component_library/example/lib/stories.dart index d2935f8..639e0d0 100644 --- a/packages/component_library/example/lib/stories.dart +++ b/packages/component_library/example/lib/stories.dart @@ -59,7 +59,24 @@ List getStories(GolekThemeData theme) { allowMultiple: k.boolean(label: 'allowMultiple', initial: false), onChange: (files) {}, files: [], - inputLabel: '', + isTouched: k.boolean(label: 'isTouched', initial: false), + isError: k.boolean(label: 'isError', initial: false), + errorMessage: + k.text(label: 'messageError', initial: 'Ini pesan message error'), + uploadButtonLabel: k.text( + label: 'uploadButtonLabel', + initial: 'Upload Foto\u{002A}', + ), + descriptionLabel: k.text( + label: 'descriptionLabel', + initial: 'Maks. 1 Foto, 1 Foto Maks. 2 mb', + ), + guidanceLabel: k.text( + label: 'guidanceLabel', + initial: + 'Tingkatkan kepercayaan partner bisnis dengan mengunggah foto tentang usaha' + ' anda (Contoh foto kantor, armada karyawan dll.)', + ), ), ), ]; diff --git a/packages/component_library/lib/src/input_file.dart b/packages/component_library/lib/src/input_file.dart index 967b569..c970dad 100644 --- a/packages/component_library/lib/src/input_file.dart +++ b/packages/component_library/lib/src/input_file.dart @@ -1,6 +1,6 @@ import 'dart:async'; import 'dart:io'; - +import 'package:mime/mime.dart'; import 'package:component_library/component_library.dart'; import 'package:extended_image/extended_image.dart'; import 'package:file_picker/file_picker.dart'; @@ -16,20 +16,24 @@ class InputFile extends StatefulWidget { required this.onChange, this.allowMultiple = false, this.allowedExtensions = const ['jpg', 'jpeg', 'png', 'gif'], - required this.inputLabel, + this.uploadButtonLabel, + this.guidanceLabel, + this.descriptionLabel, this.isTouched = false, this.isError = false, - this.messageError, + this.errorMessage, }) : super(key: key); final List files; final void Function(List files) onChange; final bool? allowMultiple; final List? allowedExtensions; - final String inputLabel; + final String? uploadButtonLabel; + final String? guidanceLabel; + final String? descriptionLabel; final bool? isTouched; final bool? isError; - final String? messageError; + final String? errorMessage; @override State createState() => _InputFileState(); @@ -46,9 +50,8 @@ class _InputFileState extends State { } void removeFile(File file) { - final filtered = files.skipWhile((f) => f.hashCode == file.hashCode); - controller.sink.add(filtered.toList()); - files = filtered.toList(); + files.removeAt(files.indexOf(file)); + controller.sink.add(files); } void close() { @@ -117,6 +120,9 @@ class _InputFileState extends State { @override Widget build(BuildContext context) { final theme = GolekTheme.of(context); + print("INPUT FILE"); + print(widget.isError!); + print(widget.isTouched!); return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -132,7 +138,7 @@ class _InputFileState extends State { child: IntrinsicHeight( child: DashedRect( color: widget.isError! && widget.isTouched! - ? theme.dangerMainColor // jika error dan touched + ? theme.dangerMainColor : theme.neutral50Color, strokeWidth: 2.0, gap: 3.0, @@ -141,7 +147,11 @@ class _InputFileState extends State { children: [ const SizedBox(height: 21), OutlinedButton( - onPressed: () => showModalFileToPick( + onPressed: () { + setState(() { + + }); + showModalFileToPick( context: context, onPickCamera: (context) async { await pickFileFromCamera(context); @@ -151,7 +161,8 @@ class _InputFileState extends State { await pickFileFromGallery(context); Navigator.of(context).pop(); }, - ), + ); + }, style: OutlinedButton.styleFrom( side: BorderSide( width: 1, @@ -163,14 +174,13 @@ class _InputFileState extends State { ), ), child: Text( - 'Upload Foto\u{002A}', + widget.uploadButtonLabel ?? 'Upload Foto\u{002A}', style: theme.textStyle.copyWith( fontSize: FontSize.style14, color: theme.primaryMainColor, ), ), ), - const SizedBox(height: 10), StreamBuilder( stream: stream, builder: (context, snapshot) { @@ -182,13 +192,26 @@ class _InputFileState extends State { ); }, ), + const SizedBox(height: 5), Text( - 'Maks. 1 Foto, 1 Foto Maks. 2 mb', + widget.guidanceLabel ?? 'Maks. 1 Foto, 1 Foto Maks. 2 mb', style: theme.textStyle.copyWith( fontSize: FontSize.style14, color: theme.primaryMainColor, ), ), + const SizedBox(height: 2), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 5), + child: Text( + widget.descriptionLabel ?? '', + textAlign: TextAlign.center, + style: theme.textStyle.copyWith( + fontSize: FontSize.style10, + color: theme.neutral70Color, + ), + ), + ), const SizedBox(height: 38), ], ), @@ -196,8 +219,8 @@ class _InputFileState extends State { ), ), ErrorMessageBuilder( - inputLabel: widget.inputLabel, - messageError: widget.messageError ?? '', + inputLabel: '', // ini nanti di custom di reactive component + messageError: widget.errorMessage ?? '', ) ], ); @@ -226,12 +249,14 @@ class ListFilesBuilder extends StatelessWidget { spacing: 4, alignment: WrapAlignment.center, children: List.generate(files.length, (index) { - var file = files[index]; + final file = files[index]; + final mimeType = lookupMimeType(file.path); + final isImage = mimeType?.startsWith('image/') == true; return Column(children: [ - Visibility( - visible: multiple == false, - child: Container( + const SizedBox(height: 10), + if (multiple == false) + Container( margin: const EdgeInsets.all(10), child: ClipRRect( borderRadius: BorderRadius.circular(6), @@ -245,25 +270,33 @@ class ListFilesBuilder extends StatelessWidget { ), ), ), - ), - Visibility( - visible: multiple == true, - child: Stack( + if (multiple == true) + Stack( children: [ - Container( - margin: const EdgeInsets.all(10), - child: ClipRRect( - borderRadius: BorderRadius.circular(6), - child: ExtendedImage.file( - file, - height: 105, - width: 150, - enableMemoryCache: false, - clearMemoryCacheIfFailed: true, - clearMemoryCacheWhenDispose: true, + if (isImage) + Container( + margin: const EdgeInsets.all(10), + child: ClipRRect( + borderRadius: BorderRadius.circular(6), + child: ExtendedImage.file( + file, + height: 105, + width: 150, + enableMemoryCache: false, + clearMemoryCacheIfFailed: true, + clearMemoryCacheWhenDispose: true, + ), + ), + ), + if (!isImage) + Container( + margin: const EdgeInsets.all(10), + child: Icon( + Icons.check_circle, + color: theme.primaryMainColor, + size: 50, ), ), - ), GestureDetector( onTap: () => onRemoveFile(file), child: Container( @@ -279,7 +312,6 @@ class ListFilesBuilder extends StatelessWidget { ) ], ), - ), Padding( padding: const EdgeInsets.symmetric(horizontal: 10), child: Text( diff --git a/packages/features/landing_menu/lib/src/landing_page.dart b/packages/features/landing_menu/lib/src/landing_page.dart index 5d54084..b5cebe9 100644 --- a/packages/features/landing_menu/lib/src/landing_page.dart +++ b/packages/features/landing_menu/lib/src/landing_page.dart @@ -1,5 +1,9 @@ +import 'dart:io'; + import 'package:component_library/component_library.dart'; import 'package:flutter/material.dart'; +import 'package:form_fields/form_fields.dart'; +import 'package:reactive_forms/reactive_forms.dart'; import 'landing_page/landing_app_bar.dart'; class LandingPage extends StatefulWidget { @@ -27,90 +31,48 @@ class _LandingPageState extends State { @override Widget build(BuildContext context) { final theme = GolekTheme.of(context); + final form = FormGroup({ + 'foto': FormControl>( + value: [], + validators: [requiredFile], + ), + }); return Scaffold( appBar: LandingAppBar(), body: SafeArea( - child: Stack( - children: [ - Container( - width: double.infinity, - height: 497, - decoration: BoxDecoration( - color: theme.primaryMainColor, - borderRadius: const BorderRadius.vertical( - bottom: Radius.circular(25), - ), + child: ReactiveForm( + formGroup: form, + child: Column( + children: [ + ReactiveValueListenableBuilder( + formControlName: 'foto', + builder: (context, control, child) { + print('DEBUG DI VALUE LISTEN'); + print(control.value); + print(control.touched); + print(control.errors.entries); + return Container(); + }, ), - ), - Container( - padding: const EdgeInsets.only( - right: 20, - left: 20, - bottom: 80, + ReactiveInputFile( + name: 'foto', + allowMultiple: true, ), - width: double.infinity, - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Image.asset( - 'assets/trucker_shipper.png', - package: 'features/landing_menu', - width: 300, - height: 200, - ), - const SizedBox(height: 12), - Text( - 'Cari, Muat, Jalan.\nPraktis kan ?', - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - maxLines: 2, - style: theme.textStyle.copyWith( - fontSize: FontSize.style24, - color: theme.neutral10Color, - fontWeight: FontWeight.w600, - letterSpacing: 1, - ), - ), - const SizedBox(height: 21), - GestureDetector( - onTap: () async {}, - child: Text( - 'GolekTruk memberikan kebebasan\nberlogistik menggunakan budaya\nperusahaan sendiri.', - textAlign: TextAlign.center, - style: theme.textStyle.copyWith( - fontSize: FontSize.style14, - color: theme.neutral10Color, - fontWeight: FontWeight.w500, - ), - ), - ), - SizedBox(height: 21), - Row( - children: [ - CardProduct( - onClickInfo: () => {}, - label: 'Cari Truk', - description: 'Pengirim Muatan', - assets: 'assets/box_shipper.png', - onTap: () {}, - ), - const SizedBox(width: 14), - CardProduct( - onClickInfo: () => {}, - label: 'Cari Muatan', - description: 'Penyedia Angkutan', - assets: 'assets/truk_trucker.png', - onTap: () async {}, - ), - ], - ), - ], - ), - ), - ], + ElevatedButton( + onPressed: () { + form.markAllAsTouched(); + print(form.control('foto').hasErrors); + print(form.control('foto').errors); + print(form.control('foto').touched); + if (form.valid){ + print("FORM IS VALID"); + } + }, + child: const Text('Submit'), + ) + ], + ), ), ), ); diff --git a/packages/form_fields/lib/form_fields.dart b/packages/form_fields/lib/form_fields.dart index a4d1513..38887f7 100644 --- a/packages/form_fields/lib/form_fields.dart +++ b/packages/form_fields/lib/form_fields.dart @@ -1 +1,3 @@ -export 'src/reactive_input_field.dart'; \ No newline at end of file +export 'src/reactive_input_field.dart'; +export 'src/reactive_input_file.dart'; +export 'src/validators/required_file.dart'; \ No newline at end of file diff --git a/packages/form_fields/lib/src/error_messages.dart b/packages/form_fields/lib/src/error_messages.dart index d7d797c..f740b03 100644 --- a/packages/form_fields/lib/src/error_messages.dart +++ b/packages/form_fields/lib/src/error_messages.dart @@ -16,10 +16,15 @@ class ErrorMessages { ...messages, }; - final message = mergedErrorMessages.entries.firstWhere( - (element) => element.key == control.errors.entries.first.key, - orElse: () => const MapEntry('', ''), - ); + MapEntry message = const MapEntry('', ''); + print('GET UI ERROR '); + print(control.errors); + if (control.errors.entries.isNotEmpty) { + message = mergedErrorMessages.entries.firstWhere( + (element) => element.key == control.errors.entries.first.key, + orElse: () => const MapEntry('', ''), + ); + } return message.value.toString().replaceFirst('{_field_}', label); } diff --git a/packages/form_fields/lib/src/reactive_input_file.dart b/packages/form_fields/lib/src/reactive_input_file.dart new file mode 100644 index 0000000..1c6ecdb --- /dev/null +++ b/packages/form_fields/lib/src/reactive_input_file.dart @@ -0,0 +1,51 @@ +import 'dart:io'; + +import 'package:component_library/component_library.dart'; +import 'package:flutter/material.dart'; +import 'package:reactive_forms/reactive_forms.dart'; + +import 'error_messages.dart'; + +class ReactiveInputFile extends ReactiveFormField, List> { + + ReactiveInputFile({ + required String name, + allowMultiple = false, + allowedExtensions = const ['jpg', 'jpeg', 'png', 'gif'], + uploadButtonLabel, + guidanceLabel, + descriptionLabel, + }) : super( + formControlName: name, + builder: (field) { + return InputFile( + files: field.value ?? [], + allowMultiple: allowMultiple, + descriptionLabel: descriptionLabel, + guidanceLabel: guidanceLabel, + uploadButtonLabel: uploadButtonLabel, + allowedExtensions: allowedExtensions, + errorMessage: ErrorMessages.getUiErrorMessage( + control: field.control, + label: '', + widgetCustomMessages: { + 'requiredFile': 'Foto harus diisi' + } + ), + isTouched: field.control.touched, + isError: field.control.hasErrors, + onChange: (files) { + print('ON CAHNEF'); + print(field.control.touched); + print(field.control.hasErrors); + field.didChange(files); + }, + ); + }, + ); + + @override + ReactiveFormFieldState, List> createState() { + return ReactiveFormFieldState, List>(); + } +} diff --git a/packages/form_fields/lib/src/validators/count_of_file.dart b/packages/form_fields/lib/src/validators/count_of_file.dart new file mode 100644 index 0000000..e69de29 diff --git a/packages/form_fields/lib/src/validators/required_file.dart b/packages/form_fields/lib/src/validators/required_file.dart new file mode 100644 index 0000000..03d8b71 --- /dev/null +++ b/packages/form_fields/lib/src/validators/required_file.dart @@ -0,0 +1,7 @@ +import 'package:reactive_forms/reactive_forms.dart'; + +Map? requiredFile(AbstractControl control) { + return control.isNotNull && control.value?.isNotEmpty == true + ? null + : {'requiredFile': true}; +} diff --git a/packages/form_fields/lib/src/validators/size_of_file.dart b/packages/form_fields/lib/src/validators/size_of_file.dart new file mode 100644 index 0000000..8ec75b0 --- /dev/null +++ b/packages/form_fields/lib/src/validators/size_of_file.dart @@ -0,0 +1,21 @@ +import 'package:reactive_forms/reactive_forms.dart'; + +ValidatorFunction _mustMatch(String controlName, String matchingControlName) { + return (AbstractControl control) { + final form = control as FormGroup; + + final formControl = form.control(controlName); + final matchingFormControl = form.control(matchingControlName); + + if (formControl.value != matchingFormControl.value) { + matchingFormControl.setErrors({'mustMatch': true}); + + // force messages to show up as soon as possible + matchingFormControl.markAsTouched(); + } else { + matchingFormControl.removeError('mustMatch'); + } + + return null; + }; +} \ No newline at end of file