feat: add stepper and page view components to SDUI, enhance form handling with dynamic visibility conditions and improved error handling

This commit is contained in:
2025-12-29 10:09:57 +03:30
parent fc0161e261
commit dcfe9f6dcf
15 changed files with 2116 additions and 31 deletions

View File

@@ -10,6 +10,10 @@ import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/image_picker/i
import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/image_picker/model/image_picker_sdui_model.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_core/core.dart';
class SDUIFormWidget extends StatelessWidget {
@@ -19,6 +23,7 @@ class SDUIFormWidget extends StatelessWidget {
final Function(String key, dynamic value)? onStateChanged;
final Map<String, RxList<XFile>>? images;
final Function(String key, RxList<XFile> images)? onImagesChanged;
final Map<String, PageController>? pageControllers;
const SDUIFormWidget({
super.key,
@@ -28,6 +33,7 @@ class SDUIFormWidget extends StatelessWidget {
this.onStateChanged,
this.images,
this.onImagesChanged,
this.pageControllers,
});
@override
@@ -74,6 +80,10 @@ class SDUIFormWidget extends StatelessWidget {
return _buildRow(widgetModel);
case 'sized_box':
return _buildSizedBox(widgetModel);
case 'stepper':
return _buildStepper(widgetModel);
case 'page_view':
return _buildPageView(widgetModel);
default:
iLog('Unknown SDUI widget type: $type');
return const SizedBox.shrink();
@@ -113,10 +123,57 @@ class SDUIFormWidget extends StatelessWidget {
Widget _buildCardLabelItem(SDUIWidgetModel widgetModel) {
try {
// Check visible_condition if present in data
if (widgetModel.data != null) {
final visibleCondition =
widgetModel.data!['visible_condition'] as String?;
if (visibleCondition != null && visibleCondition.isNotEmpty) {
if (state != null) {
return Obx(() {
if (!_evaluateVisibleCondition(visibleCondition)) {
return const SizedBox.shrink();
}
return _buildCardLabelItemInternal(widgetModel);
});
} else {
// If state is null, only show first step (0) by default
// This allows the form to work even without state initialization
if (visibleCondition.contains('activeStepperIndex == 0')) {
return _buildCardLabelItemInternal(widgetModel);
}
return const SizedBox.shrink();
}
}
}
return _buildCardLabelItemInternal(widgetModel);
} catch (e, stackTrace) {
iLog('Error building card_label_item: $e');
iLog('Stack trace: $stackTrace');
iLog('WidgetModel data: ${widgetModel.data}');
return Container(
padding: EdgeInsets.all(16),
color: Colors.orange.withOpacity(0.1),
child: Text('Card Error: $e'),
);
}
}
Widget _buildCardLabelItemInternal(SDUIWidgetModel widgetModel) {
try {
// Remove visible_condition from data before creating CardLabelItemData
// because it's not part of the model
final dataWithoutCondition = widgetModel.data != null
? Map<String, dynamic>.from(widgetModel.data!)
: null;
if (dataWithoutCondition != null) {
dataWithoutCondition.remove('visible_condition');
}
final cardModel = CardLabelItemSDUI.fromJson({
'type': widgetModel.type,
'visible': widgetModel.visible,
'data': widgetModel.data,
'data': dataWithoutCondition,
'child': widgetModel.child,
});
@@ -203,6 +260,7 @@ class SDUIFormWidget extends StatelessWidget {
onStateChanged: onStateChanged,
images: images,
onImagesChanged: onImagesChanged,
pageControllers: pageControllers,
);
} catch (e) {
iLog('Error building column child: $e');
@@ -415,4 +473,148 @@ class SDUIFormWidget extends StatelessWidget {
return MainAxisAlignment.start;
}
}
Widget _buildStepper(SDUIWidgetModel widgetModel) {
if (widgetModel.data == null) {
return const SizedBox.shrink();
}
try {
final stepperModel = StepperSDUIModel.fromJson(widgetModel.data!);
return StepperSDUI(model: stepperModel, state: state);
} catch (e, stackTrace) {
iLog('Error building stepper: $e');
iLog('Stack trace: $stackTrace');
return Container(
padding: EdgeInsets.all(16),
color: Colors.orange.withOpacity(0.1),
child: Text('Stepper Error: $e'),
);
}
}
Widget _buildPageView(SDUIWidgetModel widgetModel) {
if (widgetModel.data == null) {
return const SizedBox.shrink();
}
try {
final pageViewModel = PageViewSDUIModel.fromJson(widgetModel.data!);
// Get PageController from map if key is provided
PageController? pageController;
if (pageViewModel.key != null &&
pageViewModel.key!.isNotEmpty &&
pageControllers != null &&
pageControllers!.containsKey(pageViewModel.key!)) {
pageController = pageControllers![pageViewModel.key!];
}
// Build children if they exist
List<Widget> pageChildren = [];
if (widgetModel.children != null && widgetModel.children!.isNotEmpty) {
pageChildren = widgetModel.children!.map((child) {
try {
return SDUIFormWidget(
model: SDUIWidgetModel.fromJson(child),
controllers: controllers,
state: state,
onStateChanged: onStateChanged,
images: images,
onImagesChanged: onImagesChanged,
pageControllers: pageControllers,
);
} catch (e) {
iLog('Error building page_view child: $e');
iLog('Child data: $child');
return Container(
padding: EdgeInsets.all(8),
color: Colors.yellow.withOpacity(0.1),
child: Text('Child Error'),
);
}
}).toList();
}
return PageViewSDUI(
model: pageViewModel,
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.withOpacity(0.1),
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;
}
}
}