feat: Enhance officer and profile models with place of birth and date of birth fields; update step indicators for improved layout and styling
This commit is contained in:
parent
ce7d448b2f
commit
ce7d5f5cf4
|
@ -12,6 +12,8 @@ class OfficerModel {
|
|||
final String? phone;
|
||||
final String? email;
|
||||
final String? avatar;
|
||||
final String? placeOfBirth;
|
||||
final DateTime? dateOfBirth;
|
||||
final DateTime? validUntil;
|
||||
final String? qrCode;
|
||||
final DateTime? createdAt;
|
||||
|
@ -30,6 +32,8 @@ class OfficerModel {
|
|||
this.phone,
|
||||
this.email,
|
||||
this.avatar,
|
||||
this.placeOfBirth,
|
||||
this.dateOfBirth,
|
||||
this.validUntil,
|
||||
this.qrCode,
|
||||
this.createdAt,
|
||||
|
@ -51,6 +55,11 @@ class OfficerModel {
|
|||
phone: json['phone'] as String?,
|
||||
email: json['email'] as String?,
|
||||
avatar: json['avatar'] as String?,
|
||||
placeOfBirth: json['place_of_birth'] as String?,
|
||||
dateOfBirth:
|
||||
json['date_of_birth'] != null
|
||||
? DateTime.parse(json['date_of_birth'] as String)
|
||||
: null,
|
||||
validUntil:
|
||||
json['valid_until'] != null
|
||||
? DateTime.parse(json['valid_until'] as String)
|
||||
|
@ -85,6 +94,8 @@ class OfficerModel {
|
|||
'phone': phone,
|
||||
'email': email,
|
||||
'avatar': avatar,
|
||||
'place_of_birth': placeOfBirth,
|
||||
'date_of_birth': dateOfBirth?.toIso8601String(),
|
||||
'valid_until': validUntil?.toIso8601String(),
|
||||
'qr_code': qrCode,
|
||||
'created_at': createdAt?.toIso8601String(),
|
||||
|
@ -106,6 +117,8 @@ class OfficerModel {
|
|||
String? phone,
|
||||
String? email,
|
||||
String? avatar,
|
||||
String? placeOfBirth,
|
||||
DateTime? dateOfBirth,
|
||||
DateTime? validUntil,
|
||||
String? qrCode,
|
||||
DateTime? createdAt,
|
||||
|
@ -124,6 +137,8 @@ class OfficerModel {
|
|||
phone: phone ?? this.phone,
|
||||
email: email ?? this.email,
|
||||
avatar: avatar ?? this.avatar,
|
||||
placeOfBirth: placeOfBirth ?? this.placeOfBirth,
|
||||
dateOfBirth: dateOfBirth ?? this.dateOfBirth,
|
||||
validUntil: validUntil ?? this.validUntil,
|
||||
qrCode: qrCode ?? this.qrCode,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
|
@ -149,6 +164,17 @@ class OfficerModel {
|
|||
position: officerData['position'],
|
||||
phone: officerData['phone'],
|
||||
email: metadata['email'],
|
||||
avatar: officerData['avatar'],
|
||||
placeOfBirth: officerData['place_of_birth'],
|
||||
dateOfBirth:
|
||||
officerData['date_of_birth'] != null
|
||||
? DateTime.parse(officerData['date_of_birth'])
|
||||
: null,
|
||||
validUntil:
|
||||
officerData['valid_until'] != null
|
||||
? DateTime.parse(officerData['valid_until'])
|
||||
: null,
|
||||
qrCode: officerData['qr_code'],
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -108,7 +108,7 @@ class OnboardingController extends GetxController
|
|||
|
||||
TFullScreenLoader.stopLoading();
|
||||
|
||||
if (!isLocationValid) {
|
||||
if (isLocationValid) {
|
||||
// If location is valid, proceed to role selection
|
||||
Get.offAllNamed(AppRoutes.roleSelection);
|
||||
|
||||
|
|
|
@ -8,8 +8,10 @@ class ProfileModel {
|
|||
final String? lastName;
|
||||
final String? bio;
|
||||
final Map<String, dynamic>? address;
|
||||
final String? placeOfBirth;
|
||||
final DateTime? birthDate;
|
||||
|
||||
|
||||
ProfileModel({
|
||||
required this.id,
|
||||
required this.userId,
|
||||
|
@ -20,6 +22,7 @@ class ProfileModel {
|
|||
this.lastName,
|
||||
this.bio,
|
||||
this.address,
|
||||
this.placeOfBirth,
|
||||
this.birthDate,
|
||||
});
|
||||
|
||||
|
@ -38,6 +41,7 @@ class ProfileModel {
|
|||
json['address'] != null
|
||||
? Map<String, dynamic>.from(json['address'] as Map)
|
||||
: null,
|
||||
placeOfBirth: json['place_of_birth'] as String?,
|
||||
birthDate:
|
||||
json['birth_date'] != null
|
||||
? DateTime.parse(json['birth_date'] as String)
|
||||
|
@ -57,6 +61,7 @@ class ProfileModel {
|
|||
'last_name': lastName,
|
||||
'bio': bio,
|
||||
'address': address,
|
||||
'place_of_birth': placeOfBirth,
|
||||
'birth_date': birthDate?.toIso8601String(),
|
||||
};
|
||||
}
|
||||
|
@ -72,6 +77,7 @@ class ProfileModel {
|
|||
String? lastName,
|
||||
String? bio,
|
||||
Map<String, dynamic>? address,
|
||||
String? placeOfBirth,
|
||||
DateTime? birthDate,
|
||||
}) {
|
||||
return ProfileModel(
|
||||
|
@ -84,6 +90,7 @@ class ProfileModel {
|
|||
lastName: lastName ?? this.lastName,
|
||||
bio: bio ?? this.bio,
|
||||
address: address ?? this.address,
|
||||
placeOfBirth: placeOfBirth ?? this.placeOfBirth,
|
||||
birthDate: birthDate ?? this.birthDate,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -32,14 +32,17 @@ class NumberedStepIndicator extends StatelessWidget {
|
|||
}
|
||||
|
||||
Widget _buildTwoStepIndicator(BuildContext context) {
|
||||
const boxSize = TSizes.xl + TSizes.xs;
|
||||
final double padding = TSizes.xs;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
// Step indicators with connector line
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: TSizes.md / 2),
|
||||
padding: EdgeInsets.symmetric(horizontal: padding),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
// First step
|
||||
// First step box
|
||||
_buildStepBox(0),
|
||||
|
||||
// Connector line
|
||||
|
@ -61,110 +64,99 @@ class NumberedStepIndicator extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
|
||||
// Second step
|
||||
// Second step box
|
||||
_buildStepBox(1),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: TSizes.sm),
|
||||
|
||||
// Step titles
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: List.generate(totalSteps, (index) {
|
||||
return Text(
|
||||
stepTitles[index],
|
||||
style: theme.textTheme.labelMedium?.copyWith(
|
||||
fontWeight:
|
||||
index == currentStep ? FontWeight.bold : FontWeight.normal,
|
||||
color:
|
||||
index == currentStep
|
||||
? theme.primaryColor
|
||||
: isDark
|
||||
? Colors.white70
|
||||
: TColors.textSecondary,
|
||||
const SizedBox(height: TSizes.sm),
|
||||
|
||||
// Step titles with fixed widths aligned with boxes
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: padding),
|
||||
child: Row(
|
||||
children: [
|
||||
// First step title
|
||||
SizedBox(
|
||||
width: boxSize,
|
||||
child: Text(
|
||||
stepTitles[0],
|
||||
textAlign: TextAlign.center,
|
||||
style: _getTitleStyle(0),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
|
||||
// Empty space matching the connector
|
||||
Expanded(child: Container()),
|
||||
|
||||
// Second step title
|
||||
SizedBox(
|
||||
width: boxSize,
|
||||
child: Text(
|
||||
stepTitles[1],
|
||||
textAlign: TextAlign.center,
|
||||
style: _getTitleStyle(1),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMultiStepIndicator(BuildContext context) {
|
||||
const boxSize = TSizes.xl + TSizes.xs;
|
||||
final availableWidth =
|
||||
MediaQuery.of(context).size.width - (TSizes.defaultSpace * 2);
|
||||
final double spacing =
|
||||
(availableWidth - (boxSize * totalSteps)) / (totalSteps - 1);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
// Step indicators with connector lines
|
||||
Row(
|
||||
children: List.generate(totalSteps, (index) {
|
||||
final isLast = index == totalSteps - 1;
|
||||
|
||||
return Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
// Step number
|
||||
_buildStepBox(index),
|
||||
|
||||
// Connector line
|
||||
if (!isLast)
|
||||
Expanded(
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Container(
|
||||
height: TSizes.dividerHeight,
|
||||
color:
|
||||
isDark
|
||||
? TColors.darkerGrey
|
||||
: TColors.borderPrimary,
|
||||
),
|
||||
Container(
|
||||
height: TSizes.dividerHeight,
|
||||
width:
|
||||
index < currentStep
|
||||
? MediaQuery.of(context).size.width
|
||||
: 0,
|
||||
color: theme.primaryColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: List.generate(2 * totalSteps - 1, (index) {
|
||||
// Even indexes are step boxes, odd indexes are connectors
|
||||
if (index.isEven) {
|
||||
final stepIndex = index ~/ 2;
|
||||
return _buildStepBox(stepIndex);
|
||||
} else {
|
||||
final prevStepIndex = index ~/ 2;
|
||||
return Container(
|
||||
width: spacing,
|
||||
height: TSizes.dividerHeight,
|
||||
color:
|
||||
prevStepIndex < currentStep
|
||||
? theme.primaryColor
|
||||
: isDark
|
||||
? TColors.darkerGrey
|
||||
: TColors.borderPrimary,
|
||||
);
|
||||
}
|
||||
}),
|
||||
),
|
||||
SizedBox(height: TSizes.sm),
|
||||
|
||||
// Step titles
|
||||
const SizedBox(height: TSizes.md),
|
||||
|
||||
// Step titles with fixed positions matching boxes
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: List.generate(totalSteps, (index) {
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: index == 0 ? 0 : TSizes.xs,
|
||||
right: index == totalSteps - 1 ? 0 : TSizes.xs,
|
||||
),
|
||||
child: Text(
|
||||
stepTitles[index],
|
||||
textAlign:
|
||||
index == 0
|
||||
? TextAlign.start
|
||||
: index == totalSteps - 1
|
||||
? TextAlign.end
|
||||
: TextAlign.center,
|
||||
style: theme.textTheme.labelMedium?.copyWith(
|
||||
fontWeight:
|
||||
index == currentStep
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
color:
|
||||
index == currentStep
|
||||
? theme.primaryColor
|
||||
: isDark
|
||||
? Colors.white70
|
||||
: TColors.textSecondary,
|
||||
),
|
||||
),
|
||||
return SizedBox(
|
||||
width: boxSize,
|
||||
child: Text(
|
||||
stepTitles[index],
|
||||
textAlign: TextAlign.center,
|
||||
style: _getTitleStyle(index),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
);
|
||||
}),
|
||||
|
@ -176,13 +168,14 @@ class NumberedStepIndicator extends StatelessWidget {
|
|||
Widget _buildStepBox(int index) {
|
||||
final isActive = index <= currentStep;
|
||||
final isCompleted = index < currentStep;
|
||||
const boxSize = TSizes.xl + TSizes.xs;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => onStepTapped(index),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
width: TSizes.xl + TSizes.xs,
|
||||
height: TSizes.xl + TSizes.xs,
|
||||
width: boxSize,
|
||||
height: boxSize,
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
isCompleted
|
||||
|
@ -218,4 +211,16 @@ class NumberedStepIndicator extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
TextStyle? _getTitleStyle(int index) {
|
||||
return theme.textTheme.labelMedium?.copyWith(
|
||||
fontWeight: index == currentStep ? FontWeight.bold : FontWeight.normal,
|
||||
color:
|
||||
index == currentStep
|
||||
? theme.primaryColor
|
||||
: isDark
|
||||
? Colors.white70
|
||||
: TColors.textSecondary,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,12 +32,14 @@ class RoundedStepIndicator extends StatelessWidget {
|
|||
}
|
||||
|
||||
Widget _buildTwoStepIndicator(BuildContext context) {
|
||||
final circleWidth = TSizes.xl;
|
||||
final double padding = TSizes.xs;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: TSizes.md / 2),
|
||||
padding: EdgeInsets.symmetric(horizontal: padding),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
// First step
|
||||
_buildStepCircle(0),
|
||||
|
@ -66,106 +68,96 @@ class RoundedStepIndicator extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: TSizes.sm),
|
||||
|
||||
// Step titles
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: List.generate(totalSteps, (index) {
|
||||
return SizedBox(
|
||||
width: TSizes.xl,
|
||||
child: Text(
|
||||
stepTitles[index],
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: theme.textTheme.labelMedium?.copyWith(
|
||||
fontWeight:
|
||||
index == currentStep
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
color:
|
||||
index == currentStep
|
||||
? theme.primaryColor
|
||||
: isDark
|
||||
? Colors.white70
|
||||
: TColors.textSecondary,
|
||||
const SizedBox(height: TSizes.sm),
|
||||
|
||||
// Step titles with fixed widths aligned with circles
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: padding),
|
||||
child: Row(
|
||||
children: [
|
||||
// First step title
|
||||
SizedBox(
|
||||
width: circleWidth,
|
||||
child: Text(
|
||||
stepTitles[0],
|
||||
textAlign: TextAlign.center,
|
||||
style: _getTitleStyle(0),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
|
||||
// Empty space matching the connector
|
||||
Expanded(child: Container()),
|
||||
|
||||
// Second step title
|
||||
SizedBox(
|
||||
width: circleWidth,
|
||||
child: Text(
|
||||
stepTitles[1],
|
||||
textAlign: TextAlign.center,
|
||||
style: _getTitleStyle(1),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMultiStepIndicator(BuildContext context) {
|
||||
final circleWidth = TSizes.xl;
|
||||
final availableWidth =
|
||||
MediaQuery.of(context).size.width - (TSizes.defaultSpace * 2);
|
||||
final double spacing =
|
||||
(availableWidth - (circleWidth * totalSteps)) / (totalSteps - 1);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: TSizes.xl + TSizes.xs,
|
||||
child: Stack(
|
||||
children: [
|
||||
// Connector lines first (in the background)
|
||||
Positioned.fill(
|
||||
top: TSizes.xl / 2 - TSizes.dividerHeight / 2,
|
||||
child: Row(
|
||||
children: List.generate(totalSteps - 1, (index) {
|
||||
final isActive = index < currentStep;
|
||||
return Expanded(
|
||||
child: Container(
|
||||
height: TSizes.dividerHeight,
|
||||
margin: EdgeInsets.symmetric(horizontal: TSizes.xs),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
isActive
|
||||
? theme.primaryColor
|
||||
: isDark
|
||||
? TColors.darkerGrey
|
||||
: TColors.borderPrimary,
|
||||
borderRadius: BorderRadius.circular(
|
||||
TSizes.dividerHeight / 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
// Step indicators with connector lines
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: List.generate(2 * totalSteps - 1, (index) {
|
||||
// Even indexes are step circles, odd indexes are connectors
|
||||
if (index.isEven) {
|
||||
final stepIndex = index ~/ 2;
|
||||
return _buildStepCircle(stepIndex);
|
||||
} else {
|
||||
return Container(
|
||||
width: spacing,
|
||||
height: TSizes.dividerHeight,
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
(index ~/ 2) < currentStep
|
||||
? theme.primaryColor
|
||||
: isDark
|
||||
? TColors.darkerGrey
|
||||
: TColors.borderPrimary,
|
||||
borderRadius: BorderRadius.circular(TSizes.dividerHeight / 2),
|
||||
),
|
||||
),
|
||||
|
||||
// Step circles on top
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: List.generate(totalSteps, (index) {
|
||||
return _buildStepCircle(index);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}),
|
||||
),
|
||||
SizedBox(height: TSizes.sm),
|
||||
|
||||
// Step titles
|
||||
const SizedBox(height: TSizes.sm),
|
||||
|
||||
// Step titles with fixed positions matching circles
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: List.generate(totalSteps, (index) {
|
||||
return SizedBox(
|
||||
width: TSizes.xl,
|
||||
width: circleWidth,
|
||||
child: Text(
|
||||
stepTitles[index],
|
||||
textAlign: TextAlign.center,
|
||||
style: _getTitleStyle(index),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: theme.textTheme.labelMedium?.copyWith(
|
||||
fontWeight:
|
||||
index == currentStep
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
color:
|
||||
index == currentStep
|
||||
? theme.primaryColor
|
||||
: isDark
|
||||
? Colors.white70
|
||||
: TColors.textSecondary,
|
||||
),
|
||||
maxLines: 2,
|
||||
),
|
||||
);
|
||||
}),
|
||||
|
@ -227,4 +219,16 @@ class RoundedStepIndicator extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
TextStyle? _getTitleStyle(int index) {
|
||||
return theme.textTheme.labelMedium?.copyWith(
|
||||
fontWeight: index == currentStep ? FontWeight.bold : FontWeight.normal,
|
||||
color:
|
||||
index == currentStep
|
||||
? theme.primaryColor
|
||||
: isDark
|
||||
? Colors.white70
|
||||
: TColors.textSecondary,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,44 +33,17 @@ class StandardStepIndicator extends StatelessWidget {
|
|||
|
||||
Widget _buildTwoStepIndicator(BuildContext context) {
|
||||
final circleWidth = TSizes.xl;
|
||||
final double padding = TSizes.xs;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
// Step indicators with connector line
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: TSizes.md / 2),
|
||||
padding: EdgeInsets.symmetric(horizontal: padding),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
// First step
|
||||
Container(
|
||||
width: circleWidth,
|
||||
height: circleWidth,
|
||||
decoration: BoxDecoration(
|
||||
color: theme.primaryColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => onStepTapped(0),
|
||||
borderRadius: BorderRadius.circular(circleWidth),
|
||||
child: Center(
|
||||
child:
|
||||
0 < currentStep
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: Colors.white,
|
||||
size: TSizes.iconSm,
|
||||
)
|
||||
: Icon(
|
||||
Icons.circle,
|
||||
color: Colors.white,
|
||||
size: TSizes.iconXs,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildStepCircle(0, circleWidth),
|
||||
|
||||
// Connector line that spans the entire space between circles
|
||||
Expanded(
|
||||
|
@ -86,173 +59,97 @@ class StandardStepIndicator extends StatelessWidget {
|
|||
),
|
||||
|
||||
// Second step
|
||||
Container(
|
||||
_buildStepCircle(1, circleWidth),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: TSizes.sm),
|
||||
|
||||
// Step titles with fixed widths aligned with circles
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: padding),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: circleWidth,
|
||||
height: circleWidth,
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
currentStep >= 1
|
||||
? theme.primaryColor
|
||||
: isDark
|
||||
? TColors.darkerGrey
|
||||
: TColors.secondary,
|
||||
shape: BoxShape.circle,
|
||||
child: Text(
|
||||
stepTitles[0],
|
||||
textAlign: TextAlign.center,
|
||||
style: _getTitleStyle(0),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => onStepTapped(1),
|
||||
borderRadius: BorderRadius.circular(circleWidth),
|
||||
child: Center(
|
||||
child:
|
||||
currentStep >= 1
|
||||
? Icon(
|
||||
currentStep > 1 ? Icons.check : Icons.circle,
|
||||
color: Colors.white,
|
||||
size:
|
||||
currentStep > 1
|
||||
? TSizes.iconSm
|
||||
: TSizes.iconXs,
|
||||
)
|
||||
: Text(
|
||||
'2',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color:
|
||||
isDark
|
||||
? Colors.white
|
||||
: TColors.textSecondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Empty space matching the connector
|
||||
Expanded(child: Container()),
|
||||
|
||||
// Second step title
|
||||
SizedBox(
|
||||
width: circleWidth,
|
||||
child: Text(
|
||||
stepTitles[1],
|
||||
textAlign: TextAlign.center,
|
||||
style: _getTitleStyle(1),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: TSizes.sm),
|
||||
|
||||
// Step titles
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: List.generate(totalSteps, (index) {
|
||||
return Text(
|
||||
stepTitles[index],
|
||||
style: theme.textTheme.labelMedium?.copyWith(
|
||||
fontWeight:
|
||||
index == currentStep ? FontWeight.bold : FontWeight.normal,
|
||||
color:
|
||||
index == currentStep
|
||||
? theme.primaryColor
|
||||
: isDark
|
||||
? Colors.white70
|
||||
: TColors.textSecondary,
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMultiStepIndicator(BuildContext context) {
|
||||
const circleWidth = TSizes.xl;
|
||||
final availableWidth =
|
||||
MediaQuery.of(context).size.width - (TSizes.defaultSpace * 2);
|
||||
final double spacing =
|
||||
(availableWidth - (circleWidth * totalSteps)) / (totalSteps - 1);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
// Step indicators with connector lines
|
||||
Row(
|
||||
children: List.generate(totalSteps, (index) {
|
||||
final isActive = index <= currentStep;
|
||||
final isLast = index == totalSteps - 1;
|
||||
|
||||
return Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
// Step circle
|
||||
GestureDetector(
|
||||
onTap: () => onStepTapped(index),
|
||||
child: Container(
|
||||
width: TSizes.xl,
|
||||
height: TSizes.xl,
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
isActive
|
||||
? theme.primaryColor
|
||||
: isDark
|
||||
? TColors.darkerGrey
|
||||
: TColors.secondary,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Center(
|
||||
child:
|
||||
isActive
|
||||
? Icon(
|
||||
index < currentStep
|
||||
? Icons.check
|
||||
: Icons.circle,
|
||||
color: Colors.white,
|
||||
size:
|
||||
index < currentStep
|
||||
? TSizes.iconSm
|
||||
: TSizes.iconXs,
|
||||
)
|
||||
: Text(
|
||||
'${index + 1}',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color:
|
||||
isDark
|
||||
? Colors.white
|
||||
: TColors.textSecondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Connector line
|
||||
if (!isLast)
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: TSizes.dividerHeight,
|
||||
color:
|
||||
index < currentStep
|
||||
? theme.primaryColor
|
||||
: isDark
|
||||
? TColors.darkerGrey
|
||||
: TColors.borderPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: List.generate(2 * totalSteps - 1, (index) {
|
||||
// Even indexes (0, 2, 4...) are step circles, odd indexes (1, 3, 5...) are connectors
|
||||
if (index.isEven) {
|
||||
final stepIndex = index ~/ 2;
|
||||
return _buildStepCircle(stepIndex, circleWidth);
|
||||
} else {
|
||||
final prevStepIndex = index ~/ 2;
|
||||
return Container(
|
||||
width: spacing,
|
||||
height: TSizes.dividerHeight,
|
||||
color:
|
||||
prevStepIndex < currentStep
|
||||
? theme.primaryColor
|
||||
: isDark
|
||||
? TColors.darkerGrey
|
||||
: TColors.borderPrimary,
|
||||
);
|
||||
}
|
||||
}),
|
||||
),
|
||||
SizedBox(height: TSizes.sm),
|
||||
|
||||
// Step titles
|
||||
const SizedBox(height: TSizes.sm),
|
||||
|
||||
// Step titles with fixed positions matching circles
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: List.generate(totalSteps, (index) {
|
||||
return Expanded(
|
||||
return SizedBox(
|
||||
width: circleWidth,
|
||||
child: Text(
|
||||
stepTitles[index],
|
||||
textAlign:
|
||||
index == 0
|
||||
? TextAlign.start
|
||||
: index == totalSteps - 1
|
||||
? TextAlign.end
|
||||
: TextAlign.center,
|
||||
style: theme.textTheme.labelMedium?.copyWith(
|
||||
fontWeight:
|
||||
index == currentStep
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
color:
|
||||
index == currentStep
|
||||
? theme.primaryColor
|
||||
: isDark
|
||||
? Colors.white70
|
||||
: TColors.textSecondary,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
style: _getTitleStyle(index),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
);
|
||||
}),
|
||||
|
@ -260,4 +157,53 @@ class StandardStepIndicator extends StatelessWidget {
|
|||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStepCircle(int index, double circleWidth) {
|
||||
final isActive = index <= currentStep;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => onStepTapped(index),
|
||||
child: Container(
|
||||
width: circleWidth,
|
||||
height: circleWidth,
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
isActive
|
||||
? theme.primaryColor
|
||||
: isDark
|
||||
? TColors.darkerGrey
|
||||
: TColors.secondary,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Center(
|
||||
child:
|
||||
isActive
|
||||
? Icon(
|
||||
index < currentStep ? Icons.check : Icons.circle,
|
||||
color: Colors.white,
|
||||
size: index < currentStep ? TSizes.iconSm : TSizes.iconXs,
|
||||
)
|
||||
: Text(
|
||||
'${index + 1}',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: isDark ? Colors.white : TColors.textSecondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TextStyle? _getTitleStyle(int index) {
|
||||
return theme.textTheme.labelMedium?.copyWith(
|
||||
fontWeight: index == currentStep ? FontWeight.bold : FontWeight.normal,
|
||||
color:
|
||||
index == currentStep
|
||||
? theme.primaryColor
|
||||
: isDark
|
||||
? Colors.white70
|
||||
: TColors.textSecondary,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue