feat: enhance NewPage UI with dynamic bottom sheet for form input and restructure SDUI JSON schema for improved data handling

This commit is contained in:
2025-12-28 16:05:37 +03:30
parent 71952bef5a
commit fc0161e261
4 changed files with 1343 additions and 113 deletions

View File

@@ -25,30 +25,39 @@ class NewPage extends GetView<NewPageLogic> {
child: Column(
children: [
SizedBox(height: 24.h),
ObxValue((data) {
if (data.value == null) {
return Center(child: CircularProgressIndicator());
}
return Obx(
() => SDUIFormWidget(
model: data.value!,
controllers: controller.controllers,
state: controller.formState,
onStateChanged: (key, value) {
controller.formState[key] = value;
},
images: controller.images,
onImagesChanged: (key, imageList) {
controller.images[key] = imageList;
},
),
);
}, controller.sduiModel),
Row(children: []),
SizedBox(height: 24.h),
RElevated(
text: 'دکمه نمونه',
onPressed: () {
controller.onButtonPressed();
Get.bottomSheet(
isScrollControlled: true,
enableDrag: true,
BaseBottomSheet(
height: Get.height * 0.8,
child: ObxValue((data) {
if (data.value == null) {
return Center(child: CircularProgressIndicator());
}
return Obx(
() => SDUIFormWidget(
model: data.value!,
controllers: controller.controllers,
state: controller.formState,
onStateChanged: (key, value) {
controller.formState[key] = value;
},
images: controller.images,
onImagesChanged: (key, imageList) {
controller.images[key] = imageList;
},
),
);
}, controller.sduiModel),
),
);
},
),
SizedBox(height: 24.h),

View File

@@ -1,13 +1,149 @@
{
"type": "card_label_item",
"visible": true,
"data": {
"title": "اطلاعات مزرعه",
"padding_horizontal": 12.0,
"padding_vertical": 11.0
},
"child": {
"info": {
"type": "column",
"visible": true,
"data": {
"spacing": 10.0,
"crossAxisAlignment": "start"
},
"children": [
{
"type": "card_label_item",
"visible": true,
"data": {
"title": "اطلاعات پایه واحد",
"padding_horizontal": 12.0,
"padding_vertical": 11.0
},
"child": {
"type": "column",
"visible": true,
"data": {
"spacing": 10.0
},
"children": [
{
"type": "text_form_field",
"visible": true,
"data": {
"key": "unit_name",
"label": "نام واحد مرغداری",
"keyboard_type": "text"
}
},
{
"type": "text_form_field",
"visible": true,
"data": {
"key": "breeding_unique_id",
"label": "(عدد معمولی)کد یکتا / شناسه واحد",
"keyboard_type": "number"
}
},
{
"type": "text_form_field",
"visible": true,
"data": {
"key": "health",
"label": "پروانه بهداشتی",
"keyboard_type": "number"
}
},
{
"type": "text_form_field",
"visible": true,
"data": {
"key": "health_license",
"label": "عدد اعشاری ",
"keyboard_type": "number",
"decimal": true
}
},
{
"type": "text_form_field",
"visible": true,
"data": {
"key": "health_q",
"label": "عدد اعشاری با 2 رقم اعشار",
"keyboard_type": "number",
"decimal": true,
"decimal_places": 2
}
},
{
"type": "text_form_field",
"visible": true,
"data": {
"key": "unit_date",
"label": "نام تاریییییییییییخ",
"keyboard_type": "text",
"type": "date_picker"
}
},
{
"type": "chip_selection",
"visible": true,
"data": {
"key": "grain_quality",
"label": "کیفیت دانه",
"selectedIndex": -1,
"options": [
{
"index": 0,
"label": "خوب",
"value": "خوب"
},
{
"index": 1,
"label": "متوسط",
"value": "متوسط"
},
{
"index": 2,
"label": "ضعیف",
"value": "ضعیف"
}
]
}
},
{
"type": "dropdown",
"visible": true,
"data": {
"key": "training_status",
"label": "آموزش‌دیده در حوزه بهداشت و امنیت زیستی",
"placeholder": "آموزش‌دیده در حوزه بهداشت و امنیت زیستی",
"items": [
"بله",
"خیر"
],
"selectedValue": null,
"enabled": true
}
},
{
"type": "image_picker",
"visible": true,
"data": {
"key": "hall_images",
"label": "ثبت عکس سالن (حداقل ۳ زاویه)",
"required": true
}
},
{
"type": "image_picker",
"visible": true,
"data": {
"key": "hall_images_klllll",
"label": "ثبت عکس",
"required": true,
"max_images": 3
}
}
]
}
}
]
}
}

View File

@@ -213,86 +213,98 @@ class _RTextFieldState extends State<RTextField> {
return widget.height;
}
bool get _isMultiLine {
return (widget.maxLines != null && widget.maxLines! > 1) ||
(widget.minLines != null && widget.minLines! > 1);
}
@override
Widget build(BuildContext context) {
return SizedBox(
height: _calculateHeight().h,
child: Padding(
padding: widget.padding ?? EdgeInsets.zero,
child: TextFormField(
key: _formFieldKey,
textAlignVertical: TextAlignVertical.center,
controller: widget.controller,
focusNode: widget.focusNode,
textAlign: widget.textAlign ?? TextAlign.start,
readOnly: widget.readonly,
minLines: widget.minLines,
maxLines: widget.maxLines,
onChanged: (value) {
widget.onChanged?.call(value);
},
validator: (value) {
final error = widget.validator?.call(value);
final Widget textField = Padding(
padding: widget.padding ?? EdgeInsets.zero,
child: TextFormField(
key: _formFieldKey,
textAlignVertical: _isMultiLine
? TextAlignVertical.top
: TextAlignVertical.center,
controller: widget.controller,
focusNode: widget.focusNode,
textAlign: widget.textAlign ?? TextAlign.start,
readOnly: widget.readonly,
minLines: widget.minLines,
maxLines: widget.maxLines,
onChanged: (value) {
widget.onChanged?.call(value);
},
validator: (value) {
final error = widget.validator?.call(value);
if (widget.isFullHeight) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {});
}
});
}
return error;
},
inputFormatters: widget.inputFormatters,
enabled: widget.enabled,
obscureText: obscure,
onTap: widget.onTap,
onTapOutside: (_) => FocusScope.of(context).unfocus(),
onFieldSubmitted: widget.onSubmitted,
maxLength: widget.maxLength,
textDirection: textDirection,
style: widget.style,
keyboardType: widget.keyboardType,
autovalidateMode:
widget.autoValidateMode ?? AutovalidateMode.disabled,
cursorColor: widget.cursorColor,
textCapitalization: widget.textCapitalization,
autocorrect: widget.autocorrect ?? true,
enableSuggestions: widget.enableSuggestions ?? true,
textInputAction: widget.textInputAction,
autofillHints: widget.autofillHints,
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(
horizontal: 16,
vertical: widget.isFullHeight ? widget.height / 3 : 0,
),
errorStyle: widget.errorStyle,
errorMaxLines: 1,
isDense: widget.isDense,
suffix: widget.suffix,
suffixIcon: _buildSuffixIcon(),
suffixIconConstraints: widget.boxConstraints,
prefixIcon: widget.prefixIcon,
prefixIconConstraints: widget.boxConstraints,
hintText: widget.hintText,
labelText: widget.label,
alignLabelWithHint: true,
labelStyle: AppFonts.yekan14
.copyWith(color: AppColor.lightGreyDarkActive)
.merge(widget.labelStyle),
filled:
widget.filled || widget._noBorder || widget._passwordNoBorder,
fillColor: widget.filledColor,
counter: widget.showCounter ? null : const SizedBox(),
hintStyle: widget.hintStyle,
enabledBorder: widget._inputBorder,
focusedBorder: widget.focusedBorder ?? widget._inputBorder,
border: widget._inputBorder,
if (widget.isFullHeight) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {});
}
});
}
return error;
},
inputFormatters: widget.inputFormatters,
enabled: widget.enabled,
obscureText: obscure,
onTap: widget.onTap,
onTapOutside: (_) => FocusScope.of(context).unfocus(),
onFieldSubmitted: widget.onSubmitted,
maxLength: widget.maxLength,
textDirection: textDirection,
style: widget.style,
keyboardType: widget.keyboardType,
autovalidateMode: widget.autoValidateMode ?? AutovalidateMode.disabled,
cursorColor: widget.cursorColor,
textCapitalization: widget.textCapitalization,
autocorrect: widget.autocorrect ?? true,
enableSuggestions: widget.enableSuggestions ?? true,
textInputAction: widget.textInputAction,
autofillHints: widget.autofillHints,
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(
horizontal: 16,
vertical: _isMultiLine
? 12
: (widget.isFullHeight ? widget.height / 3 : 0),
),
errorStyle: widget.errorStyle,
errorMaxLines: 1,
isDense: widget.isDense,
suffix: widget.suffix,
suffixIcon: _buildSuffixIcon(),
suffixIconConstraints: widget.boxConstraints,
prefixIcon: widget.prefixIcon,
prefixIconConstraints: widget.boxConstraints,
hintText: widget.hintText,
labelText: widget.label,
alignLabelWithHint: true,
labelStyle: AppFonts.yekan14
.copyWith(color: AppColor.lightGreyDarkActive)
.merge(widget.labelStyle),
filled: widget.filled || widget._noBorder || widget._passwordNoBorder,
fillColor: widget.filledColor,
counter: widget.showCounter ? null : const SizedBox(),
hintStyle: widget.hintStyle,
enabledBorder: widget._inputBorder,
focusedBorder: widget.focusedBorder ?? widget._inputBorder,
border: widget._inputBorder,
),
),
);
// برای فیلدهای تک خطی، ارتفاع ثابت اعمال می‌کنیم
// برای فیلدهای چند خطی، اجازه می‌دهیم ارتفاع به صورت خودکار تنظیم شود
if (_isMultiLine) {
return textField;
}
return SizedBox(height: _calculateHeight().h, child: textField);
}
@override