805 lines
28 KiB
Dart
805 lines
28 KiB
Dart
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),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
} |