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"
|
||||
|
||||
android {
|
||||
compileSdkVersion flutter.compileSdkVersion
|
||||
compileSdkVersion 33
|
||||
ndkVersion flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.)',
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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'),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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