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