segini dulu lah input file
This commit is contained in:
parent
521c54dfa8
commit
abbd0d8cc8
|
@ -12,7 +12,7 @@ import '_dashed_rect.dart';
|
||||||
class InputFile extends StatefulWidget {
|
class InputFile extends StatefulWidget {
|
||||||
const InputFile({
|
const InputFile({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.files = const [],
|
this.files,
|
||||||
required this.onChange,
|
required this.onChange,
|
||||||
this.allowMultiple = false,
|
this.allowMultiple = false,
|
||||||
this.allowedExtensions = const ['jpg', 'jpeg', 'png', 'gif'],
|
this.allowedExtensions = const ['jpg', 'jpeg', 'png', 'gif'],
|
||||||
|
@ -24,7 +24,7 @@ class InputFile extends StatefulWidget {
|
||||||
this.errorMessage,
|
this.errorMessage,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final List<File> files;
|
final List<File>? files;
|
||||||
final void Function(List<File> files) onChange;
|
final void Function(List<File> files) onChange;
|
||||||
final bool? allowMultiple;
|
final bool? allowMultiple;
|
||||||
final List<String>? allowedExtensions;
|
final List<String>? allowedExtensions;
|
||||||
|
@ -41,37 +41,27 @@ class InputFile extends StatefulWidget {
|
||||||
|
|
||||||
class _InputFileState extends State<InputFile> {
|
class _InputFileState extends State<InputFile> {
|
||||||
List<File> files = [];
|
List<File> files = [];
|
||||||
StreamController<List<File>> controller = StreamController();
|
|
||||||
late Stream<List<File>> stream;
|
|
||||||
|
|
||||||
void addFileToSink(List<File> addedFile) {
|
void addFileToSink(List<File> addedFile) {
|
||||||
controller.sink.add(files);
|
|
||||||
files = addedFile;
|
files = addedFile;
|
||||||
|
widget.onChange.call(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeFile(File file) {
|
void removeFile(File file) {
|
||||||
files.removeAt(files.indexOf(file));
|
setState(() {
|
||||||
controller.sink.add(files);
|
files.removeAt(files.indexOf(file));
|
||||||
}
|
widget.onChange.call(files);
|
||||||
|
});
|
||||||
void close() {
|
|
||||||
controller.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
// tambahkan initial component file value
|
// tambahkan initial component file value
|
||||||
files = widget.files;
|
if (widget.files != null) {
|
||||||
addFileToSink(files);
|
files = widget.files!;
|
||||||
// dapatkan stream dan listen ke onchange
|
addFileToSink(files);
|
||||||
stream = controller.stream;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
close();
|
|
||||||
super.dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ketika user memilih menggunakan camera saat input file
|
/// ketika user memilih menggunakan camera saat input file
|
||||||
|
@ -121,8 +111,7 @@ class _InputFileState extends State<InputFile> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = GolekTheme.of(context);
|
final theme = GolekTheme.of(context);
|
||||||
print("INPUT FILE");
|
print("INPUT FILE");
|
||||||
print(widget.isError!);
|
print(files);
|
||||||
print(widget.isTouched!);
|
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
@ -147,11 +136,7 @@ class _InputFileState extends State<InputFile> {
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 21),
|
const SizedBox(height: 21),
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onPressed: () {
|
onPressed: () => showModalFileToPick(
|
||||||
setState(() {
|
|
||||||
|
|
||||||
});
|
|
||||||
showModalFileToPick(
|
|
||||||
context: context,
|
context: context,
|
||||||
onPickCamera: (context) async {
|
onPickCamera: (context) async {
|
||||||
await pickFileFromCamera(context);
|
await pickFileFromCamera(context);
|
||||||
|
@ -161,8 +146,7 @@ class _InputFileState extends State<InputFile> {
|
||||||
await pickFileFromGallery(context);
|
await pickFileFromGallery(context);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
);
|
),
|
||||||
},
|
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
side: BorderSide(
|
side: BorderSide(
|
||||||
width: 1,
|
width: 1,
|
||||||
|
@ -181,16 +165,10 @@ class _InputFileState extends State<InputFile> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
StreamBuilder(
|
ListFilesBuilder(
|
||||||
stream: stream,
|
files: files,
|
||||||
builder: (context, snapshot) {
|
multiple: widget.allowMultiple!,
|
||||||
widget.onChange.call(files);
|
onRemoveFile: removeFile,
|
||||||
return ListFilesBuilder(
|
|
||||||
files: files,
|
|
||||||
multiple: widget.allowMultiple!,
|
|
||||||
onRemoveFile: removeFile,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
Text(
|
Text(
|
||||||
|
|
|
@ -27,17 +27,16 @@ class _LandingPageState extends State<LandingPage> {
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
final form = FormGroup({
|
||||||
|
'file_pem': FormControl<List<File>>(
|
||||||
|
value: [],
|
||||||
|
validators: [requiredFile],
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = GolekTheme.of(context);
|
final theme = GolekTheme.of(context);
|
||||||
final form = FormGroup({
|
|
||||||
'foto': FormControl<List<File>>(
|
|
||||||
value: [],
|
|
||||||
validators: [requiredFile],
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: LandingAppBar(),
|
appBar: LandingAppBar(),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
|
@ -46,25 +45,22 @@ class _LandingPageState extends State<LandingPage> {
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
ReactiveValueListenableBuilder(
|
ReactiveValueListenableBuilder(
|
||||||
formControlName: 'foto',
|
formControlName: 'file_pem',
|
||||||
builder: (context, control, child) {
|
builder: (context, control, child) {
|
||||||
print('DEBUG DI VALUE LISTEN');
|
|
||||||
print(control.value);
|
|
||||||
print(control.touched);
|
|
||||||
print(control.errors.entries);
|
|
||||||
return Container();
|
return Container();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ReactiveInputFile(
|
ReactiveInputFile(
|
||||||
name: 'foto',
|
name: 'file_pem',
|
||||||
allowMultiple: true,
|
allowMultiple: true,
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
form.markAllAsTouched();
|
form.markAllAsTouched();
|
||||||
print(form.control('foto').hasErrors);
|
print(form.control('file_pem').value);
|
||||||
print(form.control('foto').errors);
|
print(form.control('file_pem').hasErrors);
|
||||||
print(form.control('foto').touched);
|
print(form.control('file_pem').errors);
|
||||||
|
print(form.control('file_pem').touched);
|
||||||
if (form.valid){
|
if (form.valid){
|
||||||
print("FORM IS VALID");
|
print("FORM IS VALID");
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,20 +12,14 @@ class ErrorMessages {
|
||||||
Map<String, String>? widgetCustomMessages,
|
Map<String, String>? widgetCustomMessages,
|
||||||
}) {
|
}) {
|
||||||
Map<String, Object>? mergedErrorMessages = {
|
Map<String, Object>? mergedErrorMessages = {
|
||||||
...?widgetCustomMessages,
|
|
||||||
...messages,
|
...messages,
|
||||||
|
...?widgetCustomMessages,
|
||||||
};
|
};
|
||||||
|
|
||||||
MapEntry<String, dynamic> message = const MapEntry('', '');
|
final message = mergedErrorMessages.entries.firstWhere(
|
||||||
print('GET UI ERROR ');
|
(element) => control.errors.entries.isNotEmpty && element.key == control.errors.entries.first.key,
|
||||||
print(control.errors);
|
orElse: () => const MapEntry('', ''),
|
||||||
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);
|
return message.value.toString().replaceFirst('{_field_}', label);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:component_library/component_library.dart';
|
import 'package:component_library/component_library.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:reactive_forms/reactive_forms.dart';
|
import 'package:reactive_forms/reactive_forms.dart';
|
||||||
|
|
||||||
import 'error_messages.dart';
|
import 'error_messages.dart';
|
||||||
|
|
||||||
class ReactiveInputFile extends ReactiveFormField<List<File>, List<File>> {
|
class ReactiveInputFile extends ReactiveFormField<List<File>, List<File>> {
|
||||||
|
|
||||||
ReactiveInputFile({
|
ReactiveInputFile({
|
||||||
required String name,
|
required String name,
|
||||||
allowMultiple = false,
|
allowMultiple = false,
|
||||||
|
@ -19,25 +17,20 @@ class ReactiveInputFile extends ReactiveFormField<List<File>, List<File>> {
|
||||||
formControlName: name,
|
formControlName: name,
|
||||||
builder: (field) {
|
builder: (field) {
|
||||||
return InputFile(
|
return InputFile(
|
||||||
files: field.value ?? [],
|
files: field.value,
|
||||||
allowMultiple: allowMultiple,
|
allowMultiple: allowMultiple,
|
||||||
descriptionLabel: descriptionLabel,
|
descriptionLabel: descriptionLabel,
|
||||||
guidanceLabel: guidanceLabel,
|
guidanceLabel: guidanceLabel,
|
||||||
uploadButtonLabel: uploadButtonLabel,
|
uploadButtonLabel: uploadButtonLabel,
|
||||||
allowedExtensions: allowedExtensions,
|
allowedExtensions: allowedExtensions,
|
||||||
errorMessage: ErrorMessages.getUiErrorMessage(
|
errorMessage: ErrorMessages.getUiErrorMessage(
|
||||||
control: field.control,
|
control: field.control,
|
||||||
label: '',
|
label: '',
|
||||||
widgetCustomMessages: {
|
widgetCustomMessages: {'required': 'Foto harus diisi'},
|
||||||
'requiredFile': 'Foto harus diisi'
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
isTouched: field.control.touched,
|
isTouched: field.control.touched,
|
||||||
isError: field.control.hasErrors,
|
isError: field.control.hasErrors,
|
||||||
onChange: (files) {
|
onChange: (files) {
|
||||||
print('ON CAHNEF');
|
|
||||||
print(field.control.touched);
|
|
||||||
print(field.control.hasErrors);
|
|
||||||
field.didChange(files);
|
field.didChange(files);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,5 +3,5 @@ import 'package:reactive_forms/reactive_forms.dart';
|
||||||
Map<String, dynamic>? requiredFile(AbstractControl<dynamic> control) {
|
Map<String, dynamic>? requiredFile(AbstractControl<dynamic> control) {
|
||||||
return control.isNotNull && control.value?.isNotEmpty == true
|
return control.isNotNull && control.value?.isNotEmpty == true
|
||||||
? null
|
? null
|
||||||
: {'requiredFile': true};
|
: {'required': true};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue