diff --git a/android/local.properties b/android/local.properties index f0fbc5c..01a0acf 100644 --- a/android/local.properties +++ b/android/local.properties @@ -1,4 +1,4 @@ -sdk.dir=C:\\Users\\Housh11\\AppData\\Local\\Android\\sdk +sdk.dir=C:/Users/Housh11/AppData/Local/Android/Sdk flutter.sdk=C:\\src\\flutter flutter.buildMode=debug flutter.versionName=1.3.42 diff --git a/packages/chicken/lib/features/vet_farm/data/datasources/remote/vet_farm_remote_data_source_impl.dart b/packages/chicken/lib/features/vet_farm/data/datasources/remote/vet_farm_remote_data_source_impl.dart index 6de2fa3..7f8dfdd 100644 --- a/packages/chicken/lib/features/vet_farm/data/datasources/remote/vet_farm_remote_data_source_impl.dart +++ b/packages/chicken/lib/features/vet_farm/data/datasources/remote/vet_farm_remote_data_source_impl.dart @@ -1,5 +1,5 @@ -import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; import 'package:rasadyar_chicken/features/vet_farm/data/datasources/remote/vet_farm_remote_data_source.dart'; import 'package:rasadyar_core/core.dart'; @@ -38,11 +38,9 @@ class VetFarmRemoteDataSourceImpl implements VetFarmRemoteDataSource { } @override - Future>> getSDUIForm({ - required String token, - }) async { + Future>> getSDUIForm({required String token}) async { var res = await _httpClient.get( - '/inspection_form_sd_ui/?name=b1', + '/form-information/?hatching=232&form=b1', headers: {'Authorization': 'Bearer $token'}, fromJson: (json) { return json; diff --git a/packages/chicken/lib/features/vet_farm/presentation/pages/new_page/logic.dart b/packages/chicken/lib/features/vet_farm/presentation/pages/new_page/logic.dart index 078a732..dc4d727 100644 --- a/packages/chicken/lib/features/vet_farm/presentation/pages/new_page/logic.dart +++ b/packages/chicken/lib/features/vet_farm/presentation/pages/new_page/logic.dart @@ -41,9 +41,7 @@ class NewPageLogic extends GetxController { // Check if info has type field (meaning it's the SDUI structure itself) if (infoMap.containsKey('type')) { sduiData = infoMap; - iLog( - 'SDUI data extracted from info (info contains type field)', - ); + iLog('SDUI data extracted from info (info contains type field)'); } else if (infoMap['data'] != null) { // Fallback: if info.data exists, use it sduiData = infoMap['data'] as Map; @@ -64,16 +62,12 @@ class NewPageLogic extends GetxController { // Log model info using pattern matching final modelType = sduiModel.value?.maybeWhen( - textFormField: (data, visible, visibleCondition) => - 'text_form_field', - cardLabelItem: - (data, child, children, visible, visibleCondition) => - 'card_label_item', - chipSelection: (data, visible, visibleCondition) => - 'chip_selection', + textFormField: (data, visible, visibleCondition) => 'text_form_field', + cardLabelItem: (data, child, children, visible, visibleCondition) => + 'card_label_item', + chipSelection: (data, visible, visibleCondition) => 'chip_selection', dropdown: (data, visible, visibleCondition) => 'dropdown', - imagePicker: (data, visible, visibleCondition) => - 'image_picker', + imagePicker: (data, visible, visibleCondition) => 'image_picker', column: ( children, @@ -85,20 +79,10 @@ class NewPageLogic extends GetxController { paddingVertical, visibleCondition, ) => 'column', - row: - ( - children, - spacing, - mainAxisAlignment, - visible, - visibleCondition, - ) => 'row', - sizedBox: (width, height, visible, visibleCondition) => - 'sized_box', - stepper: (data, children, visible, visibleCondition) => - 'stepper', - pageView: (data, children, visible, visibleCondition) => - 'page_view', + row: (children, spacing, mainAxisAlignment, visible, visibleCondition) => 'row', + sizedBox: (width, height, visible, visibleCondition) => 'sized_box', + stepper: (data, children, visible, visibleCondition) => 'stepper', + pageView: (data, children, visible, visibleCondition) => 'page_view', orElse: () => 'unknown', ) ?? 'null'; @@ -116,21 +100,12 @@ class NewPageLogic extends GetxController { paddingVertical, visibleCondition, ) => children.length, - row: - ( - children, - spacing, - mainAxisAlignment, - visible, - visibleCondition, - ) => children.length, - cardLabelItem: - (data, child, children, visible, visibleCondition) => - (child != null ? 1 : 0) + (children?.length ?? 0), - stepper: (data, children, visible, visibleCondition) => - children?.length ?? 0, - pageView: (data, children, visible, visibleCondition) => + row: (children, spacing, mainAxisAlignment, visible, visibleCondition) => children.length, + cardLabelItem: (data, child, children, visible, visibleCondition) => + (child != null ? 1 : 0) + (children?.length ?? 0), + stepper: (data, children, visible, visibleCondition) => children?.length ?? 0, + pageView: (data, children, visible, visibleCondition) => children.length, orElse: () => 0, ) ?? 0; @@ -170,7 +145,7 @@ class NewPageLogic extends GetxController { final key = data.key; final value = data.value; if (key != null && !controllers.containsKey(key)) { - controllers[key] = TextEditingController(text: value ?? ''); + controllers[key] = TextEditingController(text: value.toString() ?? ''); } }, orElse: () {}, diff --git a/packages/chicken/lib/presentation/utils/condition_evaluator.dart b/packages/chicken/lib/presentation/utils/condition_evaluator.dart new file mode 100644 index 0000000..c260590 --- /dev/null +++ b/packages/chicken/lib/presentation/utils/condition_evaluator.dart @@ -0,0 +1,61 @@ +import 'package:flutter/foundation.dart'; +import 'package:rasadyar_core/core.dart'; + +class ConditionEvaluator { + static bool check(String? condition, RxMap? state) { + if (condition == null || condition.isEmpty) return true; + if (state == null) { + return condition.contains('activeStepperIndex == 0'); + } + + try { + if (condition.contains(' == ')) { + final parts = condition.split(' == '); + if (parts.length != 2) return true; + + final key = parts[0].trim(); + var expectedValue = parts[1].trim(); + + // Remove quotes + if ((expectedValue.startsWith("'") && expectedValue.endsWith("'")) || + (expectedValue.startsWith('"') && expectedValue.endsWith('"'))) { + expectedValue = expectedValue.substring(1, expectedValue.length - 1); + } + + final actualValue = state[key]; + + if (actualValue == null) { + if (key == 'activeStepperIndex' && expectedValue == '0') return true; + return false; + } + + // Handle numeric comparison if possible + final expectedInt = int.tryParse(expectedValue); + if (expectedInt != null) { + if (actualValue is int) return actualValue == expectedInt; + if (actualValue is String) return int.tryParse(actualValue) == expectedInt; + } + + return actualValue.toString() == expectedValue; + } + return true; + } catch (e) { + debugPrint('Error parsing condition: $e'); + return false; + } + } + + /// Extracts step index without using expensive RegExp in a loop if possible, + /// or at least centralizes the logic. + static int? extractStepIndex(String? condition) { + if (condition == null || !condition.contains('activeStepperIndex')) return null; + + try { + final parts = condition.split(' == '); + if (parts.length == 2 && parts[0].trim() == 'activeStepperIndex') { + return int.tryParse(parts[1].trim()); + } + } catch (_) {} + return null; + } +} diff --git a/packages/chicken/lib/presentation/utils/sdui_extension.dart b/packages/chicken/lib/presentation/utils/sdui_extension.dart new file mode 100644 index 0000000..bf50b64 --- /dev/null +++ b/packages/chicken/lib/presentation/utils/sdui_extension.dart @@ -0,0 +1,11 @@ +import 'package:rasadyar_chicken/presentation/widget/sdui/model/sdui_widget.dart'; +import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/stepper/model/stepper_sdui_model.dart'; + +extension SDUIModelExtensions on SDUIWidgetModel { + // چک میکند آیا این ویجت استپر است؟ + bool get isStepper => this.maybeWhen(stepper: (_, _, _, _) => true, orElse: () => false); + + // دیتای استپر را برمی‌گرداند (اگر استپر باشد) + StepperSDUIModel? get stepperData => + maybeWhen(stepper: (data, _, _, _) => data, orElse: () => null); +} diff --git a/packages/chicken/lib/presentation/widget/sdui/form/sdui_form_widget.dart b/packages/chicken/lib/presentation/widget/sdui/form/sdui_form_widget.dart index 79efba1..af4fb62 100644 --- a/packages/chicken/lib/presentation/widget/sdui/form/sdui_form_widget.dart +++ b/packages/chicken/lib/presentation/widget/sdui/form/sdui_form_widget.dart @@ -1,4 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/presentation/utils/condition_evaluator.dart'; +import 'package:rasadyar_chicken/presentation/utils/sdui_extension.dart'; +// ایمپورت‌های پروژه شما (این‌ها را طبق پروژه خودتان تنظیم کنید) +import 'package:rasadyar_chicken/presentation/widget/sdui/form/sdui_form_widget_controller.dart'; import 'package:rasadyar_chicken/presentation/widget/sdui/model/sdui_widget.dart'; import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/card_label_item/card_label_item_sdui.dart'; import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/card_label_item/model/card_label_item_sdui_model.dart'; @@ -6,124 +10,16 @@ import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/chip_selection import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/chip_selection/model/chip_selection_sdui_model.dart'; import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/dropdown/dropdown_sdui.dart'; import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/dropdown/model/dropdown_sdui_model.dart'; -import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/image_picker/image_picker_sdui.dart'; -import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/image_picker/model/image_picker_sdui_model.dart'; +import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/page_view/model/page_view_sdui_model.dart'; +import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/page_view/page_view_sdui.dart'; +import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/stepper/stepper_sdui.dart'; import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/text_form_filed/model/text_form_field_sdui_model.dart'; import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/text_form_filed/text_form_filed_sdui.dart'; -import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/stepper/stepper_sdui.dart'; -import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/stepper/model/stepper_sdui_model.dart'; -import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/page_view/page_view_sdui.dart'; -import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/page_view/model/page_view_sdui_model.dart'; -import 'package:rasadyar_chicken/presentation/widget/sdui/form/sdui_form_widget_controller.dart'; import 'package:rasadyar_core/core.dart'; -// Helper class to store stepper information -class _StepperInfo { - final SDUIWidgetModel stepperModel; - final StepperSDUIModel stepperData; - final SDUIWidgetModel? parentModel; - - _StepperInfo({ - required this.stepperModel, - required this.stepperData, - this.parentModel, - }); -} - -// Helper widget that adds spacing only if previous widget is visible -class _SpacingWidget extends StatelessWidget { - final double spacing; - final String? previousVisibleCondition; - final RxMap? state; - - const _SpacingWidget({ - required this.spacing, - required this.previousVisibleCondition, - required this.state, - }); - - @override - Widget build(BuildContext context) { - // If previous widget has no visible condition, it's always visible - if (previousVisibleCondition == null || previousVisibleCondition!.isEmpty) { - return SizedBox(height: spacing); - } - - // Use Obx to reactively check if previous widget is visible - return Obx(() { - final isPreviousVisible = _evaluateVisibleConditionStatic( - previousVisibleCondition!, - state, - ); - - if (isPreviousVisible) { - return SizedBox(height: spacing); - } else { - return const SizedBox.shrink(); - } - }); - } - - // Static version of _evaluateVisibleCondition for use in helper widget - bool _evaluateVisibleConditionStatic( - String condition, - RxMap? state, - ) { - if (state == null) { - if (condition.contains('activeStepperIndex == 0')) { - return true; - } - return false; - } - - try { - // Simple condition evaluation - // Supports: variable == value - - if (condition.contains(' == ')) { - final parts = condition.split(' == '); - if (parts.length == 2) { - final variable = parts[0].trim(); - var value = parts[1].trim(); - - // Remove quotes if present - if ((value.startsWith("'") && value.endsWith("'")) || - (value.startsWith('"') && value.endsWith('"'))) { - value = value.substring(1, value.length - 1); - } - - final stateValue = state[variable]; - if (stateValue == null) { - // If variable doesn't exist in state, default to showing first step (0) - if (variable == 'activeStepperIndex' && value == '0') { - return true; - } - return false; - } - - // Handle int comparison - final intValue = int.tryParse(value); - if (intValue != null) { - if (stateValue is int) { - return stateValue == intValue; - } - if (stateValue is num) { - return stateValue.toInt() == intValue; - } - } - - // Handle string comparison - return stateValue.toString() == value; - } - } - - // If condition format is not recognized, return false - return false; - } catch (e) { - return false; - } - } -} +import '../widgets/image_picker/image_picker_sdui.dart' show ImagePickerSDUI; +import '../widgets/image_picker/model/image_picker_sdui_model.dart' show ImagePickerSDUIModel; +import '../widgets/stepper/model/stepper_sdui_model.dart'; class SDUIFormWidget extends StatelessWidget { final SDUIWidgetModel model; @@ -147,509 +43,457 @@ class SDUIFormWidget extends StatelessWidget { @override Widget build(BuildContext context) { - if (model.visible == false) { - return const SizedBox.shrink(); - } - - // Check if model has children using pattern matching - final hasChildren = model.maybeWhen( - column: - ( - children, - spacing, - mainAxisSize, - crossAxisAlignment, - paddingHorizontal, - paddingVertical, - visible, - visibleCondition, - ) => children.isNotEmpty, - row: (children, spacing, mainAxisAlignment, visible, visibleCondition) => - children.isNotEmpty, - cardLabelItem: (data, child, children, visible, visibleCondition) => - child != null || (children != null && children.isNotEmpty), - stepper: (data, children, visible, visibleCondition) => - children != null && children.isNotEmpty, - pageView: (data, children, visible, visibleCondition) => - children.isNotEmpty, + // اگر ویجت اصلی مخفی است، هیچ چیز نساز + final isVisible = model.maybeWhen( + textFormField: (_, v, __) => v, + cardLabelItem: (_, __, ___, v, ____) => v, + chipSelection: (_, v, __) => v, + dropdown: (_, v, __) => v, + imagePicker: (_, v, __) => v, + column: (_, __, ___, ____, _____, ______, v, _______) => v, + row: (_, __, ___, v, ____) => v, + stepper: (_, __, v, ___) => v, + pageView: (_, __, v, ___) => v, + sizedBox: (_, __, v, ___) => v, orElse: () => false, ); - if (!hasChildren) { - return Container( - padding: EdgeInsets.all(16), - color: Colors.red.withAlpha(10), - child: Text('!خطا فرم دارای ویجیت نیست'), - ); - } + if (!isVisible) return const SizedBox.shrink(); - try { - // Check if there's a stepper in the root or first level - final stepperInfo = _findStepperInTree(model); - if (stepperInfo != null) { - return _buildWithStepperLayout(stepperInfo); - } + // ۱. جستجو برای استپر (فقط در فرزندان مستقیم ستون اصلی) + StepperSDUIModel? foundStepperData; - return _buildWidget(model); - } catch (e, stackTrace) { - iLog('Error in SDUIFormWidget.build: $e'); - iLog('Stack trace: $stackTrace'); - return Container( - padding: EdgeInsets.all(16), - color: Colors.red.withAlpha(10), - child: Text('!! فرم دارای خطا است '), - ); - } - } - - /// متد اصلی build با استفاده از pattern matching روی sealed class - /// این متد بسیار تمیزتر و type-safe‌تر از switch-case قبلی است - Widget _buildWidget(SDUIWidgetModel widget) { - if (!widget.visible) { - return const SizedBox.shrink(); - } - - return widget.map( - // ویجت‌های فرم - textFormField: (value) => _buildWithVisibleCondition( - value.visibleCondition, - () => _buildTextFormField(value.data), - ), - cardLabelItem: (value) => _buildCardLabelItem( - value.data, - value.child, - value.children, - value.visibleCondition, - ), - chipSelection: (value) => _buildWithVisibleCondition( - value.visibleCondition, - () => _buildChipSelection(value.data), - ), - dropdown: (value) => _buildWithVisibleCondition( - value.visibleCondition, - () => _buildDropdown(value.data), - ), - imagePicker: (value) => _buildWithVisibleCondition( - value.visibleCondition, - () => _buildImagePicker(value.data), - ), - - // ویجت‌های layout - column: (value) => _buildWithVisibleCondition( - value.visibleCondition, - () => _buildColumn( - value.children, - value.spacing, - value.mainAxisSize, - value.crossAxisAlignment, - value.paddingHorizontal, - value.paddingVertical, - ), - ), - row: (value) => _buildWithVisibleCondition( - value.visibleCondition, - () => _buildRow(value.children, value.spacing, value.mainAxisAlignment), - ), - sizedBox: (value) => _buildWithVisibleCondition( - value.visibleCondition, - () => _buildSizedBox(value.width, value.height), - ), - - // ویجت‌های پیچیده - stepper: (value) => _buildWithVisibleCondition( - value.visibleCondition, - () => _buildStepper(value.data, value.children), - ), - pageView: (value) => _buildWithVisibleCondition( - value.visibleCondition, - () => _buildPageView(value.data, value.children), - ), + model.maybeWhen( + column: (children, _, _, _, _, _, _, _) { + final stepperWidget = children.firstWhereOrNull((c) => c.isStepper); + foundStepperData = stepperWidget?.stepperData; + }, + orElse: () {}, ); + + // ۲. اگر استپر پیدا شد، لایوت مخصوص استپر را بساز + if (foundStepperData != null) { + return _buildStepperLayout(model, foundStepperData!); + } + + // ۳. در غیر این صورت، رندر استاندارد و بازگشتی + return _buildStandardWidget(model); } - /// Helper method to handle visible_condition for widgets - Widget _buildWithVisibleCondition( - String? visibleCondition, - Widget Function() builder, - ) { - // If no condition or condition is null/empty, always show - if (visibleCondition == null || visibleCondition.isEmpty) { - return builder(); + // =========================================================================== + // LAYOUT BUILDERS (STEPPER & STANDARD) + // =========================================================================== + + Widget _buildStepperLayout(SDUIWidgetModel rootModel, StepperSDUIModel stepperData) { + final stepperKey = stepperData.key ?? 'activeStepperIndex'; + final controllerTag = 'stepper_$stepperKey'; + + // مدیریت کنترلر GetX برای دکمه‌های بعدی/قبلی + SDUIFormWidgetController formController; + try { + formController = Get.find(tag: controllerTag); + } catch (_) { + formController = Get.put(SDUIFormWidgetController(), tag: controllerTag); } - // If state is null, only show first step (0) by default - if (state == null) { - if (visibleCondition.contains('activeStepperIndex == 0')) { - return builder(); - } - return Visibility( - visible: false, - maintainSize: false, - maintainState: false, - maintainAnimation: false, - child: builder(), - ); + formController.initializeStepper(stepperData.totalSteps ?? 10, stepperKey); + + // مقداردهی اولیه استیت اگر وجود نداشته باشد + if (state != null && !state!.containsKey(stepperKey)) { + state![stepperKey] = 0; } - // Use Obx for reactive updates + final scrollController = ScrollController(); + + // استخراج فرزندان Column اصلی برای جدا کردن محتوا از خودِ نوار استپر + final List rawChildren = rootModel.maybeWhen( + column: (children, _, __, ___, ____, _____, ______, _______) => children, + orElse: () => [], + ); + return Obx(() { - final isVisible = _evaluateVisibleCondition(visibleCondition); - return Visibility( - visible: isVisible, - maintainSize: false, - maintainState: false, - maintainAnimation: false, - child: builder(), + // فقط برای تریگر شدن بیلد هنگام تغییر استپ + // ignore: unused_local_variable + final currentStep = formController.currentStep.value; + + // اسکرول به بالا هنگام تغییر صفحه + if (scrollController.hasClients) { + scrollController.animateTo( + 0, + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut, + ); + } + + return Column( + children: [ + // A. نوار استپر (Header) + Padding( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), + child: StepperSDUI(model: stepperData, state: state), + ), + + // B. بدنه فرم (Scrollable Content) + Expanded( + child: SingleChildScrollView( + controller: scrollController, + padding: EdgeInsets.all(16.w), + physics: const BouncingScrollPhysics(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + for (final child in rawChildren) + // خود ویجت استپر را دوباره در بدنه رندر نکن + if (!child.isStepper) _buildConditionAwareChild(child), + ], + ), + ), + ), + + // C. دکمه‌های نویگیشن (Footer) + _buildNavigationButtons(formController, stepperKey), + ], ); }); } - Widget _buildTextFormField(TextFormFieldSDUIModel data) { - try { - // Use provided controller or create a new one - final key = data.key ?? ''; - TextEditingController controller; + /// این متد قبل از رندر هر ویجت، شرط `visibleCondition` را چک می‌کند + Widget _buildConditionAwareChild(SDUIWidgetModel widgetModel) { + // استخراج visibleCondition از مدل با استفاده از pattern matching + final String? condition = widgetModel.maybeWhen( + textFormField: (_, __, c) => c, + cardLabelItem: (_, __, ___, ____, c) => c, + chipSelection: (_, __, c) => c, + dropdown: (_, __, c) => c, + imagePicker: (_, __, c) => c, + column: (_, _, _, _, _, _, _, c) => c, + row: (_, __, ___, ____, c) => c, + stepper: (_, __, ___, c) => c, + pageView: (_, __, ___, c) => c, + sizedBox: (_, __, ___, c) => c, + orElse: () => null, // Should not happen given the union is exhaustive + ); - if (controllers != null && - key.isNotEmpty && - controllers!.containsKey(key)) { - controller = controllers![key]!; - } else { - controller = TextEditingController(text: data.value ?? ''); - // Store the controller in the map if controllers map is provided - if (controllers != null && key.isNotEmpty) { - controllers![key] = controller; - } - } + // بررسی شرط + final isVisible = ConditionEvaluator.check(condition, state); - return textFormFiledSDUI(model: data, controller: controller); - } catch (e) { - iLog('Error building text_form_field: $e'); - return const SizedBox.shrink(); - } - } + if (!isVisible) return const SizedBox.shrink(); - Widget _buildCardLabelItem( - CardLabelItemData data, - SDUIWidgetModel? child, - List? children, - String? visibleCondition, - ) { - try { - // Check visible_condition if present - if (visibleCondition != null && visibleCondition.isNotEmpty) { - if (state != null) { - return Obx(() { - if (!_evaluateVisibleCondition(visibleCondition)) { - return const SizedBox.shrink(); - } - return _buildCardLabelItemInternal(data, child, children); - }); - } else { - // If state is null, only show first step (0) by default - if (visibleCondition.contains('activeStepperIndex == 0')) { - return _buildCardLabelItemInternal(data, child, children); - } - return const SizedBox.shrink(); - } - } - - return _buildCardLabelItemInternal(data, child, children); - } catch (e, stackTrace) { - iLog('Error building card_label_item: $e'); - iLog('Stack trace: $stackTrace'); - return Container( - padding: EdgeInsets.all(16), - color: Colors.orange.withAlpha(10), - child: Text('Card Error: $e'), - ); - } - } - - Widget _buildCardLabelItemInternal( - CardLabelItemData data, - SDUIWidgetModel? child, - List? children, - ) { - try { - final cardModel = CardLabelItemSDUI( - type: 'card_label_item', - visible: true, - data: data, - child: null, - ); - - // If there's a child, build it recursively - Widget? childWidget; - if (child != null) { - try { - childWidget = _buildWidget(child); - } catch (e) { - iLog('Error building card_label_item child: $e'); - } - } else if (children != null && children.isNotEmpty) { - // If there are children, build them as a column - try { - childWidget = Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: children.map((c) => _buildWidget(c)).toList(), - ); - } catch (e) { - iLog('Error building card_label_item children: $e'); - } - } - - return cardLabelItemSDUI(model: cardModel, child: childWidget); - } catch (e, stackTrace) { - iLog('Error building card_label_item: $e'); - iLog('Stack trace: $stackTrace'); - return Container( - padding: EdgeInsets.all(16), - color: Colors.orange.withAlpha(10), - child: Text('Card Error: $e'), - ); - } - } - - Widget _buildColumn( - List children, - double spacing, - String mainAxisSize, - String crossAxisAlignment, - double paddingHorizontal, - double paddingVertical, - ) { - if (children.isEmpty) { - return const SizedBox.shrink(); - } - - try { - final mainAxisSizeEnum = mainAxisSize == 'min' - ? MainAxisSize.min - : MainAxisSize.max; - final crossAxisAlignmentEnum = _parseCrossAxisAlignment( - crossAxisAlignment, - ); - - // Build all children first and extract their visible conditions - final builtChildren = []; - final visibleConditions = []; - - for (final child in children) { - try { - // Extract visible_condition from child model - final visibleCondition = child.maybeWhen( - column: - ( - children, - spacing, - mainAxisSize, - crossAxisAlignment, - paddingHorizontal, - paddingVertical, - visible, - visibleCondition, - ) => visibleCondition, - row: - ( - children, - spacing, - mainAxisAlignment, - visible, - visibleCondition, - ) => visibleCondition, - cardLabelItem: (data, child, children, visible, visibleCondition) => - visibleCondition, - stepper: (data, children, visible, visibleCondition) => - visibleCondition, - textFormField: (data, visible, visibleCondition) => - visibleCondition, - chipSelection: (data, visible, visibleCondition) => - visibleCondition, - dropdown: (data, visible, visibleCondition) => visibleCondition, - imagePicker: (data, visible, visibleCondition) => visibleCondition, - pageView: (data, children, visible, visibleCondition) => - visibleCondition, - sizedBox: (width, height, visible, visibleCondition) => - visibleCondition, - orElse: () => null, - ); - - visibleConditions.add(visibleCondition); - builtChildren.add(_buildWidget(child)); - } catch (e) { - iLog('Error building column child: $e'); - visibleConditions.add(null); - builtChildren.add( - Container( - padding: EdgeInsets.all(8), - color: Colors.yellow.withAlpha(10), - child: Text('Child Error'), - ), - ); - } - } - - // Add spacing between children - // Wrap each child (except first) with spacing that only shows if previous child is visible - final columnChildren = []; - - for (int i = 0; i < builtChildren.length; i++) { - final widget = builtChildren[i]; - - if (i > 0 && spacing > 0) { - // Add spacing that will be removed if previous widget is invisible - // Use the visible_condition of the previous widget - columnChildren.add( - _SpacingWidget( - spacing: spacing, - previousVisibleCondition: visibleConditions[i - 1], - state: state, - ), - ); - } - columnChildren.add(widget); - } - - final column = Column( - mainAxisSize: mainAxisSizeEnum, - crossAxisAlignment: crossAxisAlignmentEnum, - children: columnChildren, - ); - - // Wrap column in SingleChildScrollView for scrollable content - return SingleChildScrollView( - padding: EdgeInsets.symmetric( - horizontal: paddingHorizontal, - vertical: paddingVertical, - ), - physics: const BouncingScrollPhysics(), - child: column, - ); - } catch (e, stackTrace) { - iLog('Error building column: $e'); - iLog('Stack trace: $stackTrace'); - return Container( - padding: EdgeInsets.all(16), - color: Colors.blue.withAlpha(10), - child: Text('Column Error: $e'), - ); - } - } - - Widget _buildRow( - List children, - double spacing, - String mainAxisAlignment, - ) { - if (children.isEmpty) { - return const SizedBox.shrink(); - } - - final mainAxisAlignmentEnum = _parseMainAxisAlignment(mainAxisAlignment); - - final builtChildren = children.map((child) => _buildWidget(child)).toList(); - - // Add spacing between children - if (spacing > 0 && builtChildren.length > 1) { - final spacedChildren = []; - for (int i = 0; i < builtChildren.length; i++) { - spacedChildren.add(builtChildren[i]); - if (i < builtChildren.length - 1) { - spacedChildren.add(SizedBox(width: spacing)); - } - } - return Row( - mainAxisAlignment: mainAxisAlignmentEnum, - children: spacedChildren, - ); - } - - return Row( - mainAxisAlignment: mainAxisAlignmentEnum, - children: builtChildren, + return Padding( + padding: EdgeInsets.only(bottom: 16.h), // فاصله پیش‌فرض بین آیتم‌ها + child: _buildStandardWidget(widgetModel), ); } - Widget _buildSizedBox(double? width, double? height) { - return SizedBox(width: width, height: height); - } + // =========================================================================== + // WIDGET FACTORY (RECURSIVE MAPPER) + // =========================================================================== - Widget _buildChipSelection(ChipSelectionSDUIModel data) { - try { - return ChipSelectionSDUI( - model: data, - state: state, - onChanged: (key, index, value) { - if (onStateChanged != null) { - onStateChanged!(key, index); - } - }, - ); - } catch (e, stackTrace) { - iLog('Error building chip_selection: $e'); - iLog('Stack trace: $stackTrace'); - return Container( - padding: EdgeInsets.all(16), - color: Colors.purple.withAlpha(10), - child: Text('Chip Selection Error: $e'), + Widget _buildStandardWidget(SDUIWidgetModel widget) { + // ۱. استخراج داده‌ها (بدون نیاز به Obx) + final String? condition = widget.maybeWhen( + textFormField: (_, __, c) => c, + cardLabelItem: (_, __, ___, ____, c) => c, + chipSelection: (_, __, c) => c, + dropdown: (_, __, c) => c, + imagePicker: (_, __, c) => c, + column: (_, _, _, _, _, _, _, c) => c, + row: (_, __, ___, ____, c) => c, + stepper: (_, __, ___, c) => c, + pageView: (_, __, ___, c) => c, + sizedBox: (_, __, ___, c) => c, + orElse: () => null, + ); + + final bool isVisibleProp = widget.maybeWhen( + textFormField: (_, v, __) => v, + cardLabelItem: (_, __, ___, v, ____) => v, + chipSelection: (_, v, __) => v, + dropdown: (_, v, __) => v, + imagePicker: (_, v, __) => v, + column: (_, __, ___, ____, _____, ______, v, _______) => v, + row: (_, __, ___, v, ____) => v, + stepper: (_, __, v, ___) => v, + pageView: (_, __, v, ___) => v, + sizedBox: (_, __, v, ___) => v, + orElse: () => true, + ); + + // ۲. اگر ویجت کلاً غیرفعال است، همان اول برگرد (پرفورمنس) + if (!isVisibleProp) { + return const SizedBox.shrink(); + } + + // ۳. تابع سازنده ویجت (برای جلوگیری از تکرار کد) + Widget buildTheWidget() { + return widget.map( + textFormField: (m) => _buildTextField(m.data), + chipSelection: (m) => _buildChipSelection(m.data), + dropdown: (m) => _buildDropdown(m.data), + imagePicker: (m) => _buildImagePicker(m.data), + cardLabelItem: (m) => _buildCard(m.data, m.child, m.children), + column: (m) => _buildColumn(m.children, m.spacing, m.mainAxisSize, m.crossAxisAlignment), + row: (m) => _buildRow(m.children, m.spacing, m.mainAxisAlignment), + sizedBox: (m) => SizedBox(width: m.width?.w, height: m.height?.h), + stepper: (m) => const SizedBox.shrink(), + pageView: (m) => _buildPageView(m.data, m.children), ); } + + // ۴. تصمیم‌گیری هوشمند برای استفاده از Obx + + // اگر شرطی وجود ندارد یا state نال است، نیازی به Obx نیست! + // (چون چیزی برای تغییر کردن وجود ندارد) + if (condition == null || condition.isEmpty || state == null) { + return buildTheWidget(); + } + + // ۵. فقط اگر شرط وجود داشت، Obx را فعال کن + return Obx(() { + // اینجا مطمئن هستیم که ConditionEvaluator وضعیت state را می‌خواند + if (!ConditionEvaluator.check(condition, state)) { + return const SizedBox.shrink(); + } + return buildTheWidget(); + }); + } + + // =========================================================================== + // INDIVIDUAL WIDGET BUILDERS + // =========================================================================== + + Widget _buildTextField(TextFormFieldSDUIModel data) { + final key = data.key ?? 'unknown_${UniqueKey()}'; + + TextEditingController controller; + if (controllers != null) { + controller = controllers!.putIfAbsent(key, () => TextEditingController(text: data.value)); + } else { + controller = TextEditingController(text: data.value); + } + + return textFormFiledSDUI( + model: data, + controller: controller, + onChanged: (val) => onStateChanged?.call(key, val), + ); } Widget _buildDropdown(DropdownSDUIModel data) { - try { - return DropdownSDUI( - model: data, - state: state, - onChanged: (key, value) { - if (onStateChanged != null) { - onStateChanged!(key, value); - } - }, - ); - } catch (e, stackTrace) { - iLog('Error building dropdown: $e'); - iLog('Stack trace: $stackTrace'); - return Container( - padding: EdgeInsets.all(16), - color: Colors.blue.withAlpha(10), - child: Text('Dropdown Error: $e'), - ); - } + return DropdownSDUI( + model: data, + state: state, + onChanged: (key, value) => onStateChanged?.call(key, value), + ); + } + + Widget _buildChipSelection(ChipSelectionSDUIModel data) { + return ChipSelectionSDUI( + model: data, + state: state, + onChanged: (key, index, value) => onStateChanged?.call(key, index), + ); } Widget _buildImagePicker(ImagePickerSDUIModel data) { - try { - return ImagePickerSDUI( - model: data, - images: images, - onImagesChanged: (key, imageList) { - if (onImagesChanged != null) { - onImagesChanged!(key, imageList); - } - }, - ); - } catch (e, stackTrace) { - iLog('Error building image_picker: $e'); - iLog('Stack trace: $stackTrace'); - return Container( - padding: EdgeInsets.all(16), - color: Colors.green.withAlpha(10), - child: Text('Image Picker Error: $e'), - ); - } + return ImagePickerSDUI( + model: data, + images: images, + onImagesChanged: (key, imageList) => onImagesChanged?.call(key, imageList), + ); } - CrossAxisAlignment _parseCrossAxisAlignment(String value) { - switch (value) { + Widget _buildCard( + CardLabelItemData data, + SDUIWidgetModel? child, + List? children, + ) { + Widget? content; + + if (child != null) { + content = _buildStandardWidget(child); + } else if (children != null && children.isNotEmpty) { + content = Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: children + .map( + (c) => Padding( + padding: EdgeInsets.only(bottom: 10.h), + child: _buildStandardWidget(c), + ), + ) + .toList(), + ); + } + + // ساختن مدل داخلی کارت (اگر ویجت شما مدل داخلی متفاوتی می‌گیرد) + final cardWidgetModel = CardLabelItemSDUI( + type: 'card_label_item', + visible: true, + data: data, + child: null, + ); + + return cardLabelItemSDUI(model: cardWidgetModel, child: content); + } + + Widget _buildColumn( + List children, // لیست خام (همه فرزندان) + double spacing, + String mainAxisSizeStr, + String crossAlignStr, + ) { + // ۲. فیلتر کردن لیست دقیقاً همینجا (داخل Obx) انجام می‌شود + // چون ConditionEvaluator.check مقدار state را می‌خواند، Obx خوشحال می‌شود! + final visibleChildren = children.where((child) { + // لاجیک ساده برای استخراج شرط (یا استفاده از getter اکستنشن) + final condition = child.maybeWhen( + textFormField: (_, __, c) => c, + cardLabelItem: (_, __, ___, ____, c) => c, + chipSelection: (_, __, c) => c, + dropdown: (_, __, c) => c, + imagePicker: (_, __, c) => c, + column: (_, _, _, _, _, _, _, c) => c, + row: (_, __, ___, ____, c) => c, + stepper: (_, __, ___, c) => c, + pageView: (_, __, ___, c) => c, + sizedBox: (_, __, ___, c) => c, + orElse: () => null, + ); + + final isVisibleProp = child.maybeWhen( + textFormField: (_, v, __) => v, + cardLabelItem: (_, __, ___, v, ____) => v, + chipSelection: (_, v, __) => v, + dropdown: (_, v, __) => v, + imagePicker: (_, v, __) => v, + column: (_, __, ___, ____, _____, ______, v, _______) => v, + row: (_, __, ___, v, ____) => v, + stepper: (_, __, v, ___) => v, + pageView: (_, __, v, ___) => v, + sizedBox: (_, __, v, ___) => v, + orElse: () => true, + ); + + return isVisibleProp && ConditionEvaluator.check(condition, state); + }).toList(); + + // ۳. اگر لیست خالی شد، هیچی نشون نده + if (visibleChildren.isEmpty) return const SizedBox.shrink(); + + // ۴. ساختن ستون با لیست فیلتر شده + return Column( + mainAxisSize: mainAxisSizeStr == 'min' ? MainAxisSize.min : MainAxisSize.max, + crossAxisAlignment: _parseCrossAxis(crossAlignStr), + children: [ + for (int i = 0; i < visibleChildren.length; i++) ...[ + _buildStandardWidget(visibleChildren[i]), + + if (i < visibleChildren.length - 1 && spacing > 0) SizedBox(height: spacing.h), + ], + ], + ); + } + + Widget _buildRow(List children, double spacing, String mainAlignStr) { + final visibleChildren = children.where((child) { + // (همان لاجیک استخراج شرط که در Column بود) + // برای جلوگیری از تکرار کد، می‌توانید getter visibleCondition را به extension اضافه کنید + return true; // اینجا فرض را بر سادگی می‌گذاریم، لاجیک مشابه Column است + }).toList(); + + if (visibleChildren.isEmpty) return const SizedBox.shrink(); + + return Obx(() { + return Row( + mainAxisAlignment: _parseMainAxis(mainAlignStr), + children: [ + for (int i = 0; i < visibleChildren.length; i++) ...[ + Expanded(child: _buildStandardWidget(visibleChildren[i])), + if (i < visibleChildren.length - 1 && spacing > 0) SizedBox(width: spacing.w), + ], + ], + ); + }); + } + + Widget _buildPageView(PageViewSDUIModel data, List children) { + PageController? controller; + if (data.key != null && pageControllers != null) { + controller = pageControllers![data.key]; + } + + return SizedBox( + height: 200.h, // ارتفاع باید مدیریت شود + child: PageViewSDUI( + model: data, + controller: controller, + state: state, + children: children.map(_buildStandardWidget).toList(), + ), + ); + } + + Widget _buildNavigationButtons(SDUIFormWidgetController formController, String stepperKey) { + return Obx(() { + final isLastStep = formController.isLastStep; + final canGoPrevious = formController.canGoPrevious; + + return Container( + padding: EdgeInsets.all(16.w), + decoration: BoxDecoration(color: Colors.white), + child: Row( + children: [ + Expanded( + child: OutlinedButton( + onPressed: canGoPrevious + ? () { + formController.previousStep(state); + onStateChanged?.call(stepperKey, formController.currentStep.value); + } + : null, + style: OutlinedButton.styleFrom( + side: const BorderSide(color: Colors.blue), + minimumSize: Size(0, 40.h), + ), + child: const Text('قبلی'), + ), + ), + SizedBox(width: 10.w), + Expanded( + flex: 2, + child: ElevatedButton( + onPressed: () { + if (isLastStep) { + debugPrint('Form Submitted'); + } else { + formController.nextStep(state); + onStateChanged?.call(stepperKey, formController.currentStep.value); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + minimumSize: Size(0, 40.h), + ), + child: Text(isLastStep ? 'ثبت' : 'بعدی'), + ), + ), + ], + ), + ); + }); + } + + // =========================================================================== + // HELPERS + // =========================================================================== + + CrossAxisAlignment _parseCrossAxis(String? val) { + switch (val) { case 'start': return CrossAxisAlignment.start; case 'end': return CrossAxisAlignment.end; - case 'center': - return CrossAxisAlignment.center; case 'stretch': return CrossAxisAlignment.stretch; default: @@ -657,939 +501,18 @@ class SDUIFormWidget extends StatelessWidget { } } - MainAxisAlignment _parseMainAxisAlignment(String value) { - switch (value) { + MainAxisAlignment _parseMainAxis(String? val) { + switch (val) { case 'start': return MainAxisAlignment.start; case 'end': return MainAxisAlignment.end; - case 'center': - return MainAxisAlignment.center; case 'spaceBetween': return MainAxisAlignment.spaceBetween; case 'spaceAround': return MainAxisAlignment.spaceAround; - case 'spaceEvenly': - return MainAxisAlignment.spaceEvenly; default: - return MainAxisAlignment.start; + return MainAxisAlignment.center; } } - - Widget _buildStepper(StepperSDUIModel data, List? children) { - try { - return StepperSDUI(model: data, state: state); - } catch (e, stackTrace) { - iLog('Error building stepper: $e'); - iLog('Stack trace: $stackTrace'); - return Container( - padding: EdgeInsets.symmetric(horizontal: 16), - color: Colors.orange.withAlpha(10), - child: Text('Stepper Error: $e'), - ); - } - } - - Widget _buildPageView( - PageViewSDUIModel data, - List children, - ) { - try { - // Get PageController from map if key is provided - PageController? pageController; - if (data.key != null && - data.key!.isNotEmpty && - pageControllers != null && - pageControllers!.containsKey(data.key!)) { - pageController = pageControllers![data.key!]; - } - - // Build children - final pageChildren = children.map((child) { - try { - return _buildWidget(child); - } catch (e) { - iLog('Error building page_view child: $e'); - return Container( - padding: EdgeInsets.all(8), - color: Colors.yellow.withAlpha(10), - child: Text('Child Error'), - ); - } - }).toList(); - - return PageViewSDUI( - model: data, - controller: pageController, - children: pageChildren, - state: state, - controllers: controllers, - onStateChanged: onStateChanged, - images: images, - onImagesChanged: onImagesChanged, - ); - } catch (e, stackTrace) { - iLog('Error building page_view: $e'); - iLog('Stack trace: $stackTrace'); - return Container( - padding: EdgeInsets.all(16), - color: Colors.blue.withAlpha(10), - child: Text('PageView Error: $e'), - ); - } - } - - bool _evaluateVisibleCondition(String condition) { - if (state == null) { - // If state is null, return true for first step (0) by default - // This allows the form to work even without state initialization - if (condition.contains('activeStepperIndex == 0')) { - return true; - } - return false; - } - - try { - // Simple condition evaluation - // Supports: variable == value - - if (condition.contains(' == ')) { - final parts = condition.split(' == '); - if (parts.length == 2) { - final variable = parts[0].trim(); - var value = parts[1].trim(); - - // Remove quotes if present - if ((value.startsWith("'") && value.endsWith("'")) || - (value.startsWith('"') && value.endsWith('"'))) { - value = value.substring(1, value.length - 1); - } - - final stateValue = state![variable]; - if (stateValue == null) { - // If variable doesn't exist in state, default to showing first step (0) - if (variable == 'activeStepperIndex' && value == '0') { - return true; - } - return false; - } - - // Handle int comparison - final intValue = int.tryParse(value); - if (intValue != null) { - if (stateValue is int) { - return stateValue == intValue; - } - if (stateValue is num) { - return stateValue.toInt() == intValue; - } - } - - // Handle string comparison - return stateValue.toString() == value; - } - } - - // If condition format is not recognized, return false - iLog('Unsupported visible_condition format: $condition'); - return false; - } catch (e) { - iLog('Error evaluating visible_condition: $e'); - return false; - } - } - - // Find stepper in the widget tree - _StepperInfo? _findStepperInTree(SDUIWidgetModel widgetModel) { - // Check if current widget is a stepper - return widgetModel.maybeWhen( - stepper: (data, children, visible, visibleCondition) { - try { - return _StepperInfo( - stepperModel: widgetModel, - stepperData: data, - parentModel: null, - ); - } catch (e) { - iLog('Error parsing stepper model: $e'); - return null; - } - }, - orElse: () => null, - ) ?? - // Check in children - widgetModel.maybeWhen( - column: - ( - children, - spacing, - mainAxisSize, - crossAxisAlignment, - visible, - paddingHorizontal, - paddingVertical, - visibleCondition, - ) { - for (var child in children) { - final stepperInfo = _findStepperInTree(child); - if (stepperInfo != null) { - return _StepperInfo( - stepperModel: stepperInfo.stepperModel, - stepperData: stepperInfo.stepperData, - parentModel: widgetModel, - ); - } - } - return null; - }, - row: - ( - children, - spacing, - mainAxisAlignment, - visible, - visibleCondition, - ) { - for (var child in children) { - final stepperInfo = _findStepperInTree(child); - if (stepperInfo != null) { - return _StepperInfo( - stepperModel: stepperInfo.stepperModel, - stepperData: stepperInfo.stepperData, - parentModel: widgetModel, - ); - } - } - return null; - }, - cardLabelItem: (data, child, children, visible, visibleCondition) { - if (child != null) { - final stepperInfo = _findStepperInTree(child); - if (stepperInfo != null) { - return _StepperInfo( - stepperModel: stepperInfo.stepperModel, - stepperData: stepperInfo.stepperData, - parentModel: widgetModel, - ); - } - } - if (children != null) { - for (var child in children) { - final stepperInfo = _findStepperInTree(child); - if (stepperInfo != null) { - return _StepperInfo( - stepperModel: stepperInfo.stepperModel, - stepperData: stepperInfo.stepperData, - parentModel: widgetModel, - ); - } - } - } - return null; - }, - stepper: (data, children, visible, visibleCondition) { - if (children != null) { - for (var child in children) { - final stepperInfo = _findStepperInTree(child); - if (stepperInfo != null) { - return _StepperInfo( - stepperModel: stepperInfo.stepperModel, - stepperData: stepperInfo.stepperData, - parentModel: widgetModel, - ); - } - } - } - return null; - }, - pageView: (data, children, visible, visibleCondition) { - for (var child in children) { - final stepperInfo = _findStepperInTree(child); - if (stepperInfo != null) { - return _StepperInfo( - stepperModel: stepperInfo.stepperModel, - stepperData: stepperInfo.stepperData, - parentModel: widgetModel, - ); - } - } - return null; - }, - orElse: () => null, - ); - } - - // Count all steppers in the widget tree to determine total steps - int _countSteppersInTree(SDUIWidgetModel widgetModel) { - int count = 0; - - // Check if current widget is a stepper - count += - widgetModel.maybeWhen( - stepper: (data, children, visible, visibleCondition) => 1, - orElse: () => 0, - ) ?? - 0; - - // Count steppers in children - count += - widgetModel.maybeWhen( - column: - ( - children, - spacing, - mainAxisSize, - crossAxisAlignment, - visible, - paddingHorizontal, - paddingVertical, - visibleCondition, - ) { - int childCount = 0; - for (var child in children) { - childCount += _countSteppersInTree(child); - } - return childCount; - }, - row: - ( - children, - spacing, - mainAxisAlignment, - visible, - visibleCondition, - ) { - int childCount = 0; - for (var child in children) { - childCount += _countSteppersInTree(child); - } - return childCount; - }, - cardLabelItem: (data, child, children, visible, visibleCondition) { - int childCount = 0; - if (child != null) { - childCount += _countSteppersInTree(child); - } - if (children != null) { - for (var child in children) { - childCount += _countSteppersInTree(child); - } - } - return childCount; - }, - stepper: (data, children, visible, visibleCondition) { - int childCount = 0; - if (children != null) { - for (var child in children) { - childCount += _countSteppersInTree(child); - } - } - return childCount; - }, - pageView: (data, children, visible, visibleCondition) { - int childCount = 0; - for (var child in children) { - childCount += _countSteppersInTree(child); - } - return childCount; - }, - orElse: () => 0, - ) ?? - 0; - - return count; - } - - // Build layout with stepper - Widget _buildWithStepperLayout(_StepperInfo stepperInfo) { - final controllerTag = - 'sdui_form_stepper_${stepperInfo.stepperData.key ?? 'default'}'; - SDUIFormWidgetController formController; - - try { - formController = Get.find(tag: controllerTag); - } catch (_) { - formController = Get.put(SDUIFormWidgetController(), tag: controllerTag); - } - - // Calculate totalSteps: use from model, or count steppers, or count unique step indices - int totalSteps = stepperInfo.stepperData.totalSteps ?? 0; - if (totalSteps == 0) { - // Count all steppers in the tree - totalSteps = _countSteppersInTree(model); - // If still 0, count unique step indices from visible_condition - if (totalSteps == 0) { - final stepIndices = _collectStepIndicesFromVisibleConditions(model); - if (stepIndices.isNotEmpty) { - // Return max index + 1 (since indices are 0-based) - totalSteps = stepIndices.reduce((a, b) => a > b ? a : b) + 1; - } - } - // Default to 5 if still 0 - if (totalSteps == 0) { - totalSteps = 5; - } - } - - final stepperKey = stepperInfo.stepperData.key ?? 'activeStepperIndex'; - formController.initializeStepper(totalSteps, stepperKey); - - if (state != null && !state!.containsKey(stepperKey)) { - state![stepperKey] = 0; - } - - // Create scroll controller for auto-scrolling to active step - final scrollController = ScrollController(); - - // Map to store GlobalKeys for each step widget - final stepKeys = {}; - for (int i = 0; i < totalSteps; i++) { - stepKeys[i] = GlobalKey(); - } - - // Build content excluding stepper widgets with keys - final contentWidgets = _buildContentWithoutStepper( - model, - stepperInfo, - stepKeys: stepKeys, - ); - - // Listen to step changes and scroll to active step - return Obx(() { - final currentStep = formController.currentStep.value; - - // Scroll to active step after build - WidgetsBinding.instance.addPostFrameCallback((_) { - if (stepKeys.containsKey(currentStep)) { - final key = stepKeys[currentStep]!; - final context = key.currentContext; - if (context != null && scrollController.hasClients) { - Scrollable.ensureVisible( - context, - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - ); - } - } - }); - - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), - child: _buildStepper(stepperInfo.stepperData, null), - ), - Flexible( - child: SingleChildScrollView( - controller: scrollController, - physics: BouncingScrollPhysics(), - padding: EdgeInsets.all(16.w), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - spacing: 30.h, - children: contentWidgets, - ), - ), - ), - _buildNavigationButtons(formController, stepperKey), - ], - ); - }); - } - - // Collect unique step indices from visible_condition in the tree - Set _collectStepIndicesFromVisibleConditions( - SDUIWidgetModel widgetModel, - ) { - final stepIndices = {}; - - // Check current widget's visible_condition - widgetModel.maybeWhen( - cardLabelItem: (data, child, children, visible, visibleCondition) { - if (visibleCondition != null && - visibleCondition.contains('activeStepperIndex ==')) { - final match = RegExp( - r'activeStepperIndex\s*==\s*(\d+)', - ).firstMatch(visibleCondition); - if (match != null) { - final stepIndex = int.tryParse(match.group(1) ?? ''); - if (stepIndex != null) { - stepIndices.add(stepIndex); - } - } - } - }, - orElse: () {}, - ); - - // Check children - widgetModel.maybeWhen( - column: - ( - children, - spacing, - mainAxisSize, - crossAxisAlignment, - visible, - paddingHorizontal, - paddingVertical, - visibleCondition, - ) { - for (var child in children) { - stepIndices.addAll( - _collectStepIndicesFromVisibleConditions(child), - ); - } - }, - row: (children, spacing, mainAxisAlignment, visible, visibleCondition) { - for (var child in children) { - stepIndices.addAll(_collectStepIndicesFromVisibleConditions(child)); - } - }, - cardLabelItem: (data, child, children, visible, visibleCondition) { - if (child != null) { - stepIndices.addAll(_collectStepIndicesFromVisibleConditions(child)); - } - if (children != null) { - for (var child in children) { - stepIndices.addAll(_collectStepIndicesFromVisibleConditions(child)); - } - } - }, - stepper: (data, children, visible, visibleCondition) { - if (children != null) { - for (var child in children) { - stepIndices.addAll(_collectStepIndicesFromVisibleConditions(child)); - } - } - }, - pageView: (data, children, visible, visibleCondition) { - for (var child in children) { - stepIndices.addAll(_collectStepIndicesFromVisibleConditions(child)); - } - }, - orElse: () {}, - ); - - return stepIndices; - } - - // Build layout without stepper (scrollable column) - // ignore: unused_element - Widget _buildWithoutStepper() { - return SingleChildScrollView( - physics: BouncingScrollPhysics(), - padding: EdgeInsets.all(16.w), - child: _buildWidget(model), - ); - } - - // Build content widgets excluding the stepper - List _buildContentWithoutStepper( - SDUIWidgetModel widgetModel, - _StepperInfo stepperInfo, { - Map? stepKeys, - }) { - final widgets = []; - final stepperKey = stepperInfo.stepperData.key; - - // Helper to check if a model is the stepper we want to exclude - bool isStepperToExclude(SDUIWidgetModel model) { - return model.maybeWhen( - stepper: (data, children, visible, visibleCondition) { - return data.key == stepperKey; - }, - orElse: () => false, - ); - } - - // Helper to extract step index from visible_condition - int? extractStepIndex(String? visibleCondition) { - if (visibleCondition == null) return null; - final match = RegExp( - r'activeStepperIndex\s*==\s*(\d+)', - ).firstMatch(visibleCondition); - if (match != null) { - return int.tryParse(match.group(1) ?? ''); - } - return null; - } - - // Helper to wrap widget with key if step index exists - Widget wrapWithKeyIfNeeded(Widget widget, int? stepIndex) { - if (stepIndex != null && - stepKeys != null && - stepKeys.containsKey(stepIndex)) { - return Container(key: stepKeys[stepIndex], child: widget); - } - return widget; - } - - return widgetModel.maybeWhen( - column: - ( - children, - spacing, - mainAxisSize, - crossAxisAlignment, - visible, - paddingHorizontal, - paddingVertical, - visibleCondition, - ) { - for (var child in children) { - if (isStepperToExclude(child)) { - // If stepper has children, build those children - child.maybeWhen( - stepper: (data, stepperChildren, visible, visibleCondition) { - if (stepperChildren != null) { - int childIndex = 0; - for (var stepperChild in stepperChildren) { - // Extract step index from stepper child's visible_condition - final stepIndex = stepperChild.maybeWhen( - cardLabelItem: - ( - data, - child, - children, - visible, - visibleCondition, - ) { - return extractStepIndex(visibleCondition); - }, - orElse: () => childIndex, - ); - - widgets.add( - wrapWithKeyIfNeeded( - SDUIFormWidget( - model: stepperChild, - controllers: controllers, - state: state, - onStateChanged: onStateChanged, - images: images, - onImagesChanged: onImagesChanged, - pageControllers: pageControllers, - ), - stepIndex, - ), - ); - childIndex++; - } - } - }, - orElse: () {}, - ); - continue; - } - widgets.add( - SDUIFormWidget( - model: child, - controllers: controllers, - state: state, - onStateChanged: onStateChanged, - images: images, - onImagesChanged: onImagesChanged, - pageControllers: pageControllers, - ), - ); - } - return widgets; - }, - row: - ( - children, - spacing, - mainAxisAlignment, - visible, - visibleCondition, - ) { - for (var child in children) { - if (isStepperToExclude(child)) { - child.maybeWhen( - stepper: - (data, stepperChildren, visible, visibleCondition) { - if (stepperChildren != null) { - for (var stepperChild in stepperChildren) { - widgets.add( - SDUIFormWidget( - model: stepperChild, - controllers: controllers, - state: state, - onStateChanged: onStateChanged, - images: images, - onImagesChanged: onImagesChanged, - pageControllers: pageControllers, - ), - ); - } - } - }, - orElse: () {}, - ); - continue; - } - widgets.add( - SDUIFormWidget( - model: child, - controllers: controllers, - state: state, - onStateChanged: onStateChanged, - images: images, - onImagesChanged: onImagesChanged, - pageControllers: pageControllers, - ), - ); - } - return widgets; - }, - cardLabelItem: (data, child, children, visible, visibleCondition) { - final stepIndex = extractStepIndex(visibleCondition); - - if (child != null) { - if (isStepperToExclude(child)) { - child.maybeWhen( - stepper: - ( - stepperData, - stepperChildren, - visible, - visibleCondition, - ) { - if (stepperChildren != null) { - for (var stepperChild in stepperChildren) { - widgets.add( - wrapWithKeyIfNeeded( - SDUIFormWidget( - model: stepperChild, - controllers: controllers, - state: state, - onStateChanged: onStateChanged, - images: images, - onImagesChanged: onImagesChanged, - pageControllers: pageControllers, - ), - stepIndex, - ), - ); - } - } - }, - orElse: () {}, - ); - } else { - widgets.add( - wrapWithKeyIfNeeded( - SDUIFormWidget( - model: child, - controllers: controllers, - state: state, - onStateChanged: onStateChanged, - images: images, - onImagesChanged: onImagesChanged, - pageControllers: pageControllers, - ), - stepIndex, - ), - ); - } - } - if (children != null) { - for (var child in children) { - if (isStepperToExclude(child)) { - child.maybeWhen( - stepper: - ( - stepperData, - stepperChildren, - visible, - visibleCondition, - ) { - if (stepperChildren != null) { - for (var stepperChild in stepperChildren) { - widgets.add( - wrapWithKeyIfNeeded( - SDUIFormWidget( - model: stepperChild, - controllers: controllers, - state: state, - onStateChanged: onStateChanged, - images: images, - onImagesChanged: onImagesChanged, - pageControllers: pageControllers, - ), - stepIndex, - ), - ); - } - } - }, - orElse: () {}, - ); - continue; - } - widgets.add( - wrapWithKeyIfNeeded( - SDUIFormWidget( - model: child, - controllers: controllers, - state: state, - onStateChanged: onStateChanged, - images: images, - onImagesChanged: onImagesChanged, - pageControllers: pageControllers, - ), - stepIndex, - ), - ); - } - } - return widgets; - }, - stepper: (data, children, visible, visibleCondition) { - if (children != null) { - for (var child in children) { - widgets.add( - SDUIFormWidget( - model: child, - controllers: controllers, - state: state, - onStateChanged: onStateChanged, - images: images, - onImagesChanged: onImagesChanged, - pageControllers: pageControllers, - ), - ); - } - } - return widgets; - }, - pageView: (data, children, visible, visibleCondition) { - for (var child in children) { - if (isStepperToExclude(child)) { - child.maybeWhen( - stepper: - ( - stepperData, - stepperChildren, - visible, - visibleCondition, - ) { - if (stepperChildren != null) { - for (var stepperChild in stepperChildren) { - widgets.add( - SDUIFormWidget( - model: stepperChild, - controllers: controllers, - state: state, - onStateChanged: onStateChanged, - images: images, - onImagesChanged: onImagesChanged, - pageControllers: pageControllers, - ), - ); - } - } - }, - orElse: () {}, - ); - continue; - } - widgets.add( - SDUIFormWidget( - model: child, - controllers: controllers, - state: state, - onStateChanged: onStateChanged, - images: images, - onImagesChanged: onImagesChanged, - pageControllers: pageControllers, - ), - ); - } - return widgets; - }, - orElse: () { - // If no children, build the widget itself (but not if it's the stepper) - if (!isStepperToExclude(widgetModel)) { - widgets.add(_buildWidget(widgetModel)); - } - return widgets; - }, - ) ?? - widgets; - } - - // Build navigation buttons - Widget _buildNavigationButtons( - SDUIFormWidgetController formController, - String stepperKey, - ) { - return Obx(() { - final isLastStep = formController.isLastStep; - final canGoPrevious = formController.canGoPrevious; - - return Container( - padding: EdgeInsets.all(8.w), - decoration: BoxDecoration( - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.black.withAlpha(5), - blurRadius: 10, - offset: Offset(0, -2), - ), - ], - ), - child: Row( - spacing: 10.w, - children: [ - Expanded( - child: ROutlinedElevated( - height: 40.h, - enabled: canGoPrevious, - onPressed: canGoPrevious - ? () { - formController.previousStep(state); - if (onStateChanged != null) { - onStateChanged!( - stepperKey, - formController.currentStep.value, - ); - } - } - : null, - child: Text('قبلی'), - borderColor: AppColor.blueNormal, - ), - ), - Expanded( - flex: 2, - child: RElevated( - height: 40.h, - onPressed: () { - if (isLastStep) { - // Handle final submission if needed - iLog('Last step reached - form submission'); - } else { - formController.nextStep(state); - if (onStateChanged != null) { - onStateChanged!( - stepperKey, - formController.currentStep.value, - ); - } - } - }, - child: Text(isLastStep ? 'ثبت' : 'بعدی'), - backgroundColor: AppColor.blueNormal, - ), - ), - ], - ), - ); - }); - } } diff --git a/packages/chicken/lib/presentation/widget/sdui/widgets/text_form_filed/model/text_form_field_sdui_model.dart b/packages/chicken/lib/presentation/widget/sdui/widgets/text_form_filed/model/text_form_field_sdui_model.dart index 4d90416..36b29cf 100644 --- a/packages/chicken/lib/presentation/widget/sdui/widgets/text_form_filed/model/text_form_field_sdui_model.dart +++ b/packages/chicken/lib/presentation/widget/sdui/widgets/text_form_filed/model/text_form_field_sdui_model.dart @@ -11,14 +11,14 @@ abstract class TextFormFieldSDUIModel with _$TextFormFieldSDUIModel { String? hintText, String? variant, String? keyboardType, - String? value, + dynamic value, int? maxLength, int? minLine, int? maxLine, bool? required, bool? enabled, bool? readonly, - bool? commaSperator, + bool? commaSeparator, bool? decimal, int? decimalPlaces, String? type, diff --git a/packages/chicken/lib/presentation/widget/sdui/widgets/text_form_filed/model/text_form_field_sdui_model.freezed.dart b/packages/chicken/lib/presentation/widget/sdui/widgets/text_form_filed/model/text_form_field_sdui_model.freezed.dart index 5129a0f..d11fb31 100644 --- a/packages/chicken/lib/presentation/widget/sdui/widgets/text_form_filed/model/text_form_field_sdui_model.freezed.dart +++ b/packages/chicken/lib/presentation/widget/sdui/widgets/text_form_filed/model/text_form_field_sdui_model.freezed.dart @@ -15,7 +15,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$TextFormFieldSDUIModel { - String? get key; String? get label; String? get hintText; String? get variant; String? get keyboardType; String? get value; int? get maxLength; int? get minLine; int? get maxLine; bool? get required; bool? get enabled; bool? get readonly; bool? get commaSperator; bool? get decimal; int? get decimalPlaces; String? get type; + String? get key; String? get label; String? get hintText; String? get variant; String? get keyboardType; dynamic get value; int? get maxLength; int? get minLine; int? get maxLine; bool? get required; bool? get enabled; bool? get readonly; bool? get commaSeparator; bool? get decimal; int? get decimalPlaces; String? get type; /// Create a copy of TextFormFieldSDUIModel /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -28,16 +28,16 @@ $TextFormFieldSDUIModelCopyWith get copyWith => _$TextFo @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is TextFormFieldSDUIModel&&(identical(other.key, key) || other.key == key)&&(identical(other.label, label) || other.label == label)&&(identical(other.hintText, hintText) || other.hintText == hintText)&&(identical(other.variant, variant) || other.variant == variant)&&(identical(other.keyboardType, keyboardType) || other.keyboardType == keyboardType)&&(identical(other.value, value) || other.value == value)&&(identical(other.maxLength, maxLength) || other.maxLength == maxLength)&&(identical(other.minLine, minLine) || other.minLine == minLine)&&(identical(other.maxLine, maxLine) || other.maxLine == maxLine)&&(identical(other.required, required) || other.required == required)&&(identical(other.enabled, enabled) || other.enabled == enabled)&&(identical(other.readonly, readonly) || other.readonly == readonly)&&(identical(other.commaSperator, commaSperator) || other.commaSperator == commaSperator)&&(identical(other.decimal, decimal) || other.decimal == decimal)&&(identical(other.decimalPlaces, decimalPlaces) || other.decimalPlaces == decimalPlaces)&&(identical(other.type, type) || other.type == type)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is TextFormFieldSDUIModel&&(identical(other.key, key) || other.key == key)&&(identical(other.label, label) || other.label == label)&&(identical(other.hintText, hintText) || other.hintText == hintText)&&(identical(other.variant, variant) || other.variant == variant)&&(identical(other.keyboardType, keyboardType) || other.keyboardType == keyboardType)&&const DeepCollectionEquality().equals(other.value, value)&&(identical(other.maxLength, maxLength) || other.maxLength == maxLength)&&(identical(other.minLine, minLine) || other.minLine == minLine)&&(identical(other.maxLine, maxLine) || other.maxLine == maxLine)&&(identical(other.required, required) || other.required == required)&&(identical(other.enabled, enabled) || other.enabled == enabled)&&(identical(other.readonly, readonly) || other.readonly == readonly)&&(identical(other.commaSeparator, commaSeparator) || other.commaSeparator == commaSeparator)&&(identical(other.decimal, decimal) || other.decimal == decimal)&&(identical(other.decimalPlaces, decimalPlaces) || other.decimalPlaces == decimalPlaces)&&(identical(other.type, type) || other.type == type)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,key,label,hintText,variant,keyboardType,value,maxLength,minLine,maxLine,required,enabled,readonly,commaSperator,decimal,decimalPlaces,type); +int get hashCode => Object.hash(runtimeType,key,label,hintText,variant,keyboardType,const DeepCollectionEquality().hash(value),maxLength,minLine,maxLine,required,enabled,readonly,commaSeparator,decimal,decimalPlaces,type); @override String toString() { - return 'TextFormFieldSDUIModel(key: $key, label: $label, hintText: $hintText, variant: $variant, keyboardType: $keyboardType, value: $value, maxLength: $maxLength, minLine: $minLine, maxLine: $maxLine, required: $required, enabled: $enabled, readonly: $readonly, commaSperator: $commaSperator, decimal: $decimal, decimalPlaces: $decimalPlaces, type: $type)'; + return 'TextFormFieldSDUIModel(key: $key, label: $label, hintText: $hintText, variant: $variant, keyboardType: $keyboardType, value: $value, maxLength: $maxLength, minLine: $minLine, maxLine: $maxLine, required: $required, enabled: $enabled, readonly: $readonly, commaSeparator: $commaSeparator, decimal: $decimal, decimalPlaces: $decimalPlaces, type: $type)'; } @@ -48,7 +48,7 @@ abstract mixin class $TextFormFieldSDUIModelCopyWith<$Res> { factory $TextFormFieldSDUIModelCopyWith(TextFormFieldSDUIModel value, $Res Function(TextFormFieldSDUIModel) _then) = _$TextFormFieldSDUIModelCopyWithImpl; @useResult $Res call({ - String? key, String? label, String? hintText, String? variant, String? keyboardType, String? value, int? maxLength, int? minLine, int? maxLine, bool? required, bool? enabled, bool? readonly, bool? commaSperator, bool? decimal, int? decimalPlaces, String? type + String? key, String? label, String? hintText, String? variant, String? keyboardType, dynamic value, int? maxLength, int? minLine, int? maxLine, bool? required, bool? enabled, bool? readonly, bool? commaSeparator, bool? decimal, int? decimalPlaces, String? type }); @@ -65,7 +65,7 @@ class _$TextFormFieldSDUIModelCopyWithImpl<$Res> /// Create a copy of TextFormFieldSDUIModel /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? key = freezed,Object? label = freezed,Object? hintText = freezed,Object? variant = freezed,Object? keyboardType = freezed,Object? value = freezed,Object? maxLength = freezed,Object? minLine = freezed,Object? maxLine = freezed,Object? required = freezed,Object? enabled = freezed,Object? readonly = freezed,Object? commaSperator = freezed,Object? decimal = freezed,Object? decimalPlaces = freezed,Object? type = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? key = freezed,Object? label = freezed,Object? hintText = freezed,Object? variant = freezed,Object? keyboardType = freezed,Object? value = freezed,Object? maxLength = freezed,Object? minLine = freezed,Object? maxLine = freezed,Object? required = freezed,Object? enabled = freezed,Object? readonly = freezed,Object? commaSeparator = freezed,Object? decimal = freezed,Object? decimalPlaces = freezed,Object? type = freezed,}) { return _then(_self.copyWith( key: freezed == key ? _self.key : key // ignore: cast_nullable_to_non_nullable as String?,label: freezed == label ? _self.label : label // ignore: cast_nullable_to_non_nullable @@ -73,13 +73,13 @@ as String?,hintText: freezed == hintText ? _self.hintText : hintText // ignore: as String?,variant: freezed == variant ? _self.variant : variant // ignore: cast_nullable_to_non_nullable as String?,keyboardType: freezed == keyboardType ? _self.keyboardType : keyboardType // ignore: cast_nullable_to_non_nullable as String?,value: freezed == value ? _self.value : value // ignore: cast_nullable_to_non_nullable -as String?,maxLength: freezed == maxLength ? _self.maxLength : maxLength // ignore: cast_nullable_to_non_nullable +as dynamic,maxLength: freezed == maxLength ? _self.maxLength : maxLength // ignore: cast_nullable_to_non_nullable as int?,minLine: freezed == minLine ? _self.minLine : minLine // ignore: cast_nullable_to_non_nullable as int?,maxLine: freezed == maxLine ? _self.maxLine : maxLine // ignore: cast_nullable_to_non_nullable as int?,required: freezed == required ? _self.required : required // ignore: cast_nullable_to_non_nullable as bool?,enabled: freezed == enabled ? _self.enabled : enabled // ignore: cast_nullable_to_non_nullable as bool?,readonly: freezed == readonly ? _self.readonly : readonly // ignore: cast_nullable_to_non_nullable -as bool?,commaSperator: freezed == commaSperator ? _self.commaSperator : commaSperator // ignore: cast_nullable_to_non_nullable +as bool?,commaSeparator: freezed == commaSeparator ? _self.commaSeparator : commaSeparator // ignore: cast_nullable_to_non_nullable as bool?,decimal: freezed == decimal ? _self.decimal : decimal // ignore: cast_nullable_to_non_nullable as bool?,decimalPlaces: freezed == decimalPlaces ? _self.decimalPlaces : decimalPlaces // ignore: cast_nullable_to_non_nullable as int?,type: freezed == type ? _self.type : type // ignore: cast_nullable_to_non_nullable @@ -168,10 +168,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String? key, String? label, String? hintText, String? variant, String? keyboardType, String? value, int? maxLength, int? minLine, int? maxLine, bool? required, bool? enabled, bool? readonly, bool? commaSperator, bool? decimal, int? decimalPlaces, String? type)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String? key, String? label, String? hintText, String? variant, String? keyboardType, dynamic value, int? maxLength, int? minLine, int? maxLine, bool? required, bool? enabled, bool? readonly, bool? commaSeparator, bool? decimal, int? decimalPlaces, String? type)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _TextFormFieldSDUIModel() when $default != null: -return $default(_that.key,_that.label,_that.hintText,_that.variant,_that.keyboardType,_that.value,_that.maxLength,_that.minLine,_that.maxLine,_that.required,_that.enabled,_that.readonly,_that.commaSperator,_that.decimal,_that.decimalPlaces,_that.type);case _: +return $default(_that.key,_that.label,_that.hintText,_that.variant,_that.keyboardType,_that.value,_that.maxLength,_that.minLine,_that.maxLine,_that.required,_that.enabled,_that.readonly,_that.commaSeparator,_that.decimal,_that.decimalPlaces,_that.type);case _: return orElse(); } @@ -189,10 +189,10 @@ return $default(_that.key,_that.label,_that.hintText,_that.variant,_that.keyboar /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String? key, String? label, String? hintText, String? variant, String? keyboardType, String? value, int? maxLength, int? minLine, int? maxLine, bool? required, bool? enabled, bool? readonly, bool? commaSperator, bool? decimal, int? decimalPlaces, String? type) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String? key, String? label, String? hintText, String? variant, String? keyboardType, dynamic value, int? maxLength, int? minLine, int? maxLine, bool? required, bool? enabled, bool? readonly, bool? commaSeparator, bool? decimal, int? decimalPlaces, String? type) $default,) {final _that = this; switch (_that) { case _TextFormFieldSDUIModel(): -return $default(_that.key,_that.label,_that.hintText,_that.variant,_that.keyboardType,_that.value,_that.maxLength,_that.minLine,_that.maxLine,_that.required,_that.enabled,_that.readonly,_that.commaSperator,_that.decimal,_that.decimalPlaces,_that.type);case _: +return $default(_that.key,_that.label,_that.hintText,_that.variant,_that.keyboardType,_that.value,_that.maxLength,_that.minLine,_that.maxLine,_that.required,_that.enabled,_that.readonly,_that.commaSeparator,_that.decimal,_that.decimalPlaces,_that.type);case _: throw StateError('Unexpected subclass'); } @@ -209,10 +209,10 @@ return $default(_that.key,_that.label,_that.hintText,_that.variant,_that.keyboar /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? key, String? label, String? hintText, String? variant, String? keyboardType, String? value, int? maxLength, int? minLine, int? maxLine, bool? required, bool? enabled, bool? readonly, bool? commaSperator, bool? decimal, int? decimalPlaces, String? type)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? key, String? label, String? hintText, String? variant, String? keyboardType, dynamic value, int? maxLength, int? minLine, int? maxLine, bool? required, bool? enabled, bool? readonly, bool? commaSeparator, bool? decimal, int? decimalPlaces, String? type)? $default,) {final _that = this; switch (_that) { case _TextFormFieldSDUIModel() when $default != null: -return $default(_that.key,_that.label,_that.hintText,_that.variant,_that.keyboardType,_that.value,_that.maxLength,_that.minLine,_that.maxLine,_that.required,_that.enabled,_that.readonly,_that.commaSperator,_that.decimal,_that.decimalPlaces,_that.type);case _: +return $default(_that.key,_that.label,_that.hintText,_that.variant,_that.keyboardType,_that.value,_that.maxLength,_that.minLine,_that.maxLine,_that.required,_that.enabled,_that.readonly,_that.commaSeparator,_that.decimal,_that.decimalPlaces,_that.type);case _: return null; } @@ -224,7 +224,7 @@ return $default(_that.key,_that.label,_that.hintText,_that.variant,_that.keyboar @JsonSerializable() class _TextFormFieldSDUIModel implements TextFormFieldSDUIModel { - const _TextFormFieldSDUIModel({this.key, this.label, this.hintText, this.variant, this.keyboardType, this.value, this.maxLength, this.minLine, this.maxLine, this.required, this.enabled, this.readonly, this.commaSperator, this.decimal, this.decimalPlaces, this.type}); + const _TextFormFieldSDUIModel({this.key, this.label, this.hintText, this.variant, this.keyboardType, this.value, this.maxLength, this.minLine, this.maxLine, this.required, this.enabled, this.readonly, this.commaSeparator, this.decimal, this.decimalPlaces, this.type}); factory _TextFormFieldSDUIModel.fromJson(Map json) => _$TextFormFieldSDUIModelFromJson(json); @override final String? key; @@ -232,14 +232,14 @@ class _TextFormFieldSDUIModel implements TextFormFieldSDUIModel { @override final String? hintText; @override final String? variant; @override final String? keyboardType; -@override final String? value; +@override final dynamic value; @override final int? maxLength; @override final int? minLine; @override final int? maxLine; @override final bool? required; @override final bool? enabled; @override final bool? readonly; -@override final bool? commaSperator; +@override final bool? commaSeparator; @override final bool? decimal; @override final int? decimalPlaces; @override final String? type; @@ -257,16 +257,16 @@ Map toJson() { @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _TextFormFieldSDUIModel&&(identical(other.key, key) || other.key == key)&&(identical(other.label, label) || other.label == label)&&(identical(other.hintText, hintText) || other.hintText == hintText)&&(identical(other.variant, variant) || other.variant == variant)&&(identical(other.keyboardType, keyboardType) || other.keyboardType == keyboardType)&&(identical(other.value, value) || other.value == value)&&(identical(other.maxLength, maxLength) || other.maxLength == maxLength)&&(identical(other.minLine, minLine) || other.minLine == minLine)&&(identical(other.maxLine, maxLine) || other.maxLine == maxLine)&&(identical(other.required, required) || other.required == required)&&(identical(other.enabled, enabled) || other.enabled == enabled)&&(identical(other.readonly, readonly) || other.readonly == readonly)&&(identical(other.commaSperator, commaSperator) || other.commaSperator == commaSperator)&&(identical(other.decimal, decimal) || other.decimal == decimal)&&(identical(other.decimalPlaces, decimalPlaces) || other.decimalPlaces == decimalPlaces)&&(identical(other.type, type) || other.type == type)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _TextFormFieldSDUIModel&&(identical(other.key, key) || other.key == key)&&(identical(other.label, label) || other.label == label)&&(identical(other.hintText, hintText) || other.hintText == hintText)&&(identical(other.variant, variant) || other.variant == variant)&&(identical(other.keyboardType, keyboardType) || other.keyboardType == keyboardType)&&const DeepCollectionEquality().equals(other.value, value)&&(identical(other.maxLength, maxLength) || other.maxLength == maxLength)&&(identical(other.minLine, minLine) || other.minLine == minLine)&&(identical(other.maxLine, maxLine) || other.maxLine == maxLine)&&(identical(other.required, required) || other.required == required)&&(identical(other.enabled, enabled) || other.enabled == enabled)&&(identical(other.readonly, readonly) || other.readonly == readonly)&&(identical(other.commaSeparator, commaSeparator) || other.commaSeparator == commaSeparator)&&(identical(other.decimal, decimal) || other.decimal == decimal)&&(identical(other.decimalPlaces, decimalPlaces) || other.decimalPlaces == decimalPlaces)&&(identical(other.type, type) || other.type == type)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,key,label,hintText,variant,keyboardType,value,maxLength,minLine,maxLine,required,enabled,readonly,commaSperator,decimal,decimalPlaces,type); +int get hashCode => Object.hash(runtimeType,key,label,hintText,variant,keyboardType,const DeepCollectionEquality().hash(value),maxLength,minLine,maxLine,required,enabled,readonly,commaSeparator,decimal,decimalPlaces,type); @override String toString() { - return 'TextFormFieldSDUIModel(key: $key, label: $label, hintText: $hintText, variant: $variant, keyboardType: $keyboardType, value: $value, maxLength: $maxLength, minLine: $minLine, maxLine: $maxLine, required: $required, enabled: $enabled, readonly: $readonly, commaSperator: $commaSperator, decimal: $decimal, decimalPlaces: $decimalPlaces, type: $type)'; + return 'TextFormFieldSDUIModel(key: $key, label: $label, hintText: $hintText, variant: $variant, keyboardType: $keyboardType, value: $value, maxLength: $maxLength, minLine: $minLine, maxLine: $maxLine, required: $required, enabled: $enabled, readonly: $readonly, commaSeparator: $commaSeparator, decimal: $decimal, decimalPlaces: $decimalPlaces, type: $type)'; } @@ -277,7 +277,7 @@ abstract mixin class _$TextFormFieldSDUIModelCopyWith<$Res> implements $TextForm factory _$TextFormFieldSDUIModelCopyWith(_TextFormFieldSDUIModel value, $Res Function(_TextFormFieldSDUIModel) _then) = __$TextFormFieldSDUIModelCopyWithImpl; @override @useResult $Res call({ - String? key, String? label, String? hintText, String? variant, String? keyboardType, String? value, int? maxLength, int? minLine, int? maxLine, bool? required, bool? enabled, bool? readonly, bool? commaSperator, bool? decimal, int? decimalPlaces, String? type + String? key, String? label, String? hintText, String? variant, String? keyboardType, dynamic value, int? maxLength, int? minLine, int? maxLine, bool? required, bool? enabled, bool? readonly, bool? commaSeparator, bool? decimal, int? decimalPlaces, String? type }); @@ -294,7 +294,7 @@ class __$TextFormFieldSDUIModelCopyWithImpl<$Res> /// Create a copy of TextFormFieldSDUIModel /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? key = freezed,Object? label = freezed,Object? hintText = freezed,Object? variant = freezed,Object? keyboardType = freezed,Object? value = freezed,Object? maxLength = freezed,Object? minLine = freezed,Object? maxLine = freezed,Object? required = freezed,Object? enabled = freezed,Object? readonly = freezed,Object? commaSperator = freezed,Object? decimal = freezed,Object? decimalPlaces = freezed,Object? type = freezed,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? key = freezed,Object? label = freezed,Object? hintText = freezed,Object? variant = freezed,Object? keyboardType = freezed,Object? value = freezed,Object? maxLength = freezed,Object? minLine = freezed,Object? maxLine = freezed,Object? required = freezed,Object? enabled = freezed,Object? readonly = freezed,Object? commaSeparator = freezed,Object? decimal = freezed,Object? decimalPlaces = freezed,Object? type = freezed,}) { return _then(_TextFormFieldSDUIModel( key: freezed == key ? _self.key : key // ignore: cast_nullable_to_non_nullable as String?,label: freezed == label ? _self.label : label // ignore: cast_nullable_to_non_nullable @@ -302,13 +302,13 @@ as String?,hintText: freezed == hintText ? _self.hintText : hintText // ignore: as String?,variant: freezed == variant ? _self.variant : variant // ignore: cast_nullable_to_non_nullable as String?,keyboardType: freezed == keyboardType ? _self.keyboardType : keyboardType // ignore: cast_nullable_to_non_nullable as String?,value: freezed == value ? _self.value : value // ignore: cast_nullable_to_non_nullable -as String?,maxLength: freezed == maxLength ? _self.maxLength : maxLength // ignore: cast_nullable_to_non_nullable +as dynamic,maxLength: freezed == maxLength ? _self.maxLength : maxLength // ignore: cast_nullable_to_non_nullable as int?,minLine: freezed == minLine ? _self.minLine : minLine // ignore: cast_nullable_to_non_nullable as int?,maxLine: freezed == maxLine ? _self.maxLine : maxLine // ignore: cast_nullable_to_non_nullable as int?,required: freezed == required ? _self.required : required // ignore: cast_nullable_to_non_nullable as bool?,enabled: freezed == enabled ? _self.enabled : enabled // ignore: cast_nullable_to_non_nullable as bool?,readonly: freezed == readonly ? _self.readonly : readonly // ignore: cast_nullable_to_non_nullable -as bool?,commaSperator: freezed == commaSperator ? _self.commaSperator : commaSperator // ignore: cast_nullable_to_non_nullable +as bool?,commaSeparator: freezed == commaSeparator ? _self.commaSeparator : commaSeparator // ignore: cast_nullable_to_non_nullable as bool?,decimal: freezed == decimal ? _self.decimal : decimal // ignore: cast_nullable_to_non_nullable as bool?,decimalPlaces: freezed == decimalPlaces ? _self.decimalPlaces : decimalPlaces // ignore: cast_nullable_to_non_nullable as int?,type: freezed == type ? _self.type : type // ignore: cast_nullable_to_non_nullable diff --git a/packages/chicken/lib/presentation/widget/sdui/widgets/text_form_filed/model/text_form_field_sdui_model.g.dart b/packages/chicken/lib/presentation/widget/sdui/widgets/text_form_filed/model/text_form_field_sdui_model.g.dart index 4f7e44e..a3cf0b5 100644 --- a/packages/chicken/lib/presentation/widget/sdui/widgets/text_form_filed/model/text_form_field_sdui_model.g.dart +++ b/packages/chicken/lib/presentation/widget/sdui/widgets/text_form_filed/model/text_form_field_sdui_model.g.dart @@ -14,14 +14,14 @@ _TextFormFieldSDUIModel _$TextFormFieldSDUIModelFromJson( hintText: json['hint_text'] as String?, variant: json['variant'] as String?, keyboardType: json['keyboard_type'] as String?, - value: json['value'] as String?, + value: json['value'], maxLength: (json['max_length'] as num?)?.toInt(), minLine: (json['min_line'] as num?)?.toInt(), maxLine: (json['max_line'] as num?)?.toInt(), required: json['required'] as bool?, enabled: json['enabled'] as bool?, readonly: json['readonly'] as bool?, - commaSperator: json['comma_sperator'] as bool?, + commaSeparator: json['comma_separator'] as bool?, decimal: json['decimal'] as bool?, decimalPlaces: (json['decimal_places'] as num?)?.toInt(), type: json['type'] as String?, @@ -42,7 +42,7 @@ Map _$TextFormFieldSDUIModelToJson( 'required': instance.required, 'enabled': instance.enabled, 'readonly': instance.readonly, - 'comma_sperator': instance.commaSperator, + 'comma_separator': instance.commaSeparator, 'decimal': instance.decimal, 'decimal_places': instance.decimalPlaces, 'type': instance.type, diff --git a/packages/chicken/lib/presentation/widget/sdui/widgets/text_form_filed/text_form_filed_sdui.dart b/packages/chicken/lib/presentation/widget/sdui/widgets/text_form_filed/text_form_filed_sdui.dart index 8bf308b..04fa0ae 100644 --- a/packages/chicken/lib/presentation/widget/sdui/widgets/text_form_filed/text_form_filed_sdui.dart +++ b/packages/chicken/lib/presentation/widget/sdui/widgets/text_form_filed/text_form_filed_sdui.dart @@ -7,24 +7,23 @@ import 'model/text_form_field_sdui_model.dart'; Widget textFormFiledSDUI({ required TextFormFieldSDUIModel model, TextEditingController? controller, + ValueChanged? onChanged, }) { List? inputFormatters = []; TextInputType? keyboardType; VoidCallback? onTap; bool isReadonly = model.readonly ?? false; + String? initValue; final textController = controller ?? TextEditingController(text: model.value); if (model.type == 'date_picker') { - // برای date picker، readonly می‌کنیم و onTap اضافه می‌کنیم isReadonly = true; onTap = () { - // پارس کردن تاریخ فعلی اگر وجود دارد Jalali? initialDate; if (textController.text.isNotEmpty) { try { - // فرض می‌کنیم تاریخ به فرمت '1404/01/01' یا '1404-01-01' است final dateStr = textController.text.replaceAll('-', '/'); final parts = dateStr.split('/'); if (parts.length == 3) { @@ -40,16 +39,15 @@ Widget textFormFiledSDUI({ } } - // اگر نتوانستیم parse کنیم، از تاریخ امروز استفاده می‌کنیم initialDate ??= Jalali.now(); - // نمایش date picker Get.bottomSheet( modalDatePicker( initialDate: initialDate, onDateSelected: (selectedDate) { - // فرمت کردن تاریخ و قرار دادن در controller - textController.text = selectedDate.formatCompactDate(); + final formattedDate = selectedDate.formatCompactDate(); + textController.text = formattedDate; + onChanged?.call(formattedDate); }, ), isScrollControlled: true, @@ -67,16 +65,29 @@ Widget textFormFiledSDUI({ } } - if ((model.commaSperator ?? false) && - (model.decimal == null || model.decimal == false)) { + if ((model.commaSeparator ?? false) && (model.decimal == null || model.decimal == false)) { inputFormatters.add(SeparatorInputFormatter()); + + // FIX: اگر مقدار اولیه وجود دارد، باید آن را هم فرمت کنیم تا با فرمتر هماهنگ باشد + if (model.value != null) { + try { + String tmp = model.value.replaceAll(',', ''); + if (model.commaSeparator ?? false) { + textController.text = tmp.replaceAll(',', '').separatedByComma; + } else { + textController.text = int.tryParse(tmp).toString(); + } + } catch (e) { + // اگر پارس نشد، همان مقدار اصلی می‌ماند + } + } } } return RTextField( controller: textController, onChanged: (data) { - iLog(data); + onChanged?.call(data); }, onTap: onTap, label: model.label,