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

@@ -0,0 +1,9 @@
{
"type": "stepper",
"visible": true,
"data": {
"key": "activeStepperIndex",
"totalSteps": 5,
"activeStep": 0
}
}

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/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/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/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'; import 'package:rasadyar_core/core.dart';
class SDUIFormWidget extends StatelessWidget { class SDUIFormWidget extends StatelessWidget {
@@ -19,6 +23,7 @@ class SDUIFormWidget extends StatelessWidget {
final Function(String key, dynamic value)? onStateChanged; final Function(String key, dynamic value)? onStateChanged;
final Map<String, RxList<XFile>>? images; final Map<String, RxList<XFile>>? images;
final Function(String key, RxList<XFile> images)? onImagesChanged; final Function(String key, RxList<XFile> images)? onImagesChanged;
final Map<String, PageController>? pageControllers;
const SDUIFormWidget({ const SDUIFormWidget({
super.key, super.key,
@@ -28,6 +33,7 @@ class SDUIFormWidget extends StatelessWidget {
this.onStateChanged, this.onStateChanged,
this.images, this.images,
this.onImagesChanged, this.onImagesChanged,
this.pageControllers,
}); });
@override @override
@@ -74,6 +80,10 @@ class SDUIFormWidget extends StatelessWidget {
return _buildRow(widgetModel); return _buildRow(widgetModel);
case 'sized_box': case 'sized_box':
return _buildSizedBox(widgetModel); return _buildSizedBox(widgetModel);
case 'stepper':
return _buildStepper(widgetModel);
case 'page_view':
return _buildPageView(widgetModel);
default: default:
iLog('Unknown SDUI widget type: $type'); iLog('Unknown SDUI widget type: $type');
return const SizedBox.shrink(); return const SizedBox.shrink();
@@ -113,10 +123,57 @@ class SDUIFormWidget extends StatelessWidget {
Widget _buildCardLabelItem(SDUIWidgetModel widgetModel) { Widget _buildCardLabelItem(SDUIWidgetModel widgetModel) {
try { 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({ final cardModel = CardLabelItemSDUI.fromJson({
'type': widgetModel.type, 'type': widgetModel.type,
'visible': widgetModel.visible, 'visible': widgetModel.visible,
'data': widgetModel.data, 'data': dataWithoutCondition,
'child': widgetModel.child, 'child': widgetModel.child,
}); });
@@ -203,6 +260,7 @@ class SDUIFormWidget extends StatelessWidget {
onStateChanged: onStateChanged, onStateChanged: onStateChanged,
images: images, images: images,
onImagesChanged: onImagesChanged, onImagesChanged: onImagesChanged,
pageControllers: pageControllers,
); );
} catch (e) { } catch (e) {
iLog('Error building column child: $e'); iLog('Error building column child: $e');
@@ -415,4 +473,148 @@ class SDUIFormWidget extends StatelessWidget {
return MainAxisAlignment.start; 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;
}
}
} }

View File

@@ -16,4 +16,3 @@ abstract class SDUIWidgetModel with _$SDUIWidgetModel {
factory SDUIWidgetModel.fromJson(Map<String, dynamic> json) => factory SDUIWidgetModel.fromJson(Map<String, dynamic> json) =>
_$SDUIWidgetModelFromJson(json); _$SDUIWidgetModelFromJson(json);
} }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,76 @@
# PageView SDUI Widget
این ویجت برای استفاده از PageView در ساختار SDUI طراحی شده است.
## استفاده در کد
```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';
// ایجاد مدل
final pageViewModel = PageViewSDUIModel(
key: 'pageController', // کلید برای دسترسی به PageController
reverse: true,
physics: 'neverScrollable', // 'neverScrollable', 'bouncing', 'clamping', 'alwaysScrollable'
itemCount: 5,
);
// استفاده از ویجت
PageViewSDUI(
model: pageViewModel,
controller: controller.pageController, // PageController از logic
children: pages, // لیست صفحات
)
```
## استفاده در JSON
```json
{
"type": "page_view",
"visible": true,
"data": {
"key": "pageController",
"reverse": true,
"physics": "neverScrollable",
"itemCount": 5
},
"children": [
{
"type": "column",
"visible": true,
"children": [...]
}
]
}
```
## پارامترها
- `key`: کلید برای دسترسی به PageController از map
- `reverse`: جهت معکوس (پیش‌فرض: true)
- `physics`: نوع فیزیک اسکرول ('neverScrollable', 'bouncing', 'clamping', 'alwaysScrollable')
- `itemCount`: تعداد آیتم‌ها (پیش‌فرض: تعداد children)
## مثال استفاده در create_inspection_bottom_sheet.dart
```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';
// در build method:
Expanded(
child: PageViewSDUI(
model: PageViewSDUIModel(
key: 'pageController',
reverse: true,
physics: 'neverScrollable',
itemCount: pages.length,
),
controller: controller.pageController,
children: pages,
),
),
```

View File

@@ -0,0 +1,46 @@
{
"type": "page_view",
"visible": true,
"data": {
"key": "pageController",
"reverse": true,
"physics": "neverScrollable",
"itemCount": 5
},
"children": [
{
"type": "column",
"visible": true,
"data": {
"spacing": 10.0
},
"children": [
{
"type": "text_form_field",
"visible": true,
"data": {
"key": "field1",
"label": "Field 1"
}
}
]
},
{
"type": "column",
"visible": true,
"data": {
"spacing": 10.0
},
"children": [
{
"type": "text_form_field",
"visible": true,
"data": {
"key": "field2",
"label": "Field 2"
}
}
]
}
]
}

View File

@@ -0,0 +1,18 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'page_view_sdui_model.freezed.dart';
part 'page_view_sdui_model.g.dart';
@freezed
abstract class PageViewSDUIModel with _$PageViewSDUIModel {
const factory PageViewSDUIModel({
String? key,
bool? reverse,
String? physics, // 'neverScrollable', 'bouncing', 'clamping', 'alwaysScrollable'
int? itemCount,
}) = _PageViewSDUIModel;
factory PageViewSDUIModel.fromJson(Map<String, dynamic> json) =>
_$PageViewSDUIModelFromJson(json);
}

View File

@@ -0,0 +1,288 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'page_view_sdui_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$PageViewSDUIModel {
String? get key; bool? get reverse; String? get physics;// 'neverScrollable', 'bouncing', 'clamping', 'alwaysScrollable'
int? get itemCount;
/// Create a copy of PageViewSDUIModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$PageViewSDUIModelCopyWith<PageViewSDUIModel> get copyWith => _$PageViewSDUIModelCopyWithImpl<PageViewSDUIModel>(this as PageViewSDUIModel, _$identity);
/// Serializes this PageViewSDUIModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is PageViewSDUIModel&&(identical(other.key, key) || other.key == key)&&(identical(other.reverse, reverse) || other.reverse == reverse)&&(identical(other.physics, physics) || other.physics == physics)&&(identical(other.itemCount, itemCount) || other.itemCount == itemCount));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,key,reverse,physics,itemCount);
@override
String toString() {
return 'PageViewSDUIModel(key: $key, reverse: $reverse, physics: $physics, itemCount: $itemCount)';
}
}
/// @nodoc
abstract mixin class $PageViewSDUIModelCopyWith<$Res> {
factory $PageViewSDUIModelCopyWith(PageViewSDUIModel value, $Res Function(PageViewSDUIModel) _then) = _$PageViewSDUIModelCopyWithImpl;
@useResult
$Res call({
String? key, bool? reverse, String? physics, int? itemCount
});
}
/// @nodoc
class _$PageViewSDUIModelCopyWithImpl<$Res>
implements $PageViewSDUIModelCopyWith<$Res> {
_$PageViewSDUIModelCopyWithImpl(this._self, this._then);
final PageViewSDUIModel _self;
final $Res Function(PageViewSDUIModel) _then;
/// Create a copy of PageViewSDUIModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? key = freezed,Object? reverse = freezed,Object? physics = freezed,Object? itemCount = freezed,}) {
return _then(_self.copyWith(
key: freezed == key ? _self.key : key // ignore: cast_nullable_to_non_nullable
as String?,reverse: freezed == reverse ? _self.reverse : reverse // ignore: cast_nullable_to_non_nullable
as bool?,physics: freezed == physics ? _self.physics : physics // ignore: cast_nullable_to_non_nullable
as String?,itemCount: freezed == itemCount ? _self.itemCount : itemCount // ignore: cast_nullable_to_non_nullable
as int?,
));
}
}
/// Adds pattern-matching-related methods to [PageViewSDUIModel].
extension PageViewSDUIModelPatterns on PageViewSDUIModel {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _PageViewSDUIModel value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _PageViewSDUIModel() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _PageViewSDUIModel value) $default,){
final _that = this;
switch (_that) {
case _PageViewSDUIModel():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _PageViewSDUIModel value)? $default,){
final _that = this;
switch (_that) {
case _PageViewSDUIModel() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? key, bool? reverse, String? physics, int? itemCount)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _PageViewSDUIModel() when $default != null:
return $default(_that.key,_that.reverse,_that.physics,_that.itemCount);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? key, bool? reverse, String? physics, int? itemCount) $default,) {final _that = this;
switch (_that) {
case _PageViewSDUIModel():
return $default(_that.key,_that.reverse,_that.physics,_that.itemCount);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? key, bool? reverse, String? physics, int? itemCount)? $default,) {final _that = this;
switch (_that) {
case _PageViewSDUIModel() when $default != null:
return $default(_that.key,_that.reverse,_that.physics,_that.itemCount);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _PageViewSDUIModel implements PageViewSDUIModel {
const _PageViewSDUIModel({this.key, this.reverse, this.physics, this.itemCount});
factory _PageViewSDUIModel.fromJson(Map<String, dynamic> json) => _$PageViewSDUIModelFromJson(json);
@override final String? key;
@override final bool? reverse;
@override final String? physics;
// 'neverScrollable', 'bouncing', 'clamping', 'alwaysScrollable'
@override final int? itemCount;
/// Create a copy of PageViewSDUIModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$PageViewSDUIModelCopyWith<_PageViewSDUIModel> get copyWith => __$PageViewSDUIModelCopyWithImpl<_PageViewSDUIModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$PageViewSDUIModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PageViewSDUIModel&&(identical(other.key, key) || other.key == key)&&(identical(other.reverse, reverse) || other.reverse == reverse)&&(identical(other.physics, physics) || other.physics == physics)&&(identical(other.itemCount, itemCount) || other.itemCount == itemCount));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,key,reverse,physics,itemCount);
@override
String toString() {
return 'PageViewSDUIModel(key: $key, reverse: $reverse, physics: $physics, itemCount: $itemCount)';
}
}
/// @nodoc
abstract mixin class _$PageViewSDUIModelCopyWith<$Res> implements $PageViewSDUIModelCopyWith<$Res> {
factory _$PageViewSDUIModelCopyWith(_PageViewSDUIModel value, $Res Function(_PageViewSDUIModel) _then) = __$PageViewSDUIModelCopyWithImpl;
@override @useResult
$Res call({
String? key, bool? reverse, String? physics, int? itemCount
});
}
/// @nodoc
class __$PageViewSDUIModelCopyWithImpl<$Res>
implements _$PageViewSDUIModelCopyWith<$Res> {
__$PageViewSDUIModelCopyWithImpl(this._self, this._then);
final _PageViewSDUIModel _self;
final $Res Function(_PageViewSDUIModel) _then;
/// Create a copy of PageViewSDUIModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? key = freezed,Object? reverse = freezed,Object? physics = freezed,Object? itemCount = freezed,}) {
return _then(_PageViewSDUIModel(
key: freezed == key ? _self.key : key // ignore: cast_nullable_to_non_nullable
as String?,reverse: freezed == reverse ? _self.reverse : reverse // ignore: cast_nullable_to_non_nullable
as bool?,physics: freezed == physics ? _self.physics : physics // ignore: cast_nullable_to_non_nullable
as String?,itemCount: freezed == itemCount ? _self.itemCount : itemCount // ignore: cast_nullable_to_non_nullable
as int?,
));
}
}
// dart format on

View File

@@ -0,0 +1,23 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'page_view_sdui_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_PageViewSDUIModel _$PageViewSDUIModelFromJson(Map<String, dynamic> json) =>
_PageViewSDUIModel(
key: json['key'] as String?,
reverse: json['reverse'] as bool?,
physics: json['physics'] as String?,
itemCount: (json['item_count'] as num?)?.toInt(),
);
Map<String, dynamic> _$PageViewSDUIModelToJson(_PageViewSDUIModel instance) =>
<String, dynamic>{
'key': instance.key,
'reverse': instance.reverse,
'physics': instance.physics,
'item_count': instance.itemCount,
};

View File

@@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/page_view/model/page_view_sdui_model.dart';
import 'package:rasadyar_core/core.dart';
class PageViewSDUI extends StatelessWidget {
final PageViewSDUIModel model;
final PageController? controller;
final List<Widget> children;
final RxMap<String, dynamic>? state;
final Map<String, TextEditingController>? controllers;
final Function(String key, dynamic value)? onStateChanged;
final Map<String, RxList<XFile>>? images;
final Function(String key, RxList<XFile> images)? onImagesChanged;
const PageViewSDUI({
super.key,
required this.model,
required this.children,
this.controller,
this.state,
this.controllers,
this.onStateChanged,
this.images,
this.onImagesChanged,
});
ScrollPhysics? _parsePhysics(String? physics) {
switch (physics) {
case 'neverScrollable':
return const NeverScrollableScrollPhysics();
case 'bouncing':
return const BouncingScrollPhysics();
case 'clamping':
return const ClampingScrollPhysics();
case 'alwaysScrollable':
return const AlwaysScrollableScrollPhysics();
default:
return const NeverScrollableScrollPhysics();
}
}
@override
Widget build(BuildContext context) {
final physics = _parsePhysics(model.physics);
final reverse = model.reverse ?? true;
final itemCount = model.itemCount ?? children.length;
return PageView.builder(
physics: physics,
reverse: reverse,
controller: controller,
itemCount: itemCount,
itemBuilder: (context, index) {
if (index < children.length) {
return children[index];
}
return const SizedBox.shrink();
},
);
}
}

View File

@@ -0,0 +1,9 @@
{
"type": "stepper",
"visible": true,
"data": {
"key": "activeStepperIndex",
"totalSteps": 5,
"activeStep": 0
}
}

View File

@@ -0,0 +1,16 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'stepper_sdui_model.freezed.dart';
part 'stepper_sdui_model.g.dart';
@freezed
abstract class StepperSDUIModel with _$StepperSDUIModel {
const factory StepperSDUIModel({
String? key,
int? totalSteps,
int? activeStep,
}) = _StepperSDUIModel;
factory StepperSDUIModel.fromJson(Map<String, dynamic> json) =>
_$StepperSDUIModelFromJson(json);
}

View File

@@ -0,0 +1,283 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'stepper_sdui_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$StepperSDUIModel {
String? get key; int? get totalSteps; int? get activeStep;
/// Create a copy of StepperSDUIModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$StepperSDUIModelCopyWith<StepperSDUIModel> get copyWith => _$StepperSDUIModelCopyWithImpl<StepperSDUIModel>(this as StepperSDUIModel, _$identity);
/// Serializes this StepperSDUIModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is StepperSDUIModel&&(identical(other.key, key) || other.key == key)&&(identical(other.totalSteps, totalSteps) || other.totalSteps == totalSteps)&&(identical(other.activeStep, activeStep) || other.activeStep == activeStep));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,key,totalSteps,activeStep);
@override
String toString() {
return 'StepperSDUIModel(key: $key, totalSteps: $totalSteps, activeStep: $activeStep)';
}
}
/// @nodoc
abstract mixin class $StepperSDUIModelCopyWith<$Res> {
factory $StepperSDUIModelCopyWith(StepperSDUIModel value, $Res Function(StepperSDUIModel) _then) = _$StepperSDUIModelCopyWithImpl;
@useResult
$Res call({
String? key, int? totalSteps, int? activeStep
});
}
/// @nodoc
class _$StepperSDUIModelCopyWithImpl<$Res>
implements $StepperSDUIModelCopyWith<$Res> {
_$StepperSDUIModelCopyWithImpl(this._self, this._then);
final StepperSDUIModel _self;
final $Res Function(StepperSDUIModel) _then;
/// Create a copy of StepperSDUIModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? key = freezed,Object? totalSteps = freezed,Object? activeStep = freezed,}) {
return _then(_self.copyWith(
key: freezed == key ? _self.key : key // ignore: cast_nullable_to_non_nullable
as String?,totalSteps: freezed == totalSteps ? _self.totalSteps : totalSteps // ignore: cast_nullable_to_non_nullable
as int?,activeStep: freezed == activeStep ? _self.activeStep : activeStep // ignore: cast_nullable_to_non_nullable
as int?,
));
}
}
/// Adds pattern-matching-related methods to [StepperSDUIModel].
extension StepperSDUIModelPatterns on StepperSDUIModel {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _StepperSDUIModel value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _StepperSDUIModel() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _StepperSDUIModel value) $default,){
final _that = this;
switch (_that) {
case _StepperSDUIModel():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _StepperSDUIModel value)? $default,){
final _that = this;
switch (_that) {
case _StepperSDUIModel() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? key, int? totalSteps, int? activeStep)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _StepperSDUIModel() when $default != null:
return $default(_that.key,_that.totalSteps,_that.activeStep);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? key, int? totalSteps, int? activeStep) $default,) {final _that = this;
switch (_that) {
case _StepperSDUIModel():
return $default(_that.key,_that.totalSteps,_that.activeStep);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? key, int? totalSteps, int? activeStep)? $default,) {final _that = this;
switch (_that) {
case _StepperSDUIModel() when $default != null:
return $default(_that.key,_that.totalSteps,_that.activeStep);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _StepperSDUIModel implements StepperSDUIModel {
const _StepperSDUIModel({this.key, this.totalSteps, this.activeStep});
factory _StepperSDUIModel.fromJson(Map<String, dynamic> json) => _$StepperSDUIModelFromJson(json);
@override final String? key;
@override final int? totalSteps;
@override final int? activeStep;
/// Create a copy of StepperSDUIModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$StepperSDUIModelCopyWith<_StepperSDUIModel> get copyWith => __$StepperSDUIModelCopyWithImpl<_StepperSDUIModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$StepperSDUIModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _StepperSDUIModel&&(identical(other.key, key) || other.key == key)&&(identical(other.totalSteps, totalSteps) || other.totalSteps == totalSteps)&&(identical(other.activeStep, activeStep) || other.activeStep == activeStep));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,key,totalSteps,activeStep);
@override
String toString() {
return 'StepperSDUIModel(key: $key, totalSteps: $totalSteps, activeStep: $activeStep)';
}
}
/// @nodoc
abstract mixin class _$StepperSDUIModelCopyWith<$Res> implements $StepperSDUIModelCopyWith<$Res> {
factory _$StepperSDUIModelCopyWith(_StepperSDUIModel value, $Res Function(_StepperSDUIModel) _then) = __$StepperSDUIModelCopyWithImpl;
@override @useResult
$Res call({
String? key, int? totalSteps, int? activeStep
});
}
/// @nodoc
class __$StepperSDUIModelCopyWithImpl<$Res>
implements _$StepperSDUIModelCopyWith<$Res> {
__$StepperSDUIModelCopyWithImpl(this._self, this._then);
final _StepperSDUIModel _self;
final $Res Function(_StepperSDUIModel) _then;
/// Create a copy of StepperSDUIModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? key = freezed,Object? totalSteps = freezed,Object? activeStep = freezed,}) {
return _then(_StepperSDUIModel(
key: freezed == key ? _self.key : key // ignore: cast_nullable_to_non_nullable
as String?,totalSteps: freezed == totalSteps ? _self.totalSteps : totalSteps // ignore: cast_nullable_to_non_nullable
as int?,activeStep: freezed == activeStep ? _self.activeStep : activeStep // ignore: cast_nullable_to_non_nullable
as int?,
));
}
}
// dart format on

View File

@@ -0,0 +1,21 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'stepper_sdui_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_StepperSDUIModel _$StepperSDUIModelFromJson(Map<String, dynamic> json) =>
_StepperSDUIModel(
key: json['key'] as String?,
totalSteps: (json['total_steps'] as num?)?.toInt(),
activeStep: (json['active_step'] as num?)?.toInt(),
);
Map<String, dynamic> _$StepperSDUIModelToJson(_StepperSDUIModel instance) =>
<String, dynamic>{
'key': instance.key,
'total_steps': instance.totalSteps,
'active_step': instance.activeStep,
};

View File

@@ -0,0 +1,75 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/stepper/model/stepper_sdui_model.dart';
import 'package:rasadyar_core/core.dart';
class StepperSDUI extends StatelessWidget {
final StepperSDUIModel model;
final RxMap<String, dynamic>? state;
const StepperSDUI({super.key, required this.model, this.state});
@override
Widget build(BuildContext context) {
final totalSteps = model.totalSteps ?? 5;
return Obx(() {
final activeStep = state?[model.key] as int? ?? model.activeStep ?? 0;
return Directionality(
textDirection: TextDirection.ltr,
child: SizedBox(
height: 24,
width: Get.width,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildSteps(totalSteps, activeStep),
),
),
);
});
}
List<Widget> _buildSteps(int totalSteps, int activeStep) {
final List<Widget> widgets = [];
for (int i = 0; i < totalSteps; i++) {
// Add step circle
widgets.add(
Container(
alignment: Alignment.center,
decoration: BoxDecoration(
color: activeStep >= i
? AppColor.greenNormalHover
: AppColor.whiteNormalActive,
shape: BoxShape.circle,
),
width: 24.w,
height: 24.h,
child: Text(
'${i + 1}',
textAlign: TextAlign.center,
style: AppFonts.yekan16.copyWith(
color: activeStep >= i ? Colors.white : AppColor.iconColor,
),
),
),
);
// Add divider between steps (except after last step)
if (i < totalSteps - 1) {
widgets.add(
Expanded(
child: Divider(
color: activeStep >= i + 1
? AppColor.greenNormalHover
: AppColor.whiteNormalActive,
thickness: 8,
),
),
);
}
}
return widgets;
}
}