first commit
This commit is contained in:
805
lib/view/account/edit_profile_view.dart
Normal file
805
lib/view/account/edit_profile_view.dart
Normal file
@@ -0,0 +1,805 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart' hide Response;
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:minio/io.dart';
|
||||
import 'package:minio/minio.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
||||
import '../../constants/color_constants.dart';
|
||||
import '../../constants/font_constants.dart';
|
||||
import '../../controllers/account_controller/profile.dart';
|
||||
import '../../domain/repository/authentication/auth_repository.dart';
|
||||
import '../../modules/authentication/auth.dart';
|
||||
import '../../modules/authentication/getbyid.dart';
|
||||
import '../../widgets/text_widget.dart';
|
||||
|
||||
class EditProfilePage extends StatefulWidget {
|
||||
const EditProfilePage({super.key});
|
||||
|
||||
@override
|
||||
State<EditProfilePage> createState() => _EditProfilePageState();
|
||||
}
|
||||
|
||||
class _EditProfilePageState extends State<EditProfilePage> {
|
||||
CustomerFullView? profile;
|
||||
bool isLoading = true;
|
||||
File? pickedImage;
|
||||
final AccountController accountController = Get.find<AccountController>();
|
||||
|
||||
String Name = '';
|
||||
String Adress = '';
|
||||
String Profile = '';
|
||||
String Number = '';
|
||||
|
||||
Future<void> _pickImage() async {
|
||||
final pickedFile =
|
||||
await ImagePicker().pickImage(source: ImageSource.gallery);
|
||||
if (pickedFile != null) {
|
||||
setState(() {
|
||||
pickedImage = File(pickedFile.path);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Controllers for editable fields
|
||||
final TextEditingController _nameController = TextEditingController();
|
||||
final TextEditingController _contactController = TextEditingController();
|
||||
final TextEditingController _dobController = TextEditingController();
|
||||
final TextEditingController _genderController = TextEditingController();
|
||||
final TextEditingController _addressController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadProfile();
|
||||
}
|
||||
|
||||
Future<void> _loadProfile() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
int? id = prefs.getInt('customerId');
|
||||
if (id == null) {
|
||||
Get.snackbar("Error", "Customer ID not found");
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() => isLoading = true);
|
||||
|
||||
final repo = LoginRepository();
|
||||
final fetchedProfile = await repo.fetchProfile(id.toString());
|
||||
|
||||
if (fetchedProfile != null) {
|
||||
_nameController.text = fetchedProfile.firstname ?? '';
|
||||
_contactController.text = fetchedProfile.contactno ?? '';
|
||||
_dobController.text = fetchedProfile.dob != null
|
||||
? fetchedProfile.dob!.toIso8601String()
|
||||
: ''; _genderController.text = fetchedProfile.gender ?? '';
|
||||
_addressController.text = fetchedProfile.address ?? '';
|
||||
Name = fetchedProfile.firstname ?? '';
|
||||
Profile = fetchedProfile.profileimage ?? '';
|
||||
Number = fetchedProfile.contactno ?? '';
|
||||
Adress = fetchedProfile.address ?? '';
|
||||
}
|
||||
setState(() {
|
||||
profile = fetchedProfile;
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
Future<String?> uploadImageAndSave(
|
||||
File selectedImage, int customerId) async {
|
||||
try {
|
||||
var rng = Random();
|
||||
const String region = "sgp1";
|
||||
const String accessKey = "DO00NQER7N2FRYZAB2HR";
|
||||
const String secretKey = "nMDewX25IBEu1FM5dakK+v28/WbW3TzBAwq913+dxP0";
|
||||
const String bucketName = "nearle";
|
||||
const String folderName = "deals";
|
||||
|
||||
String fileName = 'profile-${rng.nextInt(1000)}-$customerId.jpg';
|
||||
String endpointUrl =
|
||||
"https://$bucketName.$region.digitaloceanspaces.com/$folderName/$fileName";
|
||||
|
||||
// Initialize Minio client
|
||||
final minio = Minio(
|
||||
endPoint: '$region.digitaloceanspaces.com',
|
||||
accessKey: accessKey,
|
||||
secretKey: secretKey,
|
||||
region: region,
|
||||
useSSL: true,
|
||||
);
|
||||
|
||||
// Upload file
|
||||
await minio.fPutObject(
|
||||
bucketName,
|
||||
'$folderName/$fileName',
|
||||
selectedImage.path,
|
||||
metadata: {
|
||||
'Content-Type': 'image/jpeg',
|
||||
'x-amz-acl': 'public-read', // Set ACL to public-read if needed
|
||||
},
|
||||
);
|
||||
|
||||
print("File uploaded successfully: $endpointUrl");
|
||||
return endpointUrl;
|
||||
} catch (e) {
|
||||
Get.snackbar("Error", "Image upload failed: $e");
|
||||
print("Upload error: $e");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, String>> fetchAddressDetails(String address) async {
|
||||
final url = Uri.parse(
|
||||
'https://nominatim.openstreetmap.org/search'
|
||||
'?q=${Uri.encodeComponent(address)}'
|
||||
'&format=json'
|
||||
'&addressdetails=1',
|
||||
);
|
||||
|
||||
final response = await http.get(
|
||||
url,
|
||||
headers: {'User-Agent': 'FlutterApp'},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = jsonDecode(response.body);
|
||||
|
||||
if (data.isNotEmpty) {
|
||||
final item = data[0];
|
||||
final addr = item['address'] ?? {};
|
||||
|
||||
return {
|
||||
"suburb": addr['suburb'] ?? addr['neighbourhood'] ?? '',
|
||||
"city": addr['city'] ?? addr['town'] ?? addr['village'] ?? '',
|
||||
"state": addr['state'] ?? '',
|
||||
"postcode": addr['postcode'] ?? '',
|
||||
"landmark": addr['road'] ?? addr['attraction'] ?? '',
|
||||
"latitude": item['lat'] ?? '',
|
||||
"longitude": item['lon'] ?? '',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// fallback (never null)
|
||||
return {
|
||||
"suburb": '',
|
||||
"city": '',
|
||||
"state": '',
|
||||
"postcode": '',
|
||||
"landmark": '',
|
||||
"latitude": '',
|
||||
"longitude": '',
|
||||
};
|
||||
}
|
||||
|
||||
Future<void> _updateProfile() async {
|
||||
if (profile == null) {
|
||||
Get.snackbar("Error", "Profile data not loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
int? customerId = prefs.getInt('customerId');
|
||||
|
||||
if (customerId == null) {
|
||||
Get.snackbar("Error", "Customer ID not found");
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() => isLoading = true);
|
||||
|
||||
String? uploadedFileUrl;
|
||||
|
||||
/// Upload image if selected
|
||||
if (pickedImage != null) {
|
||||
uploadedFileUrl = await uploadImageAndSave(pickedImage!, customerId);
|
||||
}
|
||||
|
||||
/// 🌍 AUTO-FETCH ADDRESS DETAILS
|
||||
final addressDetails =
|
||||
await fetchAddressDetails(_addressController.text.trim());
|
||||
|
||||
final data = {
|
||||
"customerid": customerId,
|
||||
"configid": profile!.configid ?? 1,
|
||||
"firstname": _nameController.text.trim(),
|
||||
"applocationid": profile!.applocationid ?? 91,
|
||||
"contactno": _contactController.text.trim(),
|
||||
"address": _addressController.text.trim(),
|
||||
"gender": _genderController.text.trim(),
|
||||
"dob": _dobController.text.trim(),
|
||||
"profileimage": uploadedFileUrl ?? profile!.profileimage,
|
||||
|
||||
// ✅ AUTO FILLED
|
||||
"doorno": "",
|
||||
"suburb": addressDetails['suburb'],
|
||||
"city": addressDetails['city'],
|
||||
"state": addressDetails['state'],
|
||||
"postcode": addressDetails['postcode'],
|
||||
"landmark": addressDetails['landmark'],
|
||||
"latitude": addressDetails['latitude'],
|
||||
"longitude": addressDetails['longitude'],
|
||||
};
|
||||
|
||||
print("PROFILE UPDATE REQUEST => $data");
|
||||
|
||||
try {
|
||||
final repo = LoginRepository();
|
||||
final response = await repo.updateProfile(data);
|
||||
|
||||
setState(() => isLoading = false);
|
||||
|
||||
if (response != null && response['status'] == true) {
|
||||
Get.snackbar("Success", response['message'] ?? "Profile updated");
|
||||
Navigator.pop(context, true);
|
||||
} else {
|
||||
Get.snackbar(
|
||||
"Error",
|
||||
response?['message'] ?? "Profile update failed",
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
setState(() => isLoading = false);
|
||||
Get.snackbar("Error", e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
List<dynamic> predictions = [];
|
||||
|
||||
// Replace with your API key
|
||||
final String googleApiKey = "AIzaSyBhkGfnq27sN0wV5y_S-M2KojpFTk_by-Q";
|
||||
|
||||
Future<void> searchPlace(String input) async {
|
||||
if (input.isEmpty) {
|
||||
setState(() {
|
||||
predictions = [];
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
String url =
|
||||
'https://maps.googleapis.com/maps/api/place/autocomplete/json?input=$input&types=geocode&components=country:in&key=$googleApiKey';
|
||||
|
||||
final response = await http.get(Uri.parse(url));
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
setState(() {
|
||||
predictions = data['predictions'];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Size screenSize = MediaQuery.of(context).size;
|
||||
return SafeArea(
|
||||
top: false,
|
||||
bottom: true,
|
||||
child: Scaffold(
|
||||
backgroundColor: const Color(0xFFFAFAFA),
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
shadowColor: Colors.black.withOpacity(0.05),
|
||||
leading: IconButton(
|
||||
icon: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade100,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: const Icon(Icons.arrow_back_ios_new,
|
||||
color: Colors.black87, size: 18),
|
||||
),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
title: const ReusableTextWidget(
|
||||
text: "Edit Profile",
|
||||
color: Colors.black87,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: isLoading
|
||||
? _buildShimmer(screenSize)
|
||||
: SingleChildScrollView(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: screenSize.width * 0.05,
|
||||
vertical: screenSize.height * 0.02,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Profile Image Section
|
||||
Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Stack(
|
||||
alignment: Alignment.bottomRight,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
ColorConstants.primaryColor
|
||||
.withOpacity(0.1),
|
||||
ColorConstants.primaryColor
|
||||
.withOpacity(0.05),
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: ColorConstants.primaryColor
|
||||
.withOpacity(0.15),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.all(4),
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.white,
|
||||
),
|
||||
child: CircleAvatar(
|
||||
radius: screenSize.height * 0.09,
|
||||
backgroundColor: Colors.grey.shade100,
|
||||
backgroundImage: pickedImage != null
|
||||
? FileImage(pickedImage!)
|
||||
as ImageProvider
|
||||
: (profile?.profileimage != null &&
|
||||
profile!.profileimage!.isNotEmpty
|
||||
? NetworkImage(
|
||||
profile!.profileimage!)
|
||||
: null),
|
||||
child: (pickedImage == null &&
|
||||
(profile?.profileimage == null ||
|
||||
profile!.profileimage!.isEmpty))
|
||||
? Icon(
|
||||
Icons.person_outline,
|
||||
size: 60,
|
||||
color: Colors.grey.shade400,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: _pickImage,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorConstants.primaryColor,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: Colors.white,
|
||||
width: 3,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: ColorConstants.primaryColor
|
||||
.withOpacity(0.3),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.camera_alt_rounded,
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: screenSize.height * 0.015),
|
||||
Text(
|
||||
"Tap to change profile photo",
|
||||
style: TextStyle(
|
||||
color: Colors.grey.shade600,
|
||||
fontSize: 13,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: screenSize.height * 0.04),
|
||||
|
||||
// Form Section
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.04),
|
||||
blurRadius: 15,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildLabel("Full Name"),
|
||||
const SizedBox(height: 8),
|
||||
_buildEditableField(
|
||||
_nameController,
|
||||
Icons.person_outline_rounded,
|
||||
"Enter your name",
|
||||
),
|
||||
SizedBox(height: screenSize.height * 0.025),
|
||||
_buildLabel("Contact Number"),
|
||||
const SizedBox(height: 8),
|
||||
_buildEditableField(
|
||||
_contactController,
|
||||
Icons.phone_outlined,
|
||||
"Enter your phone number",
|
||||
),
|
||||
SizedBox(height: screenSize.height * 0.025),
|
||||
_buildLabel("Address"),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF5F6FA),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: Colors.grey.shade200,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: TextFormField(
|
||||
controller: _addressController,
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
hintText: "Search location...",
|
||||
hintStyle: TextStyle(
|
||||
color: Colors.grey.shade400,
|
||||
fontSize: 15,
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Colors.transparent,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 16,
|
||||
horizontal: 16,
|
||||
),
|
||||
prefixIcon: Icon(
|
||||
Icons.location_on_outlined,
|
||||
color: ColorConstants.primaryColor,
|
||||
size: 22,
|
||||
),
|
||||
suffixIcon: _addressController.text.isNotEmpty
|
||||
? IconButton(
|
||||
icon: Icon(
|
||||
Icons.close_rounded,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
onPressed: () {
|
||||
_addressController.clear();
|
||||
setState(() {
|
||||
predictions = [];
|
||||
});
|
||||
},
|
||||
)
|
||||
: null,
|
||||
border: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(
|
||||
color: ColorConstants.primaryColor,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
searchPlace(value);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// Display suggestions
|
||||
if (predictions.isNotEmpty)
|
||||
Container(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: screenSize.height * 0.3,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
border: Border.all(
|
||||
color: Colors.grey.shade200,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.08),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
itemCount: predictions.length,
|
||||
separatorBuilder: (context, index) => Divider(
|
||||
height: 1,
|
||||
color: Colors.grey.shade100,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final prediction = predictions[index];
|
||||
return ListTile(
|
||||
dense: true,
|
||||
leading: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorConstants.primaryColor
|
||||
.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.location_on,
|
||||
color: ColorConstants.primaryColor,
|
||||
size: 18,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
prediction['description'],
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
_addressController.text =
|
||||
prediction['description'];
|
||||
setState(() {
|
||||
predictions = [];
|
||||
});
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: screenSize.height * 0.1),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Bottom Button
|
||||
bottomNavigationBar: Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: screenSize.width * 0.05,
|
||||
vertical: screenSize.height * 0.02,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, -5),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Container(
|
||||
height: screenSize.height * 0.065,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
ColorConstants.primaryColor,
|
||||
ColorConstants.primaryColor.withOpacity(0.8),
|
||||
],
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: ColorConstants.primaryColor.withOpacity(0.3),
|
||||
blurRadius: 15,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.transparent,
|
||||
shadowColor: Colors.transparent,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
),
|
||||
onPressed: _updateProfile,
|
||||
child: const ReusableTextWidget(
|
||||
text: "Update Profile",
|
||||
color: Colors.white,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Label
|
||||
Widget _buildLabel(String text) {
|
||||
return ReusableTextWidget(
|
||||
text: text,
|
||||
color: const Color(0xFF2D3142),
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
);
|
||||
}
|
||||
|
||||
/// Editable Text Field
|
||||
Widget _buildEditableField(
|
||||
TextEditingController controller,
|
||||
IconData icon,
|
||||
String hint,
|
||||
) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF5F6FA),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: Colors.grey.shade200,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: TextFormField(
|
||||
controller: controller,
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
hintText: hint,
|
||||
hintStyle: TextStyle(
|
||||
color: Colors.grey.shade400,
|
||||
fontSize: 15,
|
||||
),
|
||||
prefixIcon: Icon(
|
||||
icon,
|
||||
color: ColorConstants.primaryColor,
|
||||
size: 22,
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Colors.transparent,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 16,
|
||||
horizontal: 16,
|
||||
),
|
||||
border: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(
|
||||
color: ColorConstants.primaryColor,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Shimmer effect while loading
|
||||
Widget _buildShimmer(Size screenSize) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(height: screenSize.height * 0.04),
|
||||
|
||||
/// Profile image shimmer
|
||||
Shimmer.fromColors(
|
||||
baseColor: Colors.grey.shade200,
|
||||
highlightColor: Colors.grey.shade50,
|
||||
child: Container(
|
||||
width: screenSize.height * 0.18,
|
||||
height: screenSize.height * 0.18,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.grey.shade300,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: screenSize.height * 0.01),
|
||||
|
||||
/// Helper text shimmer
|
||||
Shimmer.fromColors(
|
||||
baseColor: Colors.grey.shade200,
|
||||
highlightColor: Colors.grey.shade50,
|
||||
child: Container(
|
||||
height: 14,
|
||||
width: screenSize.width * 0.4,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade300,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: screenSize.height * 0.04),
|
||||
|
||||
/// Form container shimmer
|
||||
Shimmer.fromColors(
|
||||
baseColor: Colors.grey.shade200,
|
||||
highlightColor: Colors.grey.shade50,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade300,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
children: List.generate(
|
||||
3,
|
||||
(_) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
height: 14,
|
||||
width: 100,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
height: 50,
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user