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

@@ -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;
}
}