refactor: update SDUIFormWidget error handling, enhance StepperSDUI with state management and scrolling behavior, and clean up SDUIWidgetModel documentation
This commit is contained in:
@@ -2,40 +2,91 @@ 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 {
|
||||
class StepperSDUI extends StatefulWidget {
|
||||
final StepperSDUIModel model;
|
||||
final RxMap<String, dynamic>? state;
|
||||
|
||||
const StepperSDUI({super.key, required this.model, this.state});
|
||||
|
||||
@override
|
||||
State<StepperSDUI> createState() => _StepperSDUIState();
|
||||
}
|
||||
|
||||
class _StepperSDUIState extends State<StepperSDUI> {
|
||||
late final ScrollController _scrollController;
|
||||
List<GlobalKey>? itemKeys;
|
||||
int? _lastActiveStep;
|
||||
late int totalSteps;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scrollController = ScrollController();
|
||||
totalSteps = widget.model.totalSteps ?? 1;
|
||||
itemKeys = List.generate(totalSteps, (index) => GlobalKey());
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final totalSteps = model.totalSteps ?? 5;
|
||||
|
||||
|
||||
// Update keys if totalSteps changed
|
||||
if (itemKeys == null || itemKeys!.length != totalSteps) {
|
||||
itemKeys = List.generate(totalSteps, (index) => GlobalKey());
|
||||
}
|
||||
|
||||
if ((totalSteps * 24.w + ((totalSteps - 1) * (40.w))) < Get.width) {
|
||||
return Obx(() {
|
||||
final activeStep =
|
||||
widget.state?[widget.model.key] as int? ??
|
||||
widget.model.activeStep ??
|
||||
0;
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: _buildSteps(totalSteps, activeStep),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return Obx(() {
|
||||
final activeStep = state?[model.key] as int? ?? model.activeStep ?? 0;
|
||||
final activeStep =
|
||||
widget.state?[widget.model.key] as int? ??
|
||||
widget.model.activeStep ??
|
||||
0;
|
||||
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: SizedBox(
|
||||
height: 30.h,
|
||||
width: Get.width,
|
||||
child: Row(children: _buildSteps(totalSteps, activeStep)),
|
||||
),
|
||||
// Scroll to active step if it changed
|
||||
if (_lastActiveStep != activeStep) {
|
||||
_lastActiveStep = activeStep;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
scrollToActive(activeStep);
|
||||
});
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
height: 30.h,
|
||||
child: SingleChildScrollView(
|
||||
controller: _scrollController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(children: _buildScrolledSteps(totalSteps, activeStep)),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
List<Widget> _buildSteps(int totalSteps, int activeStep) {
|
||||
List<Widget> _buildScrolledSteps(int totalSteps, int activeStep) {
|
||||
final List<Widget> widgets = [];
|
||||
|
||||
for (int i = 0; i < totalSteps; i++) {
|
||||
// Add step circle
|
||||
widgets.add(
|
||||
Container(
|
||||
key: itemKeys?[i],
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: activeStep >= i
|
||||
@@ -73,4 +124,84 @@ class StepperSDUI extends StatelessWidget {
|
||||
|
||||
return widgets;
|
||||
}
|
||||
|
||||
List<Widget> _buildSteps(int totalSteps, int activeStep) {
|
||||
final List<Widget> widgets = [];
|
||||
double space = 0;
|
||||
|
||||
if (totalSteps < 2) {
|
||||
space = 0;
|
||||
} else if (totalSteps == 2) {
|
||||
space = Get.width - (2 * (24.w)) - (51.w);
|
||||
} else {
|
||||
space = ((Get.width - (51.w)) - (totalSteps * (24.w))) / (totalSteps - 1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < totalSteps; i++) {
|
||||
// Add step circle
|
||||
widgets.add(
|
||||
Container(
|
||||
key: itemKeys?[i],
|
||||
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(
|
||||
SizedBox(
|
||||
width: space,
|
||||
child: Divider(
|
||||
color: activeStep >= i + 1
|
||||
? AppColor.greenNormalHover
|
||||
: AppColor.whiteNormalActive,
|
||||
thickness: 8,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return widgets;
|
||||
}
|
||||
|
||||
void scrollToActive(int index) {
|
||||
if (itemKeys == null || index < 0 || index >= itemKeys!.length) return;
|
||||
|
||||
final context = itemKeys![index].currentContext;
|
||||
if (context != null) {
|
||||
Scrollable.ensureVisible(
|
||||
context,
|
||||
duration: const Duration(milliseconds: 400),
|
||||
curve: Curves.easeInOut,
|
||||
alignment: 0.5, // دکمه بیاد وسط صفحه
|
||||
);
|
||||
} else if (_scrollController.hasClients) {
|
||||
// Fallback: calculate position manually
|
||||
final double itemWidth = 24.w + 40.w; // step width + divider width
|
||||
final double targetOffset =
|
||||
(index * itemWidth) - (Get.width / 2) + (24.w / 2);
|
||||
final double maxScroll = _scrollController.position.maxScrollExtent;
|
||||
_scrollController.animateTo(
|
||||
targetOffset.clamp(0.0, maxScroll),
|
||||
duration: const Duration(milliseconds: 400),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user