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,10 +1,31 @@
import 'package:flutter/material.dart';
import 'package:glowwheels/helpers/shopid_helper.dart';
import 'package:provider/provider.dart';
import '../provider/serviceboy_provider.dart';
import 'add_serviceboy_screen.dart';
import 'edit_serviceboy_screen.dart';
import 'package:google_fonts/google_fonts.dart';
class ServiceBoyScreen extends StatelessWidget {
class ServiceBoyScreen extends StatefulWidget {
@override
State<ServiceBoyScreen> createState() => _ServiceBoyScreenState();
}
late String shopId='';
class _ServiceBoyScreenState extends State<ServiceBoyScreen> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
shopId = getShopId(context)!;
if (shopId != null) {
Provider.of<ServiceBoyProvider>(context, listen: false)
.fetchServiceBoys(shopId);
} else {
print("Shop ID is null");
}
});
}
@override
Widget build(BuildContext context) {
final provider = Provider.of<ServiceBoyProvider>(context);
@@ -13,7 +34,6 @@ class ServiceBoyScreen extends StatelessWidget {
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Color.fromRGBO(208, 235, 255, 1), Colors.white],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
@@ -26,54 +46,64 @@ class ServiceBoyScreen extends StatelessWidget {
SizedBox(height: 40),
Text(
'GLOWWHEELS',
style: GoogleFonts.inter(
fontSize: 32,color: Color.fromRGBO(25, 25, 112, 0.87)
,fontWeight: FontWeight.w700
)
style: GoogleFonts.inter(
fontSize: 32,
color: Color.fromRGBO(25, 25, 112, 0.87),
fontWeight: FontWeight.w700,
),
),
Text(
'Service Center',
style: GoogleFonts.inter(
fontSize: 24,color: Color.fromRGBO(25, 25, 112, 0.87)
,fontWeight: FontWeight.w400
)
style: GoogleFonts.inter(
fontSize: 24,
color: Color.fromRGBO(25, 25, 112, 0.87),
fontWeight: FontWeight.w400,
),
),
SizedBox(height: 20),
Text(
'Service Boy List',
style: GoogleFonts.inter(
fontSize: 16,color: Color.fromRGBO(33, 33, 33, 1)
,fontWeight: FontWeight.w500
)
style: GoogleFonts.inter(
fontSize: 16,
color: Color.fromRGBO(33, 33, 33, 1),
fontWeight: FontWeight.w500,
),
),
SizedBox(height: 10),
Expanded(
child: provider.serviceBoys.isEmpty
child: provider.isLoading
? Center(child: CircularProgressIndicator())
: provider.serviceBoys.isEmpty
? Center(child: Text("No service boys found"))
: ListView.builder(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
padding: EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
itemCount: provider.serviceBoys.length,
itemBuilder: (context, index) {
final boy = provider.serviceBoys[index];
return Card(
color: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
elevation: 2,
child: ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
contentPadding: EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
title: Text(
boy.name,
style: GoogleFonts.inter(
fontSize: 18,color: Color.fromRGBO(33, 33, 33, 0.78)
,fontWeight: FontWeight.w600
)
style: GoogleFonts.inter(
fontSize: 18,
color: Color.fromRGBO(33, 33, 33, 0.78),
fontWeight: FontWeight.w600,
),
),
subtitle: Text(
boy.phone,
style: GoogleFonts.inter(
fontSize: 16,color: Color.fromRGBO(33, 33, 33, 0.78)
,fontWeight: FontWeight.w400
)
style: GoogleFonts.inter(
fontSize: 16,
color: Color.fromRGBO(33, 33, 33, 0.78),
fontWeight: FontWeight.w400,
),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
@@ -83,7 +113,9 @@ class ServiceBoyScreen extends StatelessWidget {
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => EditServiceBoyScreen(serviceBoy: boy),
builder: (_) =>
EditServiceBoyScreen(
serviceBoy: boy),
),
);
},
@@ -92,7 +124,7 @@ class ServiceBoyScreen extends StatelessWidget {
_buildIconButton(
icon: Icons.delete,
onPressed: () {
_showDeleteDialog(context, '');
_showDeleteDialog(context, boy.id);
},
),
],
@@ -109,7 +141,11 @@ class ServiceBoyScreen extends StatelessWidget {
shape: CircleBorder(),
backgroundColor: Colors.white,
elevation: 4,
child: Icon(Icons.add, color: Color(0xFF1F1762),size: 25,),
child: Icon(
Icons.add,
color: Color(0xFF1F1762),
size: 25,
),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => AddServiceBoyScreen()),
@@ -120,7 +156,8 @@ class ServiceBoyScreen extends StatelessWidget {
);
}
Widget _buildIconButton({required IconData icon, required VoidCallback onPressed}) {
Widget _buildIconButton(
{required IconData icon, required VoidCallback onPressed}) {
return Container(
height: 40,
width: 40,
@@ -141,63 +178,72 @@ class ServiceBoyScreen extends StatelessWidget {
}
void _showDeleteDialog(BuildContext context, String id) {
final shopId = getShopId(context);
showModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
isScrollControlled: true,
builder: (_) => Container(
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset('assets/images/delete_serviceboy.png'),
SizedBox(height: 16),
Text(
'Delete Service boy?',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.black87,
builder: (BuildContext bottomSheetContext) {
return Container(
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset('assets/images/delete_serviceboy.png'),
SizedBox(height: 16),
Text(
'Delete Service boy?',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
),
SizedBox(height: 8),
Text(
'Are you sure you want to delete this service boy? You wont be able to undo this.',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
color: Colors.black54,
SizedBox(height: 8),
Text(
'Are you sure you want to delete this service boy? You wont be able to undo this.',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
color: Colors.black54,
),
),
),
SizedBox(height: 24),
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red[700],
padding: EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
SizedBox(height: 24),
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red[700],
padding: EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
onPressed: () async {
if (shopId != null) {
await Provider.of<ServiceBoyProvider>(
bottomSheetContext,
listen: false,
).deleteServiceBoy(id, shopId);
}
Navigator.pop(bottomSheetContext); // Correct context
},
child: Text(
'Confirm',
style: TextStyle(fontSize: 16, color: Colors.white),
),
),
onPressed: () {
// Provider.of<ServiceBoyProvider>(context, listen: false).deleteServiceBoy(id);
Navigator.pop(context);
},
child: Text(
'Confirm',
style: TextStyle(fontSize: 16, color: Colors.white),
),
),
),
SizedBox(height: 12),
],
),
),
SizedBox(height: 12),
],
),
);
},
);
}

View File

@@ -1,15 +1,41 @@
import 'package:flutter/material.dart';
import 'package:glowwheels/screens/privacy_policy_screen.dart';
import 'package:glowwheels/screens/profile_details_screen.dart';
import 'package:glowwheels/helpers/shopid_helper.dart';
import 'package:glowwheels/provider/shop_profile_provider.dart';
import 'package:glowwheels/provider/shop_provider.dart';
import 'package:glowwheels/screens/login_screen.dart';
import 'package:provider/provider.dart';
import 'package:glowwheels/screens/terms_condition_screen.dart';
import 'package:glowwheels/widgets/profile_header.dart';
import 'package:google_fonts/google_fonts.dart';
class AccountScreen extends StatelessWidget {
import 'package:glowwheels/screens/profile_details_screen.dart';
import 'package:glowwheels/screens/privacy_policy_screen.dart';
import 'package:glowwheels/screens/terms_condition_screen.dart';
class AccountScreen extends StatefulWidget {
const AccountScreen({super.key});
@override
State<AccountScreen> createState() => _AccountScreenState();
}
class _AccountScreenState extends State<AccountScreen> {
bool _isInit = true;
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (_isInit) {
// Fetch the shop profile once when screen loads
final shopId=getShopId(context);
Provider.of<ShopProfileProvider>(context, listen: false).fetchShopProfile(shopId!);
_isInit = false;
}
}
@override
Widget build(BuildContext context) {
final shopProfileProvider = Provider.of<ShopProfileProvider>(context);
final shopProfile = shopProfileProvider.shopProfile;
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
@@ -20,7 +46,6 @@ class AccountScreen extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: const [
ImageIcon(AssetImage("assets/icon/account_icon.png")),
SizedBox(width: 8),
Text(
"Account",
@@ -33,15 +58,17 @@ class AccountScreen extends StatelessWidget {
padding: const EdgeInsets.all(16),
child: Column(
children: [
// Profile Info
ProfileHeader(),
// Profile Info (shows shimmer skeleton if loading)
ProfileHeader(shopProfile: shopProfile),
const SizedBox(height: 24),
// Section Title
Align(
alignment: Alignment.centerLeft,
child: Text("Options",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
child: Text(
"Options",
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
),
const SizedBox(height: 12),
@@ -51,7 +78,7 @@ class AccountScreen extends StatelessWidget {
title: "Profile Details",
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => ServiceCenterDetailsScreen()),
MaterialPageRoute(builder: (_) => ServiceCenterDetailsScreen()),
);
},
),
@@ -60,7 +87,7 @@ class AccountScreen extends StatelessWidget {
title: "Terms and Conditions",
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => TermsOfServiceScreen()),
MaterialPageRoute(builder: (_) => TermsOfServiceScreen()),
);
},
),
@@ -69,14 +96,21 @@ class AccountScreen extends StatelessWidget {
title: "Privacy Policy",
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => PrivacyPolicyScreen()),
MaterialPageRoute(builder: (_) => PrivacyPolicyScreen()),
);
},
),
OptionTile(
icon: "assets/icon/logout_icon.png",
title: "Log Out",
onTap: () {},
onTap: () async{
await Provider.of<ShopProvider>(context, listen: false).logout();
Navigator.of(context, rootNavigator: true).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => LoginScreen()),
(route) => false,
);
},
isDestructive: true,
),
],
@@ -103,19 +137,20 @@ class OptionTile extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Card(
color: isDestructive ? Colors.white : Colors.white,
color: Colors.white,
elevation: 0.5,
margin: const EdgeInsets.symmetric(vertical: 6),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: ListTile(
leading: ImageIcon(AssetImage(icon),
color: Color.fromRGBO(25, 25, 112, 1)),
title: Text(title,
style: GoogleFonts.inter(
fontSize: 14,color: Color.fromRGBO(41, 45, 50, 1)
,fontWeight: FontWeight.w500
),),
leading: ImageIcon(AssetImage(icon), color: const Color.fromRGBO(25, 25, 112, 1)),
title: Text(
title,
style: const TextStyle(
fontSize: 14,
color: Color.fromRGBO(41, 45, 50, 1),
fontWeight: FontWeight.w500,
),
),
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
onTap: onTap,
),

View File

@@ -1,16 +1,33 @@
import 'package:flutter/material.dart';
import 'package:glowwheels/helpers/shopid_helper.dart';
import 'package:provider/provider.dart';
import '../provider/serviceboy_provider.dart';
import 'package:google_fonts/google_fonts.dart';
class AddServiceBoyScreen extends StatelessWidget {
import '../provider/serviceboy_provider.dart';
import '../provider/shop_provider.dart';
class AddServiceBoyScreen extends StatefulWidget {
@override
_AddServiceBoyScreenState createState() => _AddServiceBoyScreenState();
}
class _AddServiceBoyScreenState extends State<AddServiceBoyScreen> {
final _formKey = GlobalKey<FormState>();
final TextEditingController nameController = TextEditingController();
final TextEditingController phoneController = TextEditingController();
bool isLoading = false;
late String shopId='';
@override
void initState() {
// TODO: implement initState
shopId = getShopId(context)!;
}
@override
Widget build(BuildContext context) {
final serviceBoyProvider = Provider.of<ServiceBoyProvider>(context, listen: false);
return Scaffold(
backgroundColor: Color(0xFFF9FAF4), // light background as per screenshot
backgroundColor: Color(0xFFF9FAF4),
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
@@ -20,12 +37,12 @@ class AddServiceBoyScreen extends StatelessWidget {
),
title: Text(
'Add Service boy',
style: GoogleFonts.nunito(
fontSize: 18,color: Color.fromRGBO(26, 26, 26, 1)
,fontWeight: FontWeight.w500
)
style: GoogleFonts.nunito(
fontSize: 18,
color: Color.fromRGBO(26, 26, 26, 1),
fontWeight: FontWeight.w500,
),
),
//centerTitle: true,
),
body: Padding(
padding: const EdgeInsets.all(16),
@@ -33,15 +50,15 @@ class AddServiceBoyScreen extends StatelessWidget {
key: _formKey,
child: Column(
children: [
SizedBox(height: 30,),
SizedBox(height: 30),
TextFormField(
controller: nameController,
decoration: InputDecoration(
hintText: 'Name',
hintStyle: GoogleFonts.nunito(
fontSize: 14,color: Color.fromRGBO(26, 26, 26, 1)
,fontWeight: FontWeight.w400
hintStyle: GoogleFonts.nunito(
fontSize: 14,
color: Color.fromRGBO(26, 26, 26, 1),
fontWeight: FontWeight.w400,
),
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 14),
@@ -54,9 +71,10 @@ class AddServiceBoyScreen extends StatelessWidget {
keyboardType: TextInputType.phone,
decoration: InputDecoration(
hintText: 'Mobile Number',
hintStyle: GoogleFonts.nunito(
fontSize: 14,color: Color.fromRGBO(26, 26, 26, 1)
,fontWeight: FontWeight.w400
hintStyle: GoogleFonts.nunito(
fontSize: 14,
color: Color.fromRGBO(26, 26, 26, 1),
fontWeight: FontWeight.w400,
),
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 14),
@@ -67,28 +85,69 @@ class AddServiceBoyScreen extends StatelessWidget {
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
/* if (_formKey.currentState!.validate()) {
Provider.of<ServiceBoyProvider>(context, listen: false).addServiceBoy(
nameController.text,
phoneController.text,
);
Navigator.pop(context);
}*/
onPressed: isLoading
? null
: () async {
if (_formKey.currentState!.validate()) {
if (shopId == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Shop ID is missing')),
);
return;
}
setState(() {
isLoading = true;
});
try {
await serviceBoyProvider.addServiceBoy(
shopId,
nameController.text.trim(),
phoneController.text.trim(),
);
// After successful addition, navigate back
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Service boy added successfully!')),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to add service boy')),
);
} finally {
if (mounted) {
setState(() {
isLoading = false;
});
}
}
}
},
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF000B8C), // dark blue
backgroundColor: Color(0xFF000B8C),
padding: EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
),
child: Text('Submit', style:
GoogleFonts.inter(
fontSize: 18,color: Colors.white
,fontWeight: FontWeight.w600
child: isLoading
? SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2.5,
),
)
: Text(
'Submit',
style: GoogleFonts.inter(
fontSize: 18,
color: Colors.white,
fontWeight: FontWeight.w600,
),
),
),
),

View File

@@ -1,38 +1,81 @@
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 EditServiceBoyScreen extends StatelessWidget {
class EditServiceBoyScreen extends StatefulWidget {
final ServiceBoy serviceBoy;
EditServiceBoyScreen({required this.serviceBoy});
@override
_EditServiceBoyScreenState createState() => _EditServiceBoyScreenState();
}
class _EditServiceBoyScreenState extends State<EditServiceBoyScreen> {
final _formKey = GlobalKey<FormState>();
late final TextEditingController nameController =
TextEditingController(text: serviceBoy.name);
late final TextEditingController phoneController =
TextEditingController(text: serviceBoy.phone);
late final TextEditingController nameController;
late final TextEditingController phoneController;
bool isLoading = false;
@override
void initState() {
super.initState();
nameController = TextEditingController(text: widget.serviceBoy.name);
phoneController = TextEditingController(text: widget.serviceBoy.phone);
}
@override
void dispose() {
nameController.dispose();
phoneController.dispose();
super.dispose();
}
void _saveDetails(BuildContext context) async {
final name = nameController.text.trim();
final phone = phoneController.text.trim();
// Validation: at least one field must be non-empty and different
if (name.isEmpty && phone.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Please edit at least one field')),
);
return;
}
final shopId = getShopId(context)!;
setState(() => isLoading = true);
await Provider.of<ServiceBoyProvider>(context, listen: false)
.editServiceBoy(widget.serviceBoy.id, name, phone, shopId);
setState(() => isLoading = false);
Navigator.pop(context); // back to previous screen
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF9FAF4), // light background
backgroundColor: const Color(0xFFF9FAF4),
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
leading: IconButton(
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () => Navigator.pop(context),
),
title: Text(
title: Text(
'Edit Service boy Details',
style: GoogleFonts.nunito(
fontSize: 18,color: Color.fromRGBO(26, 26, 26, 1)
,fontWeight: FontWeight.w500
)
style: GoogleFonts.nunito(
fontSize: 18,
color: Color.fromRGBO(26, 26, 26, 1),
fontWeight: FontWeight.w500),
),
),
body: Padding(
padding: const EdgeInsets.all(16),
@@ -40,50 +83,40 @@ class EditServiceBoyScreen extends StatelessWidget {
key: _formKey,
child: Column(
children: [
SizedBox(height: 30,),
SizedBox(height: 30),
TextFormField(
controller: nameController,
decoration: InputDecoration(
decoration: InputDecoration(
hintText: 'Name',
hintStyle: GoogleFonts.nunito(
fontSize: 14,color: Color.fromRGBO(26, 26, 26, 1)
,fontWeight: FontWeight.w400
),
hintStyle: GoogleFonts.nunito(
fontSize: 14,
color: Color.fromRGBO(26, 26, 26, 1),
fontWeight: FontWeight.w400),
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 14),
contentPadding:
EdgeInsets.symmetric(horizontal: 12, vertical: 14),
),
validator: (value) => value!.isEmpty ? 'Enter name' : null,
),
const SizedBox(height: 25),
SizedBox(height: 25),
TextFormField(
controller: phoneController,
keyboardType: TextInputType.phone,
decoration: InputDecoration(
decoration: InputDecoration(
hintText: 'Mobile Number',
hintStyle: GoogleFonts.nunito(
fontSize: 14,color: Color.fromRGBO(26, 26, 26, 1)
,fontWeight: FontWeight.w400
),
hintStyle: GoogleFonts.nunito(
fontSize: 14,
color: Color.fromRGBO(26, 26, 26, 1),
fontWeight: FontWeight.w400),
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 14),
contentPadding:
EdgeInsets.symmetric(horizontal: 12, vertical: 14),
),
validator: (value) => value!.isEmpty ? 'Enter phone number' : null,
),
const Spacer(),
Spacer(),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
/* if (_formKey.currentState!.validate()) {
Provider.of<ServiceBoyProvider>(context, listen: false)
.editServiceBoy(
serviceBoy.id,
nameController.text,
phoneController.text,
);
Navigator.pop(context);
}*/
},
onPressed: isLoading ? null : () => _saveDetails(context),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF000B8C),
padding: const EdgeInsets.symmetric(vertical: 14),
@@ -91,10 +124,20 @@ class EditServiceBoyScreen extends StatelessWidget {
borderRadius: BorderRadius.circular(6),
),
),
child: Text('Save', style:GoogleFonts.inter(
fontSize: 18,color: Colors.white
,fontWeight: FontWeight.w600
)),
child: isLoading
? SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2,
),
)
: Text('Save',
style: GoogleFonts.inter(
fontSize: 18,
color: Colors.white,
fontWeight: FontWeight.w600)),
),
),
],

View File

@@ -1,60 +1,97 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:glowwheels/provider/shop_provider.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';
import '../screens/main_screen.dart';
class LoginScreen extends StatefulWidget {
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final TextEditingController _emailController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
final _formKey = GlobalKey<FormState>();
bool _isLoading = false;
bool _obscurePassword = true;
void _handleLogin(BuildContext context) async {
if (!_formKey.currentState!.validate()) return;
final shopProvider = Provider.of<ShopProvider>(context, listen: false);
setState(() => _isLoading = true);
final success = await shopProvider.login(
_emailController.text.trim(),
_passwordController.text.trim(),
);
setState(() => _isLoading = false);
if (success) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => MainScreen()),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Login failed. Check credentials.')),
);
}
}
import 'main_screen.dart';
class LoginScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SingleChildScrollView(
child: Column(
children: [
// Top Banner
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color.fromRGBO(80, 166, 242, 0.66),
Color.fromRGBO(168, 134, 255, 0.66),
Color.fromRGBO(86, 88, 255, 0.3828),
Color.fromRGBO(214, 246, 255, 0.66),
],
),
borderRadius: BorderRadius.only(bottomRight: Radius.elliptical(300, 110), )
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color.fromRGBO(80, 166, 242, 0.66),
Color.fromRGBO(168, 134, 255, 0.66),
Color.fromRGBO(86, 88, 255, 0.38),
Color.fromRGBO(214, 246, 255, 0.66),
],
),
borderRadius: BorderRadius.only(
bottomRight: Radius.elliptical(300, 110),
),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
const SizedBox(height: 30),
SizedBox(height: 30),
Image.asset('assets/images/signinlogo.png', height: 250),
const SizedBox(height: 20),
const SizedBox(height: 30),
SizedBox(height: 20),
Align(
alignment: Alignment.centerLeft,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Welcome back!", style:
GoogleFonts.istokWeb(
fontSize: 18,color: Color.fromRGBO(25, 25, 112, 0.87)
,fontWeight: FontWeight.w700
)),
Text("Welcome back!",
style: GoogleFonts.istokWeb(
fontSize: 18,
color: Color.fromRGBO(25, 25, 112, 0.87),
fontWeight: FontWeight.w700,
)),
Container(
width: 200,
child: Text("Please sign in to continue",
style:GoogleFonts.radioCanada(
fontSize: 24,
fontWeight: FontWeight.w700,
height: 0.9,
color: Color.fromRGBO(25, 25, 112, 0.87)
style: GoogleFonts.radioCanada(
fontSize: 24,
fontWeight: FontWeight.w700,
height: 0.9,
color: Color.fromRGBO(25, 25, 112, 0.87),
)),
),
],
@@ -64,48 +101,68 @@ class LoginScreen extends StatelessWidget {
),
),
),
// Login Form
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 20),
Text('Enter Email Id',style:GoogleFonts.radioCanada(
fontSize: 16,
fontWeight: FontWeight.w400,
//color: Color.fromRGBO(25, 25, 112, 0.87)
)),
const SizedBox(height: 5),
TextField(
SizedBox(height: 20),
Text('Enter Email Id', style: GoogleFonts.radioCanada(fontSize: 16)),
SizedBox(height: 5),
TextFormField(
controller: _emailController,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your email';
}
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
return 'Enter a valid email';
}
return null;
},
decoration: InputDecoration(
hintText: 'abcd@gmail.com',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15.0)
borderRadius: BorderRadius.circular(15.0),
),
),
),
const SizedBox(height: 20),
Text('Enter Password',style:GoogleFonts.radioCanada(
fontSize: 16,
fontWeight: FontWeight.w400,
//color: Color.fromRGBO(25, 25, 112, 0.87)
)),
const SizedBox(height: 5),
TextField(
obscureText: true,
SizedBox(height: 20),
Text('Enter Password', style: GoogleFonts.radioCanada(fontSize: 16)),
SizedBox(height: 5),
TextFormField(
controller: _passwordController,
obscureText: _obscurePassword,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your password';
}
return null;
},
decoration: InputDecoration(
hintText: 'Enter your password',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15.0)
borderRadius: BorderRadius.circular(15.0),
),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword ? Icons.visibility_off : Icons.visibility,
),
onPressed: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
),
suffixIcon: Icon(Icons.remove_red_eye_outlined),
),
),
const SizedBox(height: 30),
SizedBox(height: 30),
// Login Button
SizedBox(
width: double.infinity,
height: 50,
@@ -116,16 +173,16 @@ class LoginScreen extends StatelessWidget {
borderRadius: BorderRadius.circular(25),
),
),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context)=>MainScreen()));
},
child: Text('Login', style:
GoogleFonts.inter(
fontSize: 18,color: Colors.white
,fontWeight: FontWeight.w600
)
onPressed: _isLoading ? null : () => _handleLogin(context),
child: _isLoading
? CircularProgressIndicator(color: Colors.white)
: Text(
'Login',
style: GoogleFonts.inter(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.w700,
),
),
),
)
@@ -138,4 +195,4 @@ class LoginScreen extends StatelessWidget {
),
);
}
}
}

View File

@@ -1,43 +1,34 @@
import 'package:flutter/material.dart';
import 'package:glowwheels/helpers/shopid_helper.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';
import '../models/order_model.dart';
import 'package:glowwheels/models/order_model.dart';
import '../provider/order_provider.dart';
import '../widgets/order_card.dart';
class OrdersScreen extends StatelessWidget {
final TextStyle labelStyle = TextStyle(fontWeight: FontWeight.w500);
final TextStyle valueStyle = TextStyle(fontWeight: FontWeight.normal);
List<Order> orders = [
Order(
customerName: "Ankit Ghosh",
mobileNumber: "8617015476",
serviceType: "Doorstep Service",
service: "Foam Wash",
price: "₹ 104",
time: "10:00 - 11:00 AM",
date: "2025-05-28",
carName: "Mahindra XUV 700",
status: "Confirmed",
imagePath: "assets/images/car.jpg",
),
Order(
customerName: "Ravi Kumar",
mobileNumber: "9876543210",
serviceType: "Workshop",
service: "Interior Cleaning",
price: "₹ 150",
time: "12:00 - 1:00 PM",
date: "2025-05-29",
carName: "Hyundai Creta",
status: "Pending",
imagePath: "assets/images/bike.png",
),
// Add more orders...
];
class OrdersScreen extends StatefulWidget {
@override
_OrdersScreenState createState() => _OrdersScreenState();
}
class _OrdersScreenState extends State<OrdersScreen> {
late String shopId;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
shopId = getShopId(context)!;
Provider.of<OrdersProvider>(context, listen: false)
.fetchOrders(shopId, refresh: false);
});
}
Future<void> _refreshOrders() async {
shopId = getShopId(context)!;
await Provider.of<OrdersProvider>(context, listen: false)
.fetchOrders(shopId, refresh: true);
}
@override
Widget build(BuildContext context) {
@@ -67,30 +58,52 @@ class OrdersScreen extends StatelessWidget {
),
body: Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Consumer<OrderProvider>(
child: Consumer<OrdersProvider>(
builder: (context, orderProvider, _) {
final orders = orderProvider.orders;
if (orderProvider.isLoading && !orderProvider.isRefreshing) {
return Center(child: CircularProgressIndicator());
}
if (orders.isEmpty) {
return Center(
child: Image.asset(
'assets/images/noorder.png',
width: 200,
height: 200,
return RefreshIndicator(
onRefresh: _refreshOrders,
child: ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: [
SizedBox(height: 100),
Center(
child: Image.asset(
'assets/images/noorder.png',
width: 200,
height: 200,
),
),
SizedBox(height: 16),
Center(
child: Text(
'No orders found',
style: GoogleFonts.poppins(fontSize: 16),
),
),
],
),
);
}
return ListView.builder(
itemCount: orders.length,
itemBuilder: (context, index) {
return OrderCard(order: orders[index],);
},
return RefreshIndicator(
onRefresh: _refreshOrders,
child: ListView.builder(
itemCount: orders.length,
itemBuilder: (context, index) {
return OrderCard(order: orders[index]);
},
),
);
},
),
),
);
}
}

View File

@@ -1,24 +1,40 @@
import 'package:flutter/material.dart';
import 'package:glowwheels/helpers/shopid_helper.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';
import '../provider/shop_provider.dart';
import 'package:shimmer/shimmer.dart';
import '../provider/shop_profile_provider.dart';
class ServiceCenterDetailsScreen extends StatefulWidget {
const ServiceCenterDetailsScreen({super.key});
@override
State<ServiceCenterDetailsScreen> createState() => _ServiceCenterDetailsScreenState();
}
class _ServiceCenterDetailsScreenState extends State<ServiceCenterDetailsScreen> {
bool _isInit = true;
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (_isInit) {
final shopId = getShopId(context);
Provider.of<ShopProfileProvider>(context, listen: false).fetchShopProfile(shopId!);
_isInit = false;
}
}
class ServiceCenterDetailsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final shop = Provider.of<ShopProvider>(context).shop;
if (shop == null) {
return Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
final provider = Provider.of<ShopProfileProvider>(context);
final shop = provider.shopProfile;
return Scaffold(
backgroundColor: Color(0xFFEAF5FF),
backgroundColor: const Color(0xFFEAF5FF),
body: SafeArea(
child: Container(
decoration: BoxDecoration(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color.fromRGBO(208, 235, 255, 1), Colors.white],
begin: Alignment.topCenter,
@@ -35,9 +51,9 @@ class ServiceCenterDetailsScreen extends StatelessWidget {
children: [
GestureDetector(
onTap: () => Navigator.pop(context),
child: Icon(Icons.arrow_back_ios, color: Colors.black),
child: const Icon(Icons.arrow_back_ios, color: Colors.black),
),
SizedBox(width: 25),
const SizedBox(width: 25),
Expanded(
child: Padding(
padding: const EdgeInsets.only(top: 15.0),
@@ -52,22 +68,25 @@ class ServiceCenterDetailsScreen extends StatelessWidget {
),
),
),
SizedBox(width: 24),
const SizedBox(width: 24),
],
),
),
// Main content
Expanded(
child: SingleChildScrollView(
child: provider.isLoading
? _buildShimmer()
: shop == null
? const Center(child: Text('Failed to load shop details'))
: SingleChildScrollView(
child: Center(
child: Container(
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 10),
padding: EdgeInsets.all(8),
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
boxShadow: const [
BoxShadow(
color: Colors.black12,
blurRadius: 10,
@@ -79,36 +98,54 @@ class ServiceCenterDetailsScreen extends StatelessWidget {
children: [
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.asset(
"assets/images/shop_image.jpg",
child: shop.images != null && shop.images!.isNotEmpty
? Image.network(
shop.images[0]!,
height: 230,
width: double.infinity,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) =>
Icon(Icons.broken_image, size: 100),
const Icon(Icons.broken_image, size: 100),
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Container(
height: 230,
width: double.infinity,
color: Colors.grey[300],
child:
const Center(child: CircularProgressIndicator()),
);
},
)
: Image.asset(
"assets/images/shop_image.jpg",
height: 230,
width: double.infinity,
fit: BoxFit.cover,
),
),
SizedBox(height: 16),
const SizedBox(height: 16),
Text(
shop.shopName,
shop.name ?? 'No Shop Name',
style: GoogleFonts.inter(
fontSize: 24,
color: Colors.black,
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 16),
const SizedBox(height: 16),
InfoRow(
icon: "assets/icon/location_icon.png",
text: shop.address ?? 'Address not available',
),
InfoRow(
icon: "assets/icon/contact_icon.png",
text: shop.mobile,
text: shop.phone ?? 'Phone not available',
),
InfoRow(
icon: "assets/icon/Message_icon.png",
text: shop.email,
text: shop.email ?? 'Email not available',
),
],
),
@@ -122,6 +159,82 @@ class ServiceCenterDetailsScreen extends StatelessWidget {
),
);
}
Widget _buildShimmer() {
final baseColor = Colors.grey.shade300;
final highlightColor = Colors.grey.shade100;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
child: Shimmer.fromColors(
baseColor: baseColor,
highlightColor: highlightColor,
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Image placeholder (same size & rounded corners)
Container(
height: 230,
decoration: BoxDecoration(
color: baseColor,
borderRadius: BorderRadius.circular(12),
),
),
const SizedBox(height: 16),
// Shop name placeholder (matching width and height)
Container(
height: 30,
width: 220,
color: baseColor,
margin: const EdgeInsets.only(left: 8),
),
const SizedBox(height: 16),
// Location info row shimmer
_shimmerInfoRow(width: 200, baseColor: baseColor),
// Phone info row shimmer
_shimmerInfoRow(width: 140, baseColor: baseColor),
// Email info row shimmer
_shimmerInfoRow(width: 180, baseColor: baseColor),
],
),
),
),
);
}
Widget _shimmerInfoRow({required double width, required Color baseColor}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 15),
child: Row(
children: [
Container(
width: 24,
height: 24,
decoration: BoxDecoration(
color: baseColor,
borderRadius: BorderRadius.circular(4),
),
),
const SizedBox(width: 10),
Container(
height: 16,
width: width,
color: baseColor,
),
],
),
);
}
}
class InfoRow extends StatelessWidget {
@@ -129,6 +242,7 @@ class InfoRow extends StatelessWidget {
final String text;
const InfoRow({
super.key,
required this.icon,
required this.text,
});
@@ -141,7 +255,7 @@ class InfoRow extends StatelessWidget {
child: Row(
children: [
ImageIcon(AssetImage(icon)),
SizedBox(width: 10),
const SizedBox(width: 10),
Expanded(
child: Text(
text,

View File

@@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:glowwheels/models/shop_model.dart';
import 'package:glowwheels/provider/shop_provider.dart';
import 'package:provider/provider.dart';
import 'login_screen.dart';
import 'main_screen.dart';
class SplashDecider extends StatefulWidget {
const SplashDecider({super.key});
@override
State<SplashDecider> createState() => _SplashDeciderState();
}
class _SplashDeciderState extends State<SplashDecider> {
@override
void initState() {
super.initState();
_checkLoginStatus();
}
Future<void> _checkLoginStatus() async {
final shopBox = await Hive.openBox<ShopModel>('shopBox');
final tokenBox = await Hive.openBox<String>('tokenBox');
final shop = shopBox.get('shop');
final token = tokenBox.get('token');
if (shop != null && token != null && token.isNotEmpty) {
// ✅ Set the provider values if needed
Provider.of<ShopProvider>(context, listen: false).setShop(shop);
Provider.of<ShopProvider>(context, listen: false).setToken(token);
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => MainScreen()),
);
} else {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => LoginScreen()),
);
}
}
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
}