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,40 +1,55 @@
import java.util.Properties
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
val keystoreProperties = Properties().apply {
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
keystorePropertiesFile.inputStream().use { load(it) }
}
}
android {
namespace = "com.example.glowwheels"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileSdk = 34
ndkVersion = "25.1.8937393" // Optional, or remove if not used
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
isCoreLibraryDesugaringEnabled = true
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
jvmTarget = "11"
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.glowwheels"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
minSdk = 21
targetSdk = 34
versionCode = 1
versionName = "1.0.0"
}
signingConfigs {
create("release") {
storeFile = File(keystoreProperties["storeFile"] as String)
storePassword = keystoreProperties["storePassword"] as String
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String
}
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
signingConfig = signingConfigs.getByName("release")
isMinifyEnabled = false
isShrinkResources = false
}
}
}
@@ -42,3 +57,7 @@ android {
flutter {
source = "../.."
}
dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
}

View File

@@ -1,6 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:label="glowwheels"
android:label="Glowwheels Vendor"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity

20
lib/constants/api.dart Normal file
View File

@@ -0,0 +1,20 @@
class ApiConstants {
// Base URL
static const String baseUrl = 'https://backend.glowwheels.in/api';
// Auth
static String loginUrl = '$baseUrl/shop/login';
// Shop
static String shopDetails(String shopId) => '$baseUrl/shop/$shopId';
// Orders
static String ordersByShop(String shopId) => '$baseUrl/order/shop/$shopId';
static String assignServiceBoy(String orderId) => '$baseUrl/order/assign-serviceboy/$orderId';
static String updateOrderStatusByAdmin(String orderId) => '$baseUrl/order/update-status-admin/$orderId';
// Service Boy
static String serviceBoyDetails(String serviceBoyId) => '$baseUrl/service-boy/$serviceBoyId';
static String serviceBoysByShop(String shopId) => '$baseUrl/service-boy/shop/$shopId';
static String addServiceBoy = '$baseUrl/service-boy/add';
}

View File

@@ -0,0 +1,8 @@
import 'package:flutter/widgets.dart';
import 'package:glowwheels/provider/shop_provider.dart';
import 'package:provider/provider.dart';
String? getShopId(BuildContext context) {
return Provider.of<ShopProvider>(context, listen: false).shop?.user.id;
}

View File

@@ -5,10 +5,10 @@ import 'package:flutter/material.dart';
import 'package:glowwheels/models/shop_model.dart';
import 'package:glowwheels/provider/order_provider.dart';
import 'package:glowwheels/provider/serviceboy_provider.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:glowwheels/screens/main_screen.dart';
import 'package:glowwheels/screens/order_screen.dart';
import 'package:glowwheels/screens/splash_screen.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:hive_flutter/adapters.dart';
import 'package:provider/provider.dart';
@@ -17,13 +17,20 @@ void main() async {
await Hive.initFlutter();
Hive.registerAdapter(ShopModelAdapter());
Hive.registerAdapter(ShopDetailsAdapter());
if (!Hive.isBoxOpen('shopbox')) {
await Hive.openBox<ShopModel>('shopbox');
}
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => OrderProvider()),
ChangeNotifierProvider(create: (_) => OrdersProvider()),
ChangeNotifierProvider(create: (_) => ServiceBoyProvider()),
ChangeNotifierProvider(create: (_) => ShopProvider()),
ChangeNotifierProvider(create: (_) => ShopProfileProvider()),
],
child: GlowWheelsApp(),
),
@@ -35,7 +42,7 @@ class GlowWheelsApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: LoginScreen(),
home: SplashDecider(),
);
}
}

View File

@@ -1,31 +1,229 @@
import 'package:glowwheels/models/serviceboy_model.dart';
class Order {
final String customerName;
final String mobileNumber;
final String serviceType;
final String service;
final String price;
final String time;
final String id;
final User user;
final Shop shop;
final Service service;
final String? address;
final String date;
final String carName;
final String timeSlot;
final String status;
final String imagePath;
ServiceBoy? assignedBoy;
final String? serviceBoyId;
final DateTime createdAt;
final DateTime updatedAt;
final ServiceBoy? assignedServiceBoy;
Order({
required this.customerName,
required this.mobileNumber,
required this.serviceType,
required this.id,
required this.user,
required this.shop,
required this.service,
required this.price,
required this.time,
required this.address,
required this.date,
required this.carName,
required this.timeSlot,
required this.status,
required this.imagePath,
this.assignedBoy,
required this.serviceBoyId,
required this.createdAt,
required this.updatedAt,
this.assignedServiceBoy,
});
factory Order.fromJson(Map<String, dynamic> json) {
return Order(
id: json['_id'],
user: User.fromJson(json['userId']),
shop: Shop.fromJson(json['shopId']),
service: Service.fromJson(json['serviceId']),
address : json['address'],
date: json['date'],
timeSlot: json['timeSlot'],
status: json['status'],
serviceBoyId: json['serviceBoyId'],
createdAt: DateTime.parse(json['createdAt']),
updatedAt: DateTime.parse(json['updatedAt']),
assignedServiceBoy: json['assignedServiceBoy'] != null
? ServiceBoy.fromJson(json['assignedServiceBoy'])
: null,
);
}
Order copyWith({
String? id,
User? user,
Shop? shop,
Service? service,
String? date,
String? timeSlot,
String? status,
String? serviceBoyId,
DateTime? createdAt,
DateTime? updatedAt,
ServiceBoy? assignedServiceBoy,
}) {
return Order(
id: id ?? this.id,
user: user ?? this.user,
shop: shop ?? this.shop,
service: service ?? this.service,
date: date ?? this.date,
timeSlot: timeSlot ?? this.timeSlot,
status: status ?? this.status,
serviceBoyId: serviceBoyId ?? this.serviceBoyId,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
assignedServiceBoy: assignedServiceBoy ?? this.assignedServiceBoy, address: '',
);
}
}
class User {
final String id;
final String name;
final int phone;
User({
required this.id,
required this.name,
required this.phone,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['_id'],
name: json['name'],
phone: json['phone'],
);
}
}
class Shop {
final String id;
final String name;
final String address;
final double latitude;
final double longitude;
final String phone;
final String email;
final List<String> images;
final int rating;
Shop({
required this.id,
required this.name,
required this.address,
required this.latitude,
required this.longitude,
required this.phone,
required this.email,
required this.images,
required this.rating,
});
factory Shop.fromJson(Map<String, dynamic> json) {
return Shop(
id: json['_id'],
name: json['name'],
address: json['address'],
latitude: json['latitude'].toDouble(),
longitude: json['longitude'].toDouble(),
phone: json['phone'],
email: json['email'],
images: List<String>.from(json['images']),
rating: json['rating'],
);
}
}
class Service {
final String id;
final Vehicle vehicle;
final Manufacture manufacture;
final Model model;
final String serviceName;
final int price;
final String serviceType;
Service({
required this.id,
required this.vehicle,
required this.manufacture,
required this.model,
required this.serviceName,
required this.price,
required this.serviceType,
});
factory Service.fromJson(Map<String, dynamic> json) {
return Service(
id: json['_id'],
vehicle: Vehicle.fromJson(json['vehicleId']),
manufacture: Manufacture.fromJson(json['manufactureId']),
model: Model.fromJson(json['modelId']),
serviceName: json['serviceName'],
price: json['price'],
serviceType: json['serviceType'],
);
}
}
class Vehicle {
final String id;
final String vehicle;
final String image;
Vehicle({
required this.id,
required this.vehicle,
required this.image,
});
factory Vehicle.fromJson(Map<String, dynamic> json) {
return Vehicle(
id: json['_id'],
vehicle: json['vehicle'],
image: json['image'],
);
}
}
class Manufacture {
final String id;
final String manufacture;
final String image;
Manufacture({
required this.id,
required this.manufacture,
required this.image,
});
factory Manufacture.fromJson(Map<String, dynamic> json) {
return Manufacture(
id: json['_id'],
manufacture: json['manufacture'],
image: json['image'],
);
}
}
class Model {
final String id;
final String model;
final String image;
Model({
required this.id,
required this.model,
required this.image,
});
factory Model.fromJson(Map<String, dynamic> json) {
return Model(
id: json['_id'],
model: json['model'],
image: json['image'],
);
}
}

View File

@@ -0,0 +1,42 @@
import 'package:glowwheels/models/service_boy_response.dart';
import 'package:glowwheels/models/serviceboy_model.dart';
class ServiceBoyResponse {
final String id;
final String shopId;
final List<ServiceBoy> serviceBoy;
final String createdAt;
final String updatedAt;
ServiceBoyResponse({
required this.id,
required this.shopId,
required this.serviceBoy,
required this.createdAt,
required this.updatedAt,
});
factory ServiceBoyResponse.fromJson(Map<String, dynamic> json) {
return ServiceBoyResponse(
id: json['_id'],
shopId: json['shopId'],
serviceBoy: (json['serviceBoy'] as List)
.map((e) => ServiceBoy.fromJson(e))
.toList(),
createdAt: json['createdAt'],
updatedAt: json['updatedAt'],
);
}
Map<String, dynamic> toJson() {
return {
'_id': id,
'shopId': shopId,
'serviceBoy': serviceBoy.map((e) => e.toJson()).toList(),
'createdAt': createdAt,
'updatedAt': updatedAt,
};
}
}

View File

@@ -1,6 +1,27 @@
class ServiceBoy {
final String id;
final String name;
final String phone;
ServiceBoy({required this.name, required this.phone});
ServiceBoy({
required this.id,
required this.name,
required this.phone,
});
factory ServiceBoy.fromJson(Map<String, dynamic> json) {
return ServiceBoy(
id: json['_id'],
name: json['name'],
phone: json['phone'],
);
}
Map<String, dynamic> toJson() {
return {
'_id': id,
'name': name,
'phone': phone,
};
}
}

View File

@@ -2,52 +2,74 @@ import 'package:hive/hive.dart';
part 'shop_model.g.dart';
@HiveType(typeId: 1)
@HiveType(typeId: 0)
class ShopModel {
@HiveField(0)
final String id;
bool success;
@HiveField(1)
final String image;
ShopDetails user;
@HiveField(2)
final String mobile;
String token;
@HiveField(3)
final String email;
@HiveField(4)
final String shopName;
@HiveField(5)
final String address; // ✅ NEW
String message;
ShopModel({
required this.id,
required this.image,
required this.mobile,
required this.email,
required this.shopName,
required this.address,
required this.success,
required this.user,
required this.token,
required this.message,
});
factory ShopModel.fromJson(Map<String, dynamic> json) {
return ShopModel(
id: json['id'] ?? '',
image: json['image'] ?? '',
mobile: json['mobile'] ?? '',
email: json['email'] ?? '',
shopName: json['shopName'] ?? '',
address: json['address'] ?? '', // ✅ NEW
factory ShopModel.fromJson(Map<String, dynamic> json) => ShopModel(
success: json['success'],
user: ShopDetails.fromJson(json['user']),
token: json['token'],
message: json['message'],
);
Map<String, dynamic> toJson() => {
'success': success,
'user': user.toJson(),
'token': token,
'message': message,
};
}
@HiveType(typeId: 1)
class ShopDetails {
@HiveField(0)
String id;
@HiveField(1)
String name;
@HiveField(2)
String email;
@HiveField(3)
String role;
ShopDetails({
required this.id,
required this.name,
required this.email,
required this.role,
});
factory ShopDetails.fromJson(Map<String, dynamic> json) => ShopDetails(
id: json['id'],
name: json['name'],
email: json['email'],
role: json['role'],
);
Map<String, dynamic> toJson() => {
'id': id,
'image': image,
'mobile': mobile,
'name': name,
'email': email,
'shopName': shopName,
'address': address, // ✅ NEW
'role': role,
};
}

View File

@@ -8,7 +8,7 @@ part of 'shop_model.dart';
class ShopModelAdapter extends TypeAdapter<ShopModel> {
@override
final int typeId = 1;
final int typeId = 0;
@override
ShopModel read(BinaryReader reader) {
@@ -17,31 +17,25 @@ class ShopModelAdapter extends TypeAdapter<ShopModel> {
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return ShopModel(
id: fields[0] as String,
image: fields[1] as String,
mobile: fields[2] as String,
email: fields[3] as String,
shopName: fields[4] as String,
address: fields[5] as String,
success: fields[0] as bool,
user: fields[1] as ShopDetails,
token: fields[2] as String,
message: fields[3] as String,
);
}
@override
void write(BinaryWriter writer, ShopModel obj) {
writer
..writeByte(6)
..writeByte(0)
..write(obj.id)
..writeByte(1)
..write(obj.image)
..writeByte(2)
..write(obj.mobile)
..writeByte(3)
..write(obj.email)
..writeByte(4)
..write(obj.shopName)
..writeByte(5)
..write(obj.address);
..writeByte(0)
..write(obj.success)
..writeByte(1)
..write(obj.user)
..writeByte(2)
..write(obj.token)
..writeByte(3)
..write(obj.message);
}
@override
@@ -54,3 +48,46 @@ class ShopModelAdapter extends TypeAdapter<ShopModel> {
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
class ShopDetailsAdapter extends TypeAdapter<ShopDetails> {
@override
final int typeId = 1;
@override
ShopDetails read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return ShopDetails(
id: fields[0] as String,
name: fields[1] as String,
email: fields[2] as String,
role: fields[3] as String,
);
}
@override
void write(BinaryWriter writer, ShopDetails obj) {
writer
..writeByte(4)
..writeByte(0)
..write(obj.id)
..writeByte(1)
..write(obj.name)
..writeByte(2)
..write(obj.email)
..writeByte(3)
..write(obj.role);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is ShopDetailsAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View File

@@ -0,0 +1,67 @@
class ShopProfileModel {
final String id;
final String name;
final String description;
final String address;
final double latitude;
final double longitude;
final String phone;
final String email;
final String password;
final String role;
final List<String> images;
final int rating;
final DateTime timestamp;
ShopProfileModel({
required this.id,
required this.name,
required this.description,
required this.address,
required this.latitude,
required this.longitude,
required this.phone,
required this.email,
required this.password,
required this.role,
required this.images,
required this.rating,
required this.timestamp,
});
factory ShopProfileModel.fromJson(Map<String, dynamic> json) {
return ShopProfileModel(
id: json['_id'],
name: json['name'],
description: json['description'],
address: json['address'],
latitude: json['latitude'].toDouble(),
longitude: json['longitude'].toDouble(),
phone: json['phone'],
email: json['email'],
password: json['password'],
role: json['role'],
images: List<String>.from(json['images']),
rating: json['rating'],
timestamp: DateTime.parse(json['timestamp']),
);
}
Map<String, dynamic> toJson() {
return {
'_id': id,
'name': name,
'description': description,
'address': address,
'latitude': latitude,
'longitude': longitude,
'phone': phone,
'email': email,
'password': password,
'role': role,
'images': images,
'rating': rating,
'timestamp': timestamp.toIso8601String(),
};
}
}

View File

@@ -1,41 +1,103 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:glowwheels/constants/api.dart';
import 'package:glowwheels/models/order_model.dart';
import 'package:http/http.dart' as http;
import '../models/order_model.dart';
import '../models/serviceboy_model.dart';
class OrderProvider with ChangeNotifier {
final 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 sample orders if needed
];
class OrdersProvider with ChangeNotifier {
List<Order> _orders = [];
bool _isLoading = false;
bool _isRefreshing = false;
String? _errorMessage;
List<Order> get orders => _orders;
bool get isLoading => _isLoading;
bool get isRefreshing => _isRefreshing;
String? get errorMessage => _errorMessage;
void assignServiceBoy(int index, ServiceBoy boy) {
_orders[index].assignedBoy = boy;
Future<void> fetchOrders(String shopId, {bool refresh = false}) async {
final ordersUri = Uri.parse(ApiConstants.ordersByShop(shopId));
if (refresh) {
_isRefreshing = true;
} else {
_isLoading = true;
}
notifyListeners();
try {
final response = await http.get(ordersUri);
if (response.statusCode == 200) {
final data = json.decode(response.body);
if (data['success'] == true) {
final List<dynamic> ordersJson = data['orders'];
_orders = ordersJson.map((orderJson) => Order.fromJson(orderJson)).toList();
_errorMessage = null;
} else {
_errorMessage = 'Failed to fetch orders';
}
} else {
_errorMessage = 'Server error: ${response.statusCode}';
}
} catch (e) {
_errorMessage = 'An error occurred: $e';
}
_isLoading = false;
_isRefreshing = false;
notifyListeners();
}
Future<void> assignServiceBoyToOrder({
required String orderId,
required String serviceBoyId,
required String shopId,
}) async {
final assignUri = Uri.parse(ApiConstants.assignServiceBoy(orderId));
try {
final response = await http.put(
assignUri,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'serviceBoyId': serviceBoyId}),
);
if (response.statusCode == 200) {
print('Successfully assigned service boy');
} else {
print('Failed to assign service boy: ${response.statusCode}');
}
} catch (e) {
print('Error assigning service boy: $e');
}
}
Future<void> updateOrderStatus({
required String orderId,
required String status,
}) async {
final updateStatusUri = Uri.parse(ApiConstants.updateOrderStatusByAdmin(orderId));
try {
final response = await http.put(
updateStatusUri,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'status': status}),
);
if (response.statusCode == 200) {
final index = _orders.indexWhere((o) => o.id == orderId);
if (index != -1) {
_orders[index] = _orders[index].copyWith(status: status);
notifyListeners();
}
} else {
print('Failed to update status: ${response.statusCode}');
}
} catch (e) {
print('Error updating order status: $e');
}
}
}

View File

@@ -1,41 +1,152 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import '../models/serviceboy_model.dart';
class ServiceBoyProvider extends ChangeNotifier {
List<ServiceBoy> _serviceBoys = [
ServiceBoy(name: 'John Doe', phone: '9875643210'),
ServiceBoy(name: 'Amit Raj', phone: '9765432180'),
ServiceBoy(name: 'Manoj Sinha', phone: '9543219876'),
];
import 'package:glowwheels/constants/api.dart';
import 'package:http/http.dart' as http;
import 'package:glowwheels/models/serviceboy_model.dart';
class ServiceBoyProvider with ChangeNotifier {
List<ServiceBoy> _serviceBoys = [];
ServiceBoy? _selectedBoy;
bool _isLoading = false;
List<ServiceBoy> get serviceBoys => _serviceBoys;
ServiceBoy? get selectedBoy => _selectedBoy;
bool get isLoading => _isLoading;
// Add a new service boy
void addServiceBoy(ServiceBoy boy) {
_serviceBoys.add(boy);
/// Fetch service boys by shop ID
Future<void> fetchServiceBoys(String shopId) async {
_isLoading = true;
notifyListeners();
final serviceBoyListUri = Uri.parse(ApiConstants.serviceBoysByShop(shopId));
try {
final response = await http.get(serviceBoyListUri);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
final List<dynamic> boysJson = data['data']['serviceBoy'];
_serviceBoys = boysJson.map((json) => ServiceBoy.fromJson(json)).toList();
print('fetched service boys');
print (_serviceBoys);
notifyListeners();
} else {
print('Failed to fetch service boys: ${response.statusCode}');
}
} catch (e) {
print('Error fetching service boys: $e');
}
_isLoading = false;
notifyListeners();
}
// Edit an existing service boy
void editServiceBoy(int index, ServiceBoy updatedBoy) {
if (index >= 0 && index < _serviceBoys.length) {
_serviceBoys[index] = updatedBoy;
notifyListeners();
/// Fetch a single service boy by ID
Future<ServiceBoy?> fetchServiceBoyById(String serviceBoyId) async {
final serviceBoyUri = Uri.parse(ApiConstants.serviceBoyDetails(serviceBoyId));
try {
final response = await http.get(serviceBoyUri);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
final boyJson = data['data']; // Adjust if API wraps in `data`
final serviceBoy = ServiceBoy.fromJson(boyJson);
return serviceBoy;
} else {
print('Failed to fetch service boy: ${response.statusCode}');
}
} catch (e) {
print('Error fetching service boy: $e');
}
return null;
}
/// Add new service boy to shop
Future<void> addServiceBoy(String shopId, String name,String phone) async {
print('add api called');
print(name+'Phone '+phone.toString());
final addServiceBoyUri = Uri.parse(ApiConstants.addServiceBoy);
final body = jsonEncode({
'shopId': shopId,
'serviceBoy': [
{
'name': name,
'phone':phone,
}
]
});
try {
final response = await http.post(
addServiceBoyUri,
headers: {'Content-Type': 'application/json'},
body: body,
);
print('add service boy response '+response.statusCode.toString());
if (response.statusCode == 200) {
await fetchServiceBoys(shopId); // refresh list
} else {
print('Failed to add service boy: ${response.statusCode}');
}
} catch (e) {
print('Error adding service boy: $e');
}
}
// Delete a service boy
void deleteServiceBoy(int index) {
if (index >= 0 && index < _serviceBoys.length) {
_serviceBoys.removeAt(index);
notifyListeners();
/// Edit service boy phone
Future<void> editServiceBoy(
String serviceBoyId,
String updatedName,
String updatedPhone,
String shopId,
) async {
final serviceBoyUri = Uri.parse(ApiConstants.serviceBoyDetails(serviceBoyId));
// Build request body only with non-empty fields
final Map<String, dynamic> data = {};
if (updatedName.isNotEmpty) data['name'] = updatedName;
if (updatedPhone.isNotEmpty) data['phone'] = updatedPhone;
if (data.isEmpty) {
print('No fields to update.');
return;
}
try {
final response = await http.put(
serviceBoyUri,
headers: {'Content-Type': 'application/json'},
body: jsonEncode(data),
);
if (response.statusCode == 200) {
print('Service boy updated successfully');
await fetchServiceBoys(shopId); // Refresh list
} else {
print('Failed to update service boy: ${response.statusCode}');
print('Response body: ${response.body}');
}
} catch (e) {
print('Error updating service boy: $e');
}
}
// Assign a selected service boy (for dialogs)
/// Delete a service boy
Future<void> deleteServiceBoy(String serviceBoyId, String shopId) async {
final serviceBoyUri = Uri.parse(ApiConstants.serviceBoyDetails(serviceBoyId));
try {
final response = await http.delete(serviceBoyUri);
if (response.statusCode == 200) {
await fetchServiceBoys(shopId); // refresh list
} else {
print('Failed to delete service boy: ${response.statusCode}');
}
} catch (e) {
print('Error deleting service boy: $e');
}
}
// Select a service boy
void selectBoy(ServiceBoy boy) {
_selectedBoy = boy;
notifyListeners();

View File

@@ -0,0 +1,40 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:glowwheels/constants/api.dart';
import 'package:glowwheels/models/shop_profile_model.dart';
import 'package:http/http.dart' as http;
class ShopProfileProvider with ChangeNotifier {
ShopProfileModel? _shopProfile;
bool _isLoading = false;
String? _errorMessage;
ShopProfileModel? get shopProfile => _shopProfile;
bool get isLoading => _isLoading;
String? get errorMessage => _errorMessage;
Future<void> fetchShopProfile(String shopId) async {
_isLoading = true;
_errorMessage = null;
notifyListeners();
final shopUri = Uri.parse(ApiConstants.shopDetails(shopId));
try {
final response = await http.get(shopUri);
if (response.statusCode == 200) {
final data = json.decode(response.body);
_shopProfile = ShopProfileModel.fromJson(data);
} else {
_errorMessage = 'Failed to load shop profile. Status code: ${response.statusCode}';
}
} catch (e) {
_errorMessage = 'An error occurred: $e';
} finally {
_isLoading = false;
notifyListeners();
}
}
}

View File

@@ -1,52 +1,98 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:glowwheels/constants/api.dart';
import 'package:hive/hive.dart';
import 'package:http/http.dart' as http;
import 'package:provider/provider.dart';
import '../models/shop_model.dart';
class ShopProvider with ChangeNotifier {
ShopModel? _shop;
ShopModel? get shop => _shop;
String? _token;
final String _boxName = 'shopBox';
ShopModel? get shop => _shop;
String? get token => _token;
final String _shopBox = 'shopBox';
final String _tokenBox = 'tokenBox';
ShopProvider() {
_loadOrCreateDummyShop();
_loadShopAndTokenFromHive();
}
void _loadOrCreateDummyShop() async {
final box = await Hive.openBox<ShopModel>(_boxName);
if (box.isNotEmpty) {
_shop = box.getAt(0);
} else {
// Dummy data
_shop = ShopModel(
id: '1',
shopName: "Omkara Car Wash Center",
email: "omkara@gmail.com",
mobile: "8617019854",
image: "assets/images/shop_image.jpg",
address: "Bidhannagar, Kolkata, pin-700017",
void _loadShopAndTokenFromHive() async {
final shopBox = await Hive.openBox<ShopModel>(_shopBox);
_shop = shopBox.get('shop');
final tokenBox = await Hive.openBox<String>(_tokenBox);
_token = tokenBox.get('token');
notifyListeners();
}
String? getShopId(BuildContext context) {
return Provider.of<ShopProvider>(context, listen: false).shop?.user.id;
}
Future<bool> login(String email, String password) async {
final loginUri = Uri.parse(ApiConstants.loginUrl);
try {
final response = await http.post(
loginUri,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'email': email, 'password': password}),
);
await box.add(_shop!);
}
print('Response code: ${response.statusCode}');
print('Response body: ${response.body}');
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
final shopModel = ShopModel.fromJson(data);
final shopBox = await Hive.openBox<ShopModel>(_shopBox);
final tokenBox = await Hive.openBox<String>(_tokenBox);
await shopBox.put('shop', shopModel);
await tokenBox.put('token', shopModel.token);
_shop = shopModel;
_token = shopModel.token;
notifyListeners();
return true;
} else {
print('HTTP Error: ${response.statusCode}');
return false;
}
} catch (e) {
print('Login Exception: $e');
return false;
}
}
Future<void> setShop(ShopModel shop) async {
_shop = shop;
notifyListeners();
final box = await Hive.openBox<ShopModel>(_boxName);
await box.clear(); // Keep only one shop
await box.add(shop);
}
Future<void> logout() async {
_shop = null;
_token = null;
notifyListeners();
final box = await Hive.openBox<ShopModel>(_boxName);
await box.clear();
final shopBox = await Hive.openBox<ShopModel>(_shopBox);
await shopBox.clear();
final tokenBox = await Hive.openBox<String>(_tokenBox);
await tokenBox.clear();
}
void setShop(ShopModel shop) {
_shop = shop;
notifyListeners();
}
void setToken(String token) {
_token = token;
notifyListeners();
}
}

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,
),
@@ -27,53 +47,63 @@ class ServiceBoyScreen extends StatelessWidget {
Text(
'GLOWWHEELS',
style: GoogleFonts.inter(
fontSize: 32,color: Color.fromRGBO(25, 25, 112, 0.87)
,fontWeight: FontWeight.w700
)
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
)
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
)
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
)
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
)
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,11 +178,14 @@ class ServiceBoyScreen extends StatelessWidget {
}
void _showDeleteDialog(BuildContext context, String id) {
final shopId = getShopId(context);
showModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
isScrollControlled: true,
builder: (_) => Container(
builder: (BuildContext bottomSheetContext) {
return Container(
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white,
@@ -184,9 +224,14 @@ class ServiceBoyScreen extends StatelessWidget {
borderRadius: BorderRadius.circular(8),
),
),
onPressed: () {
// Provider.of<ServiceBoyProvider>(context, listen: false).deleteServiceBoy(id);
Navigator.pop(context);
onPressed: () async {
if (shopId != null) {
await Provider.of<ServiceBoyProvider>(
bottomSheetContext,
listen: false,
).deleteServiceBoy(id, shopId);
}
Navigator.pop(bottomSheetContext); // Correct context
},
child: Text(
'Confirm',
@@ -197,7 +242,8 @@ class ServiceBoyScreen extends StatelessWidget {
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),
@@ -76,7 +103,14 @@ class AccountScreen extends StatelessWidget {
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,
@@ -21,11 +38,11 @@ class AddServiceBoyScreen extends StatelessWidget {
title: Text(
'Add Service boy',
style: GoogleFonts.nunito(
fontSize: 18,color: Color.fromRGBO(26, 26, 26, 1)
,fontWeight: FontWeight.w500
)
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
fontSize: 14,
color: Color.fromRGBO(26, 26, 26, 1),
fontWeight: FontWeight.w400,
),
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 14),
@@ -55,8 +72,9 @@ class AddServiceBoyScreen extends StatelessWidget {
decoration: InputDecoration(
hintText: 'Mobile Number',
hintStyle: GoogleFonts.nunito(
fontSize: 14,color: Color.fromRGBO(26, 26, 26, 1)
,fontWeight: FontWeight.w400
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,
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,23 +1,67 @@
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,
@@ -28,11 +72,10 @@ class EditServiceBoyScreen extends StatelessWidget {
title: Text(
'Edit Service boy Details',
style: GoogleFonts.nunito(
fontSize: 18,color: Color.fromRGBO(26, 26, 26, 1)
,fontWeight: FontWeight.w500
)
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(
hintText: 'Name',
hintStyle: GoogleFonts.nunito(
fontSize: 14,color: Color.fromRGBO(26, 26, 26, 1)
,fontWeight: FontWeight.w400
),
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(
hintText: 'Mobile Number',
hintStyle: GoogleFonts.nunito(
fontSize: 14,color: Color.fromRGBO(26, 26, 26, 1)
,fontWeight: FontWeight.w400
),
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,18 +1,55 @@
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(
@@ -21,32 +58,32 @@ class LoginScreen extends StatelessWidget {
colors: [
Color.fromRGBO(80, 166, 242, 0.66),
Color.fromRGBO(168, 134, 255, 0.66),
Color.fromRGBO(86, 88, 255, 0.3828),
Color.fromRGBO(86, 88, 255, 0.38),
Color.fromRGBO(214, 246, 255, 0.66),
],
),
borderRadius: BorderRadius.only(bottomRight: Radius.elliptical(300, 110), )
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",
@@ -54,7 +91,7 @@ class LoginScreen extends StatelessWidget {
fontSize: 24,
fontWeight: FontWeight.w700,
height: 0.9,
color: Color.fromRGBO(25, 25, 112, 0.87)
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: Icon(Icons.remove_red_eye_outlined),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword ? Icons.visibility_off : Icons.visibility,
),
onPressed: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
),
),
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,
),
),
),
)

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(
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(
return RefreshIndicator(
onRefresh: _refreshOrders,
child: ListView.builder(
itemCount: orders.length,
itemBuilder: (context, index) {
return OrderCard(order: orders[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});
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()),
);
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;
}
}
@override
Widget build(BuildContext context) {
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()),
);
}
}

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,20 +93,103 @@ class _OrderCardState extends State<OrderCard> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
widget.order.status,
GestureDetector(
onTap: () async {
String? newStatus;
String? dialogMessage;
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);
}
}
}
},
child: Text(
orderStatus,
style: TextStyle(
color: widget.order.status == "Confirmed" ? Colors.green : Colors.orange,
color:orderStatus == "confirmed" || orderStatus == "completed"
? Colors.green
: orderStatus == "pending"
? Colors.orange
: Colors.red,
fontWeight: FontWeight.bold,
fontSize: 16,
// Visual cue for clickable
),
),
assignedBoy == null
? ElevatedButton(
onPressed: () async {
),
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: serviceBoys),
builder: (_) => AssignServiceBoyDialog(
serviceBoys: serviceBoyProvider.serviceBoys,
orderId: widget.order.id,
),
);
if (selected != null) {
@@ -89,25 +197,106 @@ class _OrderCardState extends State<OrderCard> {
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("Assign",style: GoogleFonts.inter(
fontSize: 12,color: Colors.white
,fontWeight: FontWeight.w500
),),
child: Text(
"Cancel",
style: GoogleFonts.inter(
fontSize: 12, color: Colors.white, fontWeight: FontWeight.w500),
),
)
: Column(
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),
],
),
),
],
),
),
);
}
}

View File

@@ -552,6 +552,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.0"
shimmer:
dependency: "direct main"
description:
name: shimmer
sha256: "1f1009b5845a1f88f1c5630212279540486f97409e9fc3f63883e71070d107bf"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
sky_engine:
dependency: transitive
description: flutter

View File

@@ -40,6 +40,7 @@ dependencies:
hive: ^2.2.3
hive_flutter: ^1.1.0
path_provider: ^2.1.2
shimmer: ^2.0.0
dev_dependencies:
flutter_test: