debug: input file

This commit is contained in:
Golek 2022-12-07 13:41:00 +07:00
parent c63282dfba
commit 521c54dfa8
11 changed files with 220 additions and 123 deletions

View File

@ -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 {

View File

@ -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 {

View File

@ -59,7 +59,24 @@ List<Story> 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.)',
),
),
),
];

View File

@ -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<File> files;
final void Function(List<File> files) onChange;
final bool? allowMultiple;
final List<String>? 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<InputFile> createState() => _InputFileState();
@ -46,9 +50,8 @@ class _InputFileState extends State<InputFile> {
}
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<InputFile> {
@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<InputFile> {
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<InputFile> {
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<InputFile> {
await pickFileFromGallery(context);
Navigator.of(context).pop();
},
),
);
},
style: OutlinedButton.styleFrom(
side: BorderSide(
width: 1,
@ -163,14 +174,13 @@ class _InputFileState extends State<InputFile> {
),
),
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<InputFile> {
);
},
),
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<InputFile> {
),
),
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(

View File

@ -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<LandingPage> {
@override
Widget build(BuildContext context) {
final theme = GolekTheme.of(context);
final form = FormGroup({
'foto': FormControl<List<File>>(
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'),
)
],
),
),
),
);

View File

@ -1 +1,3 @@
export 'src/reactive_input_field.dart';
export 'src/reactive_input_field.dart';
export 'src/reactive_input_file.dart';
export 'src/validators/required_file.dart';

View File

@ -16,10 +16,15 @@ class ErrorMessages {
...messages,
};
final message = mergedErrorMessages.entries.firstWhere(
(element) => element.key == control.errors.entries.first.key,
orElse: () => const MapEntry('', ''),
);
MapEntry<String, dynamic> 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);
}

View File

@ -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<File>, List<File>> {
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<File>, List<File>> createState() {
return ReactiveFormFieldState<List<File>, List<File>>();
}
}

View File

@ -0,0 +1,7 @@
import 'package:reactive_forms/reactive_forms.dart';
Map<String, dynamic>? requiredFile(AbstractControl<dynamic> control) {
return control.isNotNull && control.value?.isNotEmpty == true
? null
: {'requiredFile': true};
}

View File

@ -0,0 +1,21 @@
import 'package:reactive_forms/reactive_forms.dart';
ValidatorFunction _mustMatch(String controlName, String matchingControlName) {
return (AbstractControl<dynamic> 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;
};
}