debug: input file
This commit is contained in:
parent
c63282dfba
commit
521c54dfa8
|
@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
|
||||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion flutter.compileSdkVersion
|
compileSdkVersion 33
|
||||||
ndkVersion flutter.ndkVersion
|
ndkVersion flutter.ndkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
|
|
@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
|
||||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion flutter.compileSdkVersion
|
compileSdkVersion 33
|
||||||
ndkVersion flutter.ndkVersion
|
ndkVersion flutter.ndkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
|
|
@ -59,7 +59,24 @@ List<Story> getStories(GolekThemeData theme) {
|
||||||
allowMultiple: k.boolean(label: 'allowMultiple', initial: false),
|
allowMultiple: k.boolean(label: 'allowMultiple', initial: false),
|
||||||
onChange: (files) {},
|
onChange: (files) {},
|
||||||
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.)',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'package:mime/mime.dart';
|
||||||
import 'package:component_library/component_library.dart';
|
import 'package:component_library/component_library.dart';
|
||||||
import 'package:extended_image/extended_image.dart';
|
import 'package:extended_image/extended_image.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
|
@ -16,20 +16,24 @@ class InputFile extends StatefulWidget {
|
||||||
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'],
|
||||||
required this.inputLabel,
|
this.uploadButtonLabel,
|
||||||
|
this.guidanceLabel,
|
||||||
|
this.descriptionLabel,
|
||||||
this.isTouched = false,
|
this.isTouched = false,
|
||||||
this.isError = false,
|
this.isError = false,
|
||||||
this.messageError,
|
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;
|
||||||
final String inputLabel;
|
final String? uploadButtonLabel;
|
||||||
|
final String? guidanceLabel;
|
||||||
|
final String? descriptionLabel;
|
||||||
final bool? isTouched;
|
final bool? isTouched;
|
||||||
final bool? isError;
|
final bool? isError;
|
||||||
final String? messageError;
|
final String? errorMessage;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<InputFile> createState() => _InputFileState();
|
State<InputFile> createState() => _InputFileState();
|
||||||
|
@ -46,9 +50,8 @@ class _InputFileState extends State<InputFile> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeFile(File file) {
|
void removeFile(File file) {
|
||||||
final filtered = files.skipWhile((f) => f.hashCode == file.hashCode);
|
files.removeAt(files.indexOf(file));
|
||||||
controller.sink.add(filtered.toList());
|
controller.sink.add(files);
|
||||||
files = filtered.toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void close() {
|
void close() {
|
||||||
|
@ -117,6 +120,9 @@ class _InputFileState extends State<InputFile> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = GolekTheme.of(context);
|
final theme = GolekTheme.of(context);
|
||||||
|
print("INPUT FILE");
|
||||||
|
print(widget.isError!);
|
||||||
|
print(widget.isTouched!);
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
@ -132,7 +138,7 @@ class _InputFileState extends State<InputFile> {
|
||||||
child: IntrinsicHeight(
|
child: IntrinsicHeight(
|
||||||
child: DashedRect(
|
child: DashedRect(
|
||||||
color: widget.isError! && widget.isTouched!
|
color: widget.isError! && widget.isTouched!
|
||||||
? theme.dangerMainColor // jika error dan touched
|
? theme.dangerMainColor
|
||||||
: theme.neutral50Color,
|
: theme.neutral50Color,
|
||||||
strokeWidth: 2.0,
|
strokeWidth: 2.0,
|
||||||
gap: 3.0,
|
gap: 3.0,
|
||||||
|
@ -141,7 +147,11 @@ class _InputFileState extends State<InputFile> {
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 21),
|
const SizedBox(height: 21),
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onPressed: () => showModalFileToPick(
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
|
||||||
|
});
|
||||||
|
showModalFileToPick(
|
||||||
context: context,
|
context: context,
|
||||||
onPickCamera: (context) async {
|
onPickCamera: (context) async {
|
||||||
await pickFileFromCamera(context);
|
await pickFileFromCamera(context);
|
||||||
|
@ -151,7 +161,8 @@ 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,
|
||||||
|
@ -163,14 +174,13 @@ class _InputFileState extends State<InputFile> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Upload Foto\u{002A}',
|
widget.uploadButtonLabel ?? 'Upload Foto\u{002A}',
|
||||||
style: theme.textStyle.copyWith(
|
style: theme.textStyle.copyWith(
|
||||||
fontSize: FontSize.style14,
|
fontSize: FontSize.style14,
|
||||||
color: theme.primaryMainColor,
|
color: theme.primaryMainColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
|
||||||
StreamBuilder(
|
StreamBuilder(
|
||||||
stream: stream,
|
stream: stream,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
|
@ -182,13 +192,26 @@ class _InputFileState extends State<InputFile> {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
Text(
|
Text(
|
||||||
'Maks. 1 Foto, 1 Foto Maks. 2 mb',
|
widget.guidanceLabel ?? 'Maks. 1 Foto, 1 Foto Maks. 2 mb',
|
||||||
style: theme.textStyle.copyWith(
|
style: theme.textStyle.copyWith(
|
||||||
fontSize: FontSize.style14,
|
fontSize: FontSize.style14,
|
||||||
color: theme.primaryMainColor,
|
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),
|
const SizedBox(height: 38),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -196,8 +219,8 @@ class _InputFileState extends State<InputFile> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ErrorMessageBuilder(
|
ErrorMessageBuilder(
|
||||||
inputLabel: widget.inputLabel,
|
inputLabel: '', // ini nanti di custom di reactive component
|
||||||
messageError: widget.messageError ?? '',
|
messageError: widget.errorMessage ?? '',
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -226,12 +249,14 @@ class ListFilesBuilder extends StatelessWidget {
|
||||||
spacing: 4,
|
spacing: 4,
|
||||||
alignment: WrapAlignment.center,
|
alignment: WrapAlignment.center,
|
||||||
children: List.generate(files.length, (index) {
|
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: [
|
return Column(children: [
|
||||||
Visibility(
|
const SizedBox(height: 10),
|
||||||
visible: multiple == false,
|
if (multiple == false)
|
||||||
child: Container(
|
Container(
|
||||||
margin: const EdgeInsets.all(10),
|
margin: const EdgeInsets.all(10),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(6),
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
@ -245,25 +270,33 @@ class ListFilesBuilder extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
if (multiple == true)
|
||||||
Visibility(
|
Stack(
|
||||||
visible: multiple == true,
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
children: [
|
||||||
Container(
|
if (isImage)
|
||||||
margin: const EdgeInsets.all(10),
|
Container(
|
||||||
child: ClipRRect(
|
margin: const EdgeInsets.all(10),
|
||||||
borderRadius: BorderRadius.circular(6),
|
child: ClipRRect(
|
||||||
child: ExtendedImage.file(
|
borderRadius: BorderRadius.circular(6),
|
||||||
file,
|
child: ExtendedImage.file(
|
||||||
height: 105,
|
file,
|
||||||
width: 150,
|
height: 105,
|
||||||
enableMemoryCache: false,
|
width: 150,
|
||||||
clearMemoryCacheIfFailed: true,
|
enableMemoryCache: false,
|
||||||
clearMemoryCacheWhenDispose: true,
|
clearMemoryCacheIfFailed: true,
|
||||||
|
clearMemoryCacheWhenDispose: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (!isImage)
|
||||||
|
Container(
|
||||||
|
margin: const EdgeInsets.all(10),
|
||||||
|
child: Icon(
|
||||||
|
Icons.check_circle,
|
||||||
|
color: theme.primaryMainColor,
|
||||||
|
size: 50,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () => onRemoveFile(file),
|
onTap: () => onRemoveFile(file),
|
||||||
child: Container(
|
child: Container(
|
||||||
|
@ -279,7 +312,6 @@ class ListFilesBuilder extends StatelessWidget {
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:component_library/component_library.dart';
|
import 'package:component_library/component_library.dart';
|
||||||
import 'package:flutter/material.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';
|
import 'landing_page/landing_app_bar.dart';
|
||||||
|
|
||||||
class LandingPage extends StatefulWidget {
|
class LandingPage extends StatefulWidget {
|
||||||
|
@ -27,90 +31,48 @@ class _LandingPageState extends State<LandingPage> {
|
||||||
@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(
|
||||||
child: Stack(
|
child: ReactiveForm(
|
||||||
children: [
|
formGroup: form,
|
||||||
Container(
|
child: Column(
|
||||||
width: double.infinity,
|
children: [
|
||||||
height: 497,
|
ReactiveValueListenableBuilder(
|
||||||
decoration: BoxDecoration(
|
formControlName: 'foto',
|
||||||
color: theme.primaryMainColor,
|
builder: (context, control, child) {
|
||||||
borderRadius: const BorderRadius.vertical(
|
print('DEBUG DI VALUE LISTEN');
|
||||||
bottom: Radius.circular(25),
|
print(control.value);
|
||||||
),
|
print(control.touched);
|
||||||
|
print(control.errors.entries);
|
||||||
|
return Container();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
ReactiveInputFile(
|
||||||
Container(
|
name: 'foto',
|
||||||
padding: const EdgeInsets.only(
|
allowMultiple: true,
|
||||||
right: 20,
|
|
||||||
left: 20,
|
|
||||||
bottom: 80,
|
|
||||||
),
|
),
|
||||||
width: double.infinity,
|
ElevatedButton(
|
||||||
child: Column(
|
onPressed: () {
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
form.markAllAsTouched();
|
||||||
mainAxisSize: MainAxisSize.max,
|
print(form.control('foto').hasErrors);
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
print(form.control('foto').errors);
|
||||||
children: [
|
print(form.control('foto').touched);
|
||||||
Image.asset(
|
if (form.valid){
|
||||||
'assets/trucker_shipper.png',
|
print("FORM IS VALID");
|
||||||
package: 'features/landing_menu',
|
}
|
||||||
width: 300,
|
},
|
||||||
height: 200,
|
child: const Text('Submit'),
|
||||||
),
|
)
|
||||||
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 {},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -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';
|
|
@ -16,10 +16,15 @@ class ErrorMessages {
|
||||||
...messages,
|
...messages,
|
||||||
};
|
};
|
||||||
|
|
||||||
final message = mergedErrorMessages.entries.firstWhere(
|
MapEntry<String, dynamic> message = const MapEntry('', '');
|
||||||
(element) => element.key == control.errors.entries.first.key,
|
print('GET UI ERROR ');
|
||||||
orElse: () => const MapEntry('', ''),
|
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);
|
return message.value.toString().replaceFirst('{_field_}', label);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>>();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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};
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue