Files
mobile-app/lib/features/profile/presentation/widgets/profile_shared_widgets.dart

387 lines
12 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:real_estate_mobile/core/constants/app_colors.dart';
class ProfileFormField extends StatelessWidget {
final String label;
final TextEditingController controller;
final bool enabled;
final TextInputType? keyboardType;
final IconData? prefixIcon;
final TextCapitalization textCapitalization;
final bool obscureText;
final Widget? suffixIcon;
final List<TextInputFormatter>? inputFormatters;
final int? maxLength;
const ProfileFormField({
super.key,
required this.label,
required this.controller,
this.enabled = true,
this.keyboardType,
this.prefixIcon,
this.textCapitalization = TextCapitalization.words,
this.obscureText = false,
this.suffixIcon,
this.inputFormatters,
this.maxLength,
});
@override
Widget build(BuildContext context) {
final borderSide = BorderSide(
color: AppColors.primaryDark.withValues(alpha: 0.1),
width: 1,
);
final border = OutlineInputBorder(
borderRadius: BorderRadius.circular(7),
borderSide: borderSide,
);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label,
style: const TextStyle(
fontFamily: 'Fractul',
fontSize: 14,
fontWeight: FontWeight.w400,
color: AppColors.primaryDark,
)),
const SizedBox(height: 14),
SizedBox(
height: 38,
child: TextField(
controller: controller,
enabled: enabled,
keyboardType: keyboardType,
textCapitalization: textCapitalization,
obscureText: obscureText,
inputFormatters: inputFormatters,
maxLength: maxLength,
textAlignVertical: TextAlignVertical.center,
style: const TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 14,
fontWeight: FontWeight.w400,
color: AppColors.primaryDark,
),
decoration: InputDecoration(
isDense: true,
counterText: '',
contentPadding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
border: border,
enabledBorder: border,
focusedBorder: border,
disabledBorder: border,
prefixIcon: prefixIcon != null
? Icon(prefixIcon, size: 18, color: AppColors.accentOrange)
: null,
prefixIconConstraints: prefixIcon != null
? const BoxConstraints(minWidth: 40)
: null,
suffixIcon: suffixIcon,
),
),
),
],
);
}
}
class ProfileActionButtons extends StatelessWidget {
final VoidCallback onSave;
final VoidCallback onCancel;
final bool isSaving;
final bool hasChanges;
final String saveLabel;
const ProfileActionButtons({
super.key,
required this.onSave,
required this.onCancel,
this.isSaving = false,
this.hasChanges = true,
this.saveLabel = 'Save Changes',
});
@override
Widget build(BuildContext context) {
return Container(
height: 71,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
border: Border.all(
color: AppColors.primaryDark.withValues(alpha: 0.1),
width: 1,
),
),
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
Expanded(
flex: 2,
child: GestureDetector(
onTap: hasChanges ? onCancel : null,
child: Opacity(
opacity: hasChanges ? 1.0 : 0.5,
child: Container(
height: 31,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(7),
border: Border.all(
color: AppColors.primaryDark.withValues(alpha: 0.1),
width: 1,
),
),
alignment: Alignment.center,
child: const Text('Cancel',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontFamily: 'Fractul',
fontSize: 14,
fontWeight: FontWeight.w400,
color: AppColors.primaryDark,
)),
),
),
),
),
const SizedBox(width: 10),
Expanded(
flex: 3,
child: GestureDetector(
onTap: (isSaving || !hasChanges) ? null : onSave,
child: Opacity(
opacity: hasChanges ? 1.0 : 0.5,
child: Container(
height: 31,
padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
color: AppColors.accentOrange,
borderRadius: BorderRadius.circular(7),
),
alignment: Alignment.center,
child: isSaving
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2, color: AppColors.primaryDark),
)
: FittedBox(
fit: BoxFit.scaleDown,
child: Text(saveLabel,
maxLines: 1,
style: const TextStyle(
fontFamily: 'Fractul',
fontSize: 14,
fontWeight: FontWeight.w400,
color: AppColors.primaryDark,
)),
),
),
),
),
),
],
),
);
}
}
class ProfileSectionCard extends StatelessWidget {
final String title;
final String subtitle;
final Widget child;
final Color borderColor;
final Color titleColor;
const ProfileSectionCard({
super.key,
required this.title,
required this.subtitle,
required this.child,
this.borderColor = const Color(0xFFE8E8E8),
this.titleColor = AppColors.primaryDark,
});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15),
border: Border.all(color: borderColor, width: 1),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title,
style: TextStyle(
fontFamily: 'Fractul',
fontSize: 16,
fontWeight: FontWeight.w700,
color: titleColor,
)),
const SizedBox(height: 4),
Text(subtitle,
style: const TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 13,
color: AppColors.hintText,
)),
const SizedBox(height: 16),
child,
],
),
);
}
}
class ProfileDropdownRow extends StatelessWidget {
final String label;
final String description;
final String value;
final Map<String, String> options;
final ValueChanged<String?> onChanged;
const ProfileDropdownRow({
super.key,
required this.label,
required this.description,
required this.value,
required this.options,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label,
style: const TextStyle(
fontFamily: 'Fractul',
fontSize: 14,
fontWeight: FontWeight.w700,
color: AppColors.primaryDark,
)),
const SizedBox(height: 2),
Text(description,
style: const TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 12,
color: AppColors.hintText,
)),
const SizedBox(height: 8),
Container(
height: 42,
padding: const EdgeInsets.symmetric(horizontal: 14),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: AppColors.primaryDark.withValues(alpha: 0.15)),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: value,
isExpanded: true,
icon: const Icon(Icons.keyboard_arrow_down,
color: AppColors.primaryDark, size: 20),
style: const TextStyle(
fontFamily: 'Fractul',
fontSize: 13,
fontWeight: FontWeight.w500,
color: AppColors.primaryDark,
),
items: options.entries
.map((e) =>
DropdownMenuItem(value: e.key, child: Text(e.value)))
.toList(),
onChanged: onChanged,
),
),
),
],
);
}
}
class ProfileToggleRow extends StatelessWidget {
final String label;
final String description;
final bool value;
final ValueChanged<bool> onChanged;
const ProfileToggleRow({
super.key,
required this.label,
required this.description,
required this.value,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label,
style: const TextStyle(
fontFamily: 'Fractul',
fontSize: 14,
fontWeight: FontWeight.w700,
color: AppColors.primaryDark,
)),
const SizedBox(height: 2),
Text(description,
style: const TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 12,
color: AppColors.hintText,
)),
],
),
),
const SizedBox(width: 12),
GestureDetector(
onTap: () => onChanged(!value),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
width: 44,
height: 24,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: value
? AppColors.accentOrange
: AppColors.primaryDark.withValues(alpha: 0.2),
),
child: AnimatedAlign(
duration: const Duration(milliseconds: 200),
alignment:
value ? Alignment.centerRight : Alignment.centerLeft,
child: Container(
width: 20,
height: 20,
margin: const EdgeInsets.symmetric(horizontal: 2),
decoration: const BoxDecoration(
color: Colors.white, shape: BoxShape.circle),
),
),
),
),
],
);
}
}