Initial commit of Flutter project

This commit is contained in:
2025-09-19 11:30:38 +05:30
parent 1f0ec17edc
commit 4a9ae0a3b3
28 changed files with 2033 additions and 594 deletions

View File

@@ -1,14 +1,28 @@
import 'package:flutter/material.dart';
import 'package:glowwheels/helpers/shopid_helper.dart';
import 'package:provider/provider.dart';
import '../models/serviceboy_model.dart';
import '../provider/serviceboy_provider.dart';
import 'package:google_fonts/google_fonts.dart';
class AssignServiceBoyDialog extends StatelessWidget {
final List<ServiceBoy> serviceBoys;
import 'package:glowwheels/models/serviceboy_model.dart';
import '../provider/serviceboy_provider.dart';
import '../provider/order_provider.dart';
const AssignServiceBoyDialog({super.key, required this.serviceBoys});
class AssignServiceBoyDialog extends StatefulWidget {
final List<ServiceBoy> serviceBoys;
final String orderId;
const AssignServiceBoyDialog({
Key? key,
required this.serviceBoys,
required this.orderId,
}) : super(key: key);
@override
State<AssignServiceBoyDialog> createState() => _AssignServiceBoyDialogState();
}
class _AssignServiceBoyDialogState extends State<AssignServiceBoyDialog> {
bool _isLoading = false;
@override
Widget build(BuildContext context) {
@@ -19,14 +33,17 @@ class AssignServiceBoyDialog extends StatelessWidget {
return AlertDialog(
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
title: const Text("Select Service Boy", style: TextStyle(fontWeight: FontWeight.bold)),
title: const Text(
"Select Service Boy",
style: TextStyle(fontWeight: FontWeight.bold),
),
content: SizedBox(
height: 200,
width: double.maxFinite,
child: ListView.builder(
itemCount: serviceBoys.length,
itemCount: widget.serviceBoys.length,
itemBuilder: (context, index) {
final boy = serviceBoys[index];
final boy = widget.serviceBoys[index];
final isSelected = assignProvider.selectedBoy == boy;
return GestureDetector(
@@ -35,8 +52,9 @@ class AssignServiceBoyDialog extends StatelessWidget {
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.symmetric(vertical: 4),
decoration: BoxDecoration(
color: isSelected ? Color.fromRGBO(0, 80, 170, 1) : Colors.transparent,
color: isSelected ? const Color.fromRGBO(0, 80, 170, 1) : Colors.transparent,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey.shade300),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -64,28 +82,80 @@ class AssignServiceBoyDialog extends StatelessWidget {
),
actions: [
TextButton(
child: Text("CANCEL",style: GoogleFonts.poppins(
fontSize: 14,color: Color.fromRGBO(25, 25, 112, 1)
,fontWeight: FontWeight.w400
),),
onPressed: () {
Navigator.of(context).pop(); // return null
},
onPressed: () => Navigator.of(context).pop(),
child: Text(
"CANCEL",
style: GoogleFonts.poppins(
fontSize: 14,
color: const Color.fromRGBO(25, 25, 112, 1),
fontWeight: FontWeight.w400,
),
),
),
ElevatedButton(
onPressed: assignProvider.selectedBoy != null
? () {
Navigator.of(context).pop(assignProvider.selectedBoy);
onPressed: assignProvider.selectedBoy != null && !_isLoading
? () async {
setState(() {
_isLoading = true;
});
final selectedBoy = assignProvider.selectedBoy!;
final orderProvider = Provider.of<OrdersProvider>(context, listen: false);
final shopId = getShopId(context);
try {
await orderProvider.assignServiceBoyToOrder(
orderId: widget.orderId,
serviceBoyId: selectedBoy.id,
shopId: shopId!,
);
if (mounted) {
Navigator.of(context, rootNavigator: true).pop(
selectedBoy
); // ✅ return selected boy
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Service boy assigned successfully!')),
);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to assign service boy: $e')),
);
}
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
: null,
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF1B1464),
backgroundColor: const Color(0xFF1B1464),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)),
),
child: Text("Assign",style: GoogleFonts.inter(
fontSize: 12,color: Colors.white
,fontWeight: FontWeight.w500
),),
child: _isLoading
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: Text(
"Assign",
style: GoogleFonts.inter(
fontSize: 12,
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
),
],
);
@@ -94,4 +164,3 @@ class AssignServiceBoyDialog extends StatelessWidget {
);
}
}

View File

@@ -1,8 +1,12 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:glowwheels/helpers/shopid_helper.dart';
import 'package:glowwheels/provider/order_provider.dart';
import 'package:glowwheels/provider/serviceboy_provider.dart';
import 'package:google_fonts/google_fonts.dart';
import '../models/order_model.dart';
import '../models/serviceboy_model.dart';
import 'package:glowwheels/models/order_model.dart';
import 'package:provider/provider.dart';
import 'package:glowwheels/models/serviceboy_model.dart';
import 'assign_serviceboy_dialog.dart';
class OrderCard extends StatefulWidget {
@@ -16,12 +20,18 @@ class OrderCard extends StatefulWidget {
class _OrderCardState extends State<OrderCard> {
ServiceBoy? assignedBoy;
late String shopId = '';
bool isLoading = false;
late String orderStatus=widget.order.status;
@override
void initState() {
assignedBoy = widget.order.assignedServiceBoy;
super.initState();
}
List<ServiceBoy> serviceBoys = [
ServiceBoy(name: 'John Doe', phone: '9875643210'),
ServiceBoy(name: 'Amit Raj', phone: '9765432180'),
ServiceBoy(name: 'Manoj Sinha', phone: '9543219876'),
];
@override
Widget build(BuildContext context) {
@@ -40,18 +50,31 @@ class _OrderCardState extends State<OrderCard> {
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.asset(widget.order.imagePath, width: 100, height: 100, fit: BoxFit.contain),
widget.order.service.vehicle.image.isNotEmpty
? Image.network(
widget.order.service.vehicle.image,
width: 100,
height: 100,
fit: BoxFit.contain,
)
: SizedBox(
width: 100,
height: 100,
child: Center(child: Icon(Icons.image_not_supported)),
),
SizedBox(width: 16),
Expanded(
child: Column(
children: [
_buildRow("Customer Name", widget.order.customerName),
_buildRow("Mobile Number", widget.order.mobileNumber),
_buildRow("Service Type", widget.order.serviceType),
_buildRow("Service", widget.order.service),
_buildRow("Price", widget.order.price),
_buildRow("Service Time", widget.order.time),
_buildRow("Customer Name", widget.order.user.name),
_buildRow("Mobile Number", widget.order.user.phone.toString()),
_buildRow("Service Type", widget.order.service.serviceType),
_buildRow("Service", widget.order.service.serviceName),
_buildRow("Price", widget.order.service.price.toString()),
_buildRow("Service Time", widget.order.timeSlot),
_buildRow("Service Date", widget.order.date),
(widget.order.address!=null)?_buildRow("Location", widget.order.address!):SizedBox(),
],
),
),
@@ -60,7 +83,9 @@ class _OrderCardState extends State<OrderCard> {
SizedBox(height: 12),
Center(
child: Text(
widget.order.carName,
widget.order.service.manufacture.manufacture +
" " +
widget.order.service.model.model,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
),
@@ -68,46 +93,210 @@ class _OrderCardState extends State<OrderCard> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
widget.order.status,
style: TextStyle(
color: widget.order.status == "Confirmed" ? Colors.green : Colors.orange,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
assignedBoy == null
? ElevatedButton(
onPressed: () async {
final selected = await showDialog<ServiceBoy>(
context: context,
builder: (_) => AssignServiceBoyDialog(serviceBoys: serviceBoys),
);
GestureDetector(
onTap: () async {
String? newStatus;
String? dialogMessage;
if (selected != null) {
setState(() {
assignedBoy = selected;
});
if (orderStatus == "pending") {
newStatus = "confirmed";
dialogMessage = "Are you sure you want to change the status to Confirmed?";
} else if (orderStatus == "confirmed" && assignedBoy!=null) {
newStatus = "completed";
dialogMessage = "Are you sure you want to change the status to Completed?";
}
if (newStatus != null && dialogMessage != null) {
final confirm = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text("Confirm Status Change"),
content: Text(dialogMessage!),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text("Cancel"),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text("OK"),
),
],
),
);
if (confirm == true) {
try {
setState(() => isLoading = true);
final orderProvider = Provider.of<OrdersProvider>(context, listen: false);
await orderProvider.updateOrderStatus(
orderId: widget.order.id,
status: newStatus,
);
setState(() {
orderStatus = newStatus!;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Order status updated to $newStatus")),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Failed to update status: $e")),
);
} finally {
setState(() => isLoading = false);
}
}
}
},
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF1B1464),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)),
),
child: Text("Assign",style: GoogleFonts.inter(
fontSize: 12,color: Colors.white
,fontWeight: FontWeight.w500
),),
child: Text(
orderStatus,
style: TextStyle(
color:orderStatus == "confirmed" || orderStatus == "completed"
? Colors.green
: orderStatus == "pending"
? Colors.orange
: Colors.red,
fontWeight: FontWeight.bold,
fontSize: 16,
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Assigned to: ${assignedBoy!.name}",
style: const TextStyle(fontWeight: FontWeight.bold)),
Text("Phone: ${assignedBoy!.phone}"),
],
// Visual cue for clickable
),
),
),
if (assignedBoy == null &&
orderStatus.toLowerCase() == "confirmed")
ElevatedButton(
onPressed: isLoading
? null
: () async {
setState(() {
isLoading = true;
});
final serviceBoyProvider =
Provider.of<ServiceBoyProvider>(context, listen: false);
final shopId = getShopId(context);
await serviceBoyProvider.fetchServiceBoys(shopId!);
final selected = await showDialog<ServiceBoy>(
context: context,
builder: (_) => AssignServiceBoyDialog(
serviceBoys: serviceBoyProvider.serviceBoys,
orderId: widget.order.id,
),
);
if (selected != null) {
setState(() {
assignedBoy = selected;
});
}
setState(() {
isLoading = false;
});
},
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF1B1464),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30)),
),
child: isLoading
? SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2,
),
)
: Text(
"Assign",
style: GoogleFonts.inter(
fontSize: 12,
color: Colors.white,
fontWeight: FontWeight.w500),
),
)
else if (orderStatus.toLowerCase() == "pending")
ElevatedButton(
onPressed: () async{
final confirm = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text("Confirm Status Change"),
content: Text("Are you sure you want to cancel the order?"!),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text("Cancel"),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text("OK"),
),
],
),
);
if (confirm == true) {
try {
setState(() => isLoading = true);
final orderProvider = Provider.of<OrdersProvider>(context, listen: false);
await orderProvider.updateOrderStatus(
orderId: widget.order.id,
status: "cancelled by admin",
);
setState(() {
orderStatus = "cancelled by admin";
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Order status updated to cancelled by admin")),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Failed to update status: $e")),
);
} finally {
setState(() => isLoading = false);
}
}
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)),
),
child: Text(
"Cancel",
style: GoogleFonts.inter(
fontSize: 12, color: Colors.white, fontWeight: FontWeight.w500),
),
)
else if (orderStatus.toLowerCase() == "completed")
SizedBox()
else if (orderStatus.toLowerCase() == "cancelled by admin")
SizedBox()
else if (assignedBoy != null)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Assigned to: ${assignedBoy!.name}",
style: const TextStyle(fontWeight: FontWeight.bold)),
Text("Phone: ${assignedBoy!.phone}"),
],
)
],
)
],
@@ -121,18 +310,20 @@ class _OrderCardState extends State<OrderCard> {
padding: const EdgeInsets.symmetric(vertical: 3),
child: Row(
children: [
Expanded(flex: 2, child: Text(label, style: GoogleFonts.inter(
fontSize: 10,color: Colors.black
,fontWeight: FontWeight.w500
))),
Expanded(flex: 3, child: Text(value, style: GoogleFonts.inter(
fontSize: 10,color: Colors.black
,fontWeight: FontWeight.w500
))),
Expanded(
flex: 2,
child: Text(label,
style: GoogleFonts.inter(
fontSize: 10, color: Colors.black, fontWeight: FontWeight.w500)),
),
Expanded(
flex: 3,
child: Text(value,
style: GoogleFonts.inter(
fontSize: 10, color: Colors.black, fontWeight: FontWeight.w500)),
),
],
),
);
}
}

View File

@@ -1,52 +1,66 @@
import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.dart';
import 'package:glowwheels/models/shop_profile_model.dart';
class ProfileHeader extends StatelessWidget {
const ProfileHeader({super.key});
final ShopProfileModel? shopProfile;
const ProfileHeader({super.key, this.shopProfile});
@override
Widget build(BuildContext context) {
if (shopProfile == null) {
// Show skeleton loader
return _buildSkeleton(context);
}
final name = shopProfile!.name;
final phone = shopProfile!.phone;
final email = shopProfile!.email;
final imageUrl = (shopProfile!.images.isNotEmpty) ? shopProfile!.images.first : null;
return Container(
color: Colors.white,
padding: const EdgeInsets.all(16),
child: Row(
children: [
// Circular Profile Image
CircleAvatar(
radius: 40,
backgroundImage: AssetImage('assets/images/shop_image.jpg'), // Replace with your asset
backgroundImage: imageUrl != null
? NetworkImage(imageUrl)
: const AssetImage('assets/images/shop_image.jpg') as ImageProvider,
),
const SizedBox(width: 16),
// Details Column
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
children: [
Row(
children: [
ImageIcon(AssetImage("assets/icon/account_icon.png")),
SizedBox(width: 8),
const ImageIcon(AssetImage("assets/icon/account_icon.png")),
const SizedBox(width: 8),
Expanded(
child: Text(
'Omkara Car Wash Center',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
name,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
),
),
],
),
SizedBox(height: 8),
const SizedBox(height: 8),
Row(
children: [
ImageIcon(AssetImage("assets/icon/contact_icon.png")),
SizedBox(width: 8),
Text('+91 9999988888'),
const ImageIcon(AssetImage("assets/icon/contact_icon.png")),
const SizedBox(width: 8),
Text(phone),
],
),
SizedBox(height: 8),
const SizedBox(height: 8),
Row(
children: [
ImageIcon(AssetImage("assets/icon/Message_icon.png")),
SizedBox(width: 8),
Expanded(child: Text('loremipsum@gmail.com')),
const ImageIcon(AssetImage("assets/icon/Message_icon.png")),
const SizedBox(width: 8),
Expanded(child: Text(email)),
],
),
],
@@ -56,4 +70,38 @@ class ProfileHeader extends StatelessWidget {
),
);
}
Widget _buildSkeleton(BuildContext context) {
final baseColor = Colors.grey[300]!;
final highlightColor = Colors.grey[100]!;
return Container(
color: Colors.white,
padding: const EdgeInsets.all(16),
child: Shimmer.fromColors(
baseColor: baseColor,
highlightColor: highlightColor,
child: Row(
children: [
Container(
width: 80,
height: 80,
decoration: BoxDecoration(color: baseColor, shape: BoxShape.circle),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(height: 20, color: baseColor, margin: const EdgeInsets.only(bottom: 12)),
Container(height: 16, width: 150, color: baseColor, margin: const EdgeInsets.only(bottom: 12)),
Container(height: 16, width: 200, color: baseColor),
],
),
),
],
),
),
);
}
}