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 { plugins {
id("com.android.application") id("com.android.application")
id("kotlin-android") id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin") 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 { android {
namespace = "com.example.glowwheels" namespace = "com.example.glowwheels"
compileSdk = flutter.compileSdkVersion compileSdk = 34
ndkVersion = flutter.ndkVersion ndkVersion = "25.1.8937393" // Optional, or remove if not used
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_11 sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11
isCoreLibraryDesugaringEnabled = true
} }
kotlinOptions { kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString() jvmTarget = "11"
} }
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.glowwheels" applicationId = "com.example.glowwheels"
// You can update the following values to match your application needs. minSdk = 21
// For more information, see: https://flutter.dev/to/review-gradle-config. targetSdk = 34
minSdk = flutter.minSdkVersion versionCode = 1
targetSdk = flutter.targetSdkVersion versionName = "1.0.0"
versionCode = flutter.versionCode }
versionName = flutter.versionName
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 { buildTypes {
release { release {
// TODO: Add your own signing config for the release build. signingConfig = signingConfigs.getByName("release")
// Signing with the debug keys for now, so `flutter run --release` works. isMinifyEnabled = false
signingConfig = signingConfigs.getByName("debug") isShrinkResources = false
} }
} }
} }
@@ -42,3 +57,7 @@ android {
flutter { flutter {
source = "../.." 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"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<application <application
android:label="glowwheels" android:label="Glowwheels Vendor"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<activity <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/models/shop_model.dart';
import 'package:glowwheels/provider/order_provider.dart'; import 'package:glowwheels/provider/order_provider.dart';
import 'package:glowwheels/provider/serviceboy_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/provider/shop_provider.dart';
import 'package:glowwheels/screens/login_screen.dart'; import 'package:glowwheels/screens/splash_screen.dart';
import 'package:glowwheels/screens/main_screen.dart';
import 'package:glowwheels/screens/order_screen.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:hive_flutter/adapters.dart'; import 'package:hive_flutter/adapters.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -17,13 +17,20 @@ void main() async {
await Hive.initFlutter(); await Hive.initFlutter();
Hive.registerAdapter(ShopModelAdapter()); Hive.registerAdapter(ShopModelAdapter());
Hive.registerAdapter(ShopDetailsAdapter());
if (!Hive.isBoxOpen('shopbox')) {
await Hive.openBox<ShopModel>('shopbox');
}
runApp( runApp(
MultiProvider( MultiProvider(
providers: [ providers: [
ChangeNotifierProvider(create: (_) => OrderProvider()), ChangeNotifierProvider(create: (_) => OrdersProvider()),
ChangeNotifierProvider(create: (_) => ServiceBoyProvider()), ChangeNotifierProvider(create: (_) => ServiceBoyProvider()),
ChangeNotifierProvider(create: (_) => ShopProvider()), ChangeNotifierProvider(create: (_) => ShopProvider()),
ChangeNotifierProvider(create: (_) => ShopProfileProvider()),
], ],
child: GlowWheelsApp(), child: GlowWheelsApp(),
), ),
@@ -35,7 +42,7 @@ class GlowWheelsApp extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
home: LoginScreen(), home: SplashDecider(),
); );
} }
} }

View File

@@ -1,31 +1,229 @@
import 'package:glowwheels/models/serviceboy_model.dart'; import 'package:glowwheels/models/serviceboy_model.dart';
class Order { class Order {
final String customerName; final String id;
final String mobileNumber; final User user;
final String serviceType; final Shop shop;
final String service; final Service service;
final String price; final String? address;
final String time;
final String date; final String date;
final String carName; final String timeSlot;
final String status; final String status;
final String imagePath; final String? serviceBoyId;
ServiceBoy? assignedBoy; final DateTime createdAt;
final DateTime updatedAt;
final ServiceBoy? assignedServiceBoy;
Order({ Order({
required this.customerName, required this.id,
required this.mobileNumber, required this.user,
required this.serviceType, required this.shop,
required this.service, required this.service,
required this.price, required this.address,
required this.time,
required this.date, required this.date,
required this.carName, required this.timeSlot,
required this.status, required this.status,
required this.imagePath, required this.serviceBoyId,
this.assignedBoy, 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 { class ServiceBoy {
final String id;
final String name; final String name;
final String phone; 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'; part 'shop_model.g.dart';
@HiveType(typeId: 1) @HiveType(typeId: 0)
class ShopModel { class ShopModel {
@HiveField(0) @HiveField(0)
final String id; bool success;
@HiveField(1) @HiveField(1)
final String image; ShopDetails user;
@HiveField(2) @HiveField(2)
final String mobile; String token;
@HiveField(3) @HiveField(3)
final String email; String message;
@HiveField(4)
final String shopName;
@HiveField(5)
final String address; // ✅ NEW
ShopModel({ ShopModel({
required this.id, required this.success,
required this.image, required this.user,
required this.mobile, required this.token,
required this.email, required this.message,
required this.shopName,
required this.address,
}); });
factory ShopModel.fromJson(Map<String, dynamic> json) { factory ShopModel.fromJson(Map<String, dynamic> json) => ShopModel(
return ShopModel( success: json['success'],
id: json['id'] ?? '', user: ShopDetails.fromJson(json['user']),
image: json['image'] ?? '', token: json['token'],
mobile: json['mobile'] ?? '', message: json['message'],
email: json['email'] ?? '',
shopName: json['shopName'] ?? '',
address: json['address'] ?? '', // ✅ NEW
); );
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() => { Map<String, dynamic> toJson() => {
'id': id, 'id': id,
'image': image, 'name': name,
'mobile': mobile,
'email': email, 'email': email,
'shopName': shopName, 'role': role,
'address': address, // ✅ NEW
}; };
} }

View File

@@ -8,7 +8,7 @@ part of 'shop_model.dart';
class ShopModelAdapter extends TypeAdapter<ShopModel> { class ShopModelAdapter extends TypeAdapter<ShopModel> {
@override @override
final int typeId = 1; final int typeId = 0;
@override @override
ShopModel read(BinaryReader reader) { ShopModel read(BinaryReader reader) {
@@ -17,31 +17,25 @@ class ShopModelAdapter extends TypeAdapter<ShopModel> {
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
}; };
return ShopModel( return ShopModel(
id: fields[0] as String, success: fields[0] as bool,
image: fields[1] as String, user: fields[1] as ShopDetails,
mobile: fields[2] as String, token: fields[2] as String,
email: fields[3] as String, message: fields[3] as String,
shopName: fields[4] as String,
address: fields[5] as String,
); );
} }
@override @override
void write(BinaryWriter writer, ShopModel obj) { void write(BinaryWriter writer, ShopModel obj) {
writer 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) ..writeByte(4)
..write(obj.shopName) ..writeByte(0)
..writeByte(5) ..write(obj.success)
..write(obj.address); ..writeByte(1)
..write(obj.user)
..writeByte(2)
..write(obj.token)
..writeByte(3)
..write(obj.message);
} }
@override @override
@@ -54,3 +48,46 @@ class ShopModelAdapter extends TypeAdapter<ShopModel> {
runtimeType == other.runtimeType && runtimeType == other.runtimeType &&
typeId == other.typeId; 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: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'; class OrdersProvider with ChangeNotifier {
import '../models/serviceboy_model.dart'; List<Order> _orders = [];
bool _isLoading = false;
class OrderProvider with ChangeNotifier { bool _isRefreshing = false;
final List<Order> _orders = [ String? _errorMessage;
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
];
List<Order> get orders => _orders; List<Order> get orders => _orders;
bool get isLoading => _isLoading;
bool get isRefreshing => _isRefreshing;
String? get errorMessage => _errorMessage;
void assignServiceBoy(int index, ServiceBoy boy) { Future<void> fetchOrders(String shopId, {bool refresh = false}) async {
_orders[index].assignedBoy = boy; 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(); 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 'package:flutter/material.dart';
import '../models/serviceboy_model.dart'; import 'package:glowwheels/constants/api.dart';
import 'package:http/http.dart' as http;
class ServiceBoyProvider extends ChangeNotifier { import 'package:glowwheels/models/serviceboy_model.dart';
List<ServiceBoy> _serviceBoys = [
ServiceBoy(name: 'John Doe', phone: '9875643210'),
ServiceBoy(name: 'Amit Raj', phone: '9765432180'),
ServiceBoy(name: 'Manoj Sinha', phone: '9543219876'),
];
class ServiceBoyProvider with ChangeNotifier {
List<ServiceBoy> _serviceBoys = [];
ServiceBoy? _selectedBoy; ServiceBoy? _selectedBoy;
bool _isLoading = false;
List<ServiceBoy> get serviceBoys => _serviceBoys; List<ServiceBoy> get serviceBoys => _serviceBoys;
ServiceBoy? get selectedBoy => _selectedBoy; ServiceBoy? get selectedBoy => _selectedBoy;
bool get isLoading => _isLoading;
// Add a new service boy /// Fetch service boys by shop ID
void addServiceBoy(ServiceBoy boy) { Future<void> fetchServiceBoys(String shopId) async {
_serviceBoys.add(boy); _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(); notifyListeners();
} }
// Edit an existing service boy /// Fetch a single service boy by ID
void editServiceBoy(int index, ServiceBoy updatedBoy) { Future<ServiceBoy?> fetchServiceBoyById(String serviceBoyId) async {
if (index >= 0 && index < _serviceBoys.length) { final serviceBoyUri = Uri.parse(ApiConstants.serviceBoyDetails(serviceBoyId));
_serviceBoys[index] = updatedBoy;
notifyListeners(); 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 /// Edit service boy phone
void deleteServiceBoy(int index) { Future<void> editServiceBoy(
if (index >= 0 && index < _serviceBoys.length) { String serviceBoyId,
_serviceBoys.removeAt(index); String updatedName,
notifyListeners(); 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) { void selectBoy(ServiceBoy boy) {
_selectedBoy = boy; _selectedBoy = boy;
notifyListeners(); 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:flutter/material.dart';
import 'package:glowwheels/constants/api.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:http/http.dart' as http;
import 'package:provider/provider.dart';
import '../models/shop_model.dart'; import '../models/shop_model.dart';
class ShopProvider with ChangeNotifier { class ShopProvider with ChangeNotifier {
ShopModel? _shop; 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() { ShopProvider() {
_loadOrCreateDummyShop(); _loadShopAndTokenFromHive();
} }
void _loadOrCreateDummyShop() async {
final box = await Hive.openBox<ShopModel>(_boxName);
if (box.isNotEmpty) { void _loadShopAndTokenFromHive() async {
_shop = box.getAt(0); final shopBox = await Hive.openBox<ShopModel>(_shopBox);
} else { _shop = shopBox.get('shop');
// Dummy data
_shop = ShopModel( final tokenBox = await Hive.openBox<String>(_tokenBox);
id: '1', _token = tokenBox.get('token');
shopName: "Omkara Car Wash Center",
email: "omkara@gmail.com", notifyListeners();
mobile: "8617019854", }
image: "assets/images/shop_image.jpg", String? getShopId(BuildContext context) {
address: "Bidhannagar, Kolkata, pin-700017", 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(); 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 { Future<void> logout() async {
_shop = null; _shop = null;
_token = null;
notifyListeners(); notifyListeners();
final box = await Hive.openBox<ShopModel>(_boxName); final shopBox = await Hive.openBox<ShopModel>(_shopBox);
await box.clear(); 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:flutter/material.dart';
import 'package:glowwheels/helpers/shopid_helper.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../provider/serviceboy_provider.dart'; import '../provider/serviceboy_provider.dart';
import 'add_serviceboy_screen.dart'; import 'add_serviceboy_screen.dart';
import 'edit_serviceboy_screen.dart'; import 'edit_serviceboy_screen.dart';
import 'package:google_fonts/google_fonts.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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final provider = Provider.of<ServiceBoyProvider>(context); final provider = Provider.of<ServiceBoyProvider>(context);
@@ -13,7 +34,6 @@ class ServiceBoyScreen extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [Color.fromRGBO(208, 235, 255, 1), Colors.white], colors: [Color.fromRGBO(208, 235, 255, 1), Colors.white],
begin: Alignment.topCenter, begin: Alignment.topCenter,
end: Alignment.bottomCenter, end: Alignment.bottomCenter,
), ),
@@ -27,53 +47,63 @@ class ServiceBoyScreen extends StatelessWidget {
Text( Text(
'GLOWWHEELS', 'GLOWWHEELS',
style: GoogleFonts.inter( style: GoogleFonts.inter(
fontSize: 32,color: Color.fromRGBO(25, 25, 112, 0.87) fontSize: 32,
,fontWeight: FontWeight.w700 color: Color.fromRGBO(25, 25, 112, 0.87),
) fontWeight: FontWeight.w700,
),
), ),
Text( Text(
'Service Center', 'Service Center',
style: GoogleFonts.inter( style: GoogleFonts.inter(
fontSize: 24,color: Color.fromRGBO(25, 25, 112, 0.87) fontSize: 24,
,fontWeight: FontWeight.w400 color: Color.fromRGBO(25, 25, 112, 0.87),
) fontWeight: FontWeight.w400,
),
), ),
SizedBox(height: 20), SizedBox(height: 20),
Text( Text(
'Service Boy List', 'Service Boy List',
style: GoogleFonts.inter( style: GoogleFonts.inter(
fontSize: 16,color: Color.fromRGBO(33, 33, 33, 1) fontSize: 16,
,fontWeight: FontWeight.w500 color: Color.fromRGBO(33, 33, 33, 1),
) fontWeight: FontWeight.w500,
),
), ),
SizedBox(height: 10), SizedBox(height: 10),
Expanded( Expanded(
child: provider.serviceBoys.isEmpty child: provider.isLoading
? Center(child: CircularProgressIndicator())
: provider.serviceBoys.isEmpty
? Center(child: Text("No service boys found")) ? Center(child: Text("No service boys found"))
: ListView.builder( : ListView.builder(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
itemCount: provider.serviceBoys.length, itemCount: provider.serviceBoys.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final boy = provider.serviceBoys[index]; final boy = provider.serviceBoys[index];
return Card( return Card(
color: Colors.white, color: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
elevation: 2, elevation: 2,
child: ListTile( child: ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), contentPadding: EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
title: Text( title: Text(
boy.name, boy.name,
style: GoogleFonts.inter( style: GoogleFonts.inter(
fontSize: 18,color: Color.fromRGBO(33, 33, 33, 0.78) fontSize: 18,
,fontWeight: FontWeight.w600 color: Color.fromRGBO(33, 33, 33, 0.78),
) fontWeight: FontWeight.w600,
),
), ),
subtitle: Text( subtitle: Text(
boy.phone, boy.phone,
style: GoogleFonts.inter( style: GoogleFonts.inter(
fontSize: 16,color: Color.fromRGBO(33, 33, 33, 0.78) fontSize: 16,
,fontWeight: FontWeight.w400 color: Color.fromRGBO(33, 33, 33, 0.78),
) fontWeight: FontWeight.w400,
),
), ),
trailing: Row( trailing: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -83,7 +113,9 @@ class ServiceBoyScreen extends StatelessWidget {
onPressed: () { onPressed: () {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (_) => EditServiceBoyScreen(serviceBoy: boy), builder: (_) =>
EditServiceBoyScreen(
serviceBoy: boy),
), ),
); );
}, },
@@ -92,7 +124,7 @@ class ServiceBoyScreen extends StatelessWidget {
_buildIconButton( _buildIconButton(
icon: Icons.delete, icon: Icons.delete,
onPressed: () { onPressed: () {
_showDeleteDialog(context, ''); _showDeleteDialog(context, boy.id);
}, },
), ),
], ],
@@ -109,7 +141,11 @@ class ServiceBoyScreen extends StatelessWidget {
shape: CircleBorder(), shape: CircleBorder(),
backgroundColor: Colors.white, backgroundColor: Colors.white,
elevation: 4, elevation: 4,
child: Icon(Icons.add, color: Color(0xFF1F1762),size: 25,), child: Icon(
Icons.add,
color: Color(0xFF1F1762),
size: 25,
),
onPressed: () { onPressed: () {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute(builder: (_) => AddServiceBoyScreen()), 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( return Container(
height: 40, height: 40,
width: 40, width: 40,
@@ -141,11 +178,14 @@ class ServiceBoyScreen extends StatelessWidget {
} }
void _showDeleteDialog(BuildContext context, String id) { void _showDeleteDialog(BuildContext context, String id) {
final shopId = getShopId(context);
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
isScrollControlled: true, isScrollControlled: true,
builder: (_) => Container( builder: (BuildContext bottomSheetContext) {
return Container(
padding: EdgeInsets.all(24), padding: EdgeInsets.all(24),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
@@ -184,9 +224,14 @@ class ServiceBoyScreen extends StatelessWidget {
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
), ),
onPressed: () { onPressed: () async {
// Provider.of<ServiceBoyProvider>(context, listen: false).deleteServiceBoy(id); if (shopId != null) {
Navigator.pop(context); await Provider.of<ServiceBoyProvider>(
bottomSheetContext,
listen: false,
).deleteServiceBoy(id, shopId);
}
Navigator.pop(bottomSheetContext); // Correct context
}, },
child: Text( child: Text(
'Confirm', 'Confirm',
@@ -197,7 +242,8 @@ class ServiceBoyScreen extends StatelessWidget {
SizedBox(height: 12), SizedBox(height: 12),
], ],
), ),
), );
},
); );
} }

View File

@@ -1,15 +1,41 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:glowwheels/screens/privacy_policy_screen.dart'; import 'package:glowwheels/helpers/shopid_helper.dart';
import 'package:glowwheels/screens/profile_details_screen.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:glowwheels/widgets/profile_header.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:glowwheels/screens/profile_details_screen.dart';
class AccountScreen extends StatelessWidget { import 'package:glowwheels/screens/privacy_policy_screen.dart';
import 'package:glowwheels/screens/terms_condition_screen.dart';
class AccountScreen extends StatefulWidget {
const AccountScreen({super.key}); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final shopProfileProvider = Provider.of<ShopProfileProvider>(context);
final shopProfile = shopProfileProvider.shopProfile;
return Scaffold( return Scaffold(
backgroundColor: Colors.white, backgroundColor: Colors.white,
appBar: AppBar( appBar: AppBar(
@@ -20,7 +46,6 @@ class AccountScreen extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: const [ children: const [
ImageIcon(AssetImage("assets/icon/account_icon.png")), ImageIcon(AssetImage("assets/icon/account_icon.png")),
SizedBox(width: 8), SizedBox(width: 8),
Text( Text(
"Account", "Account",
@@ -33,15 +58,17 @@ class AccountScreen extends StatelessWidget {
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(
children: [ children: [
// Profile Info // Profile Info (shows shimmer skeleton if loading)
ProfileHeader(), ProfileHeader(shopProfile: shopProfile),
const SizedBox(height: 24), const SizedBox(height: 24),
// Section Title // Section Title
Align( Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text("Options", child: Text(
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), "Options",
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
@@ -76,7 +103,14 @@ class AccountScreen extends StatelessWidget {
OptionTile( OptionTile(
icon: "assets/icon/logout_icon.png", icon: "assets/icon/logout_icon.png",
title: "Log Out", 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, isDestructive: true,
), ),
], ],
@@ -103,19 +137,20 @@ class OptionTile extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Card( return Card(
color: isDestructive ? Colors.white : Colors.white, color: Colors.white,
elevation: 0.5, elevation: 0.5,
margin: const EdgeInsets.symmetric(vertical: 6), margin: const EdgeInsets.symmetric(vertical: 6),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: ListTile( child: ListTile(
leading: ImageIcon(AssetImage(icon), leading: ImageIcon(AssetImage(icon), color: const Color.fromRGBO(25, 25, 112, 1)),
color: Color.fromRGBO(25, 25, 112, 1)), title: Text(
title: Text(title, title,
style: const TextStyle(
style: GoogleFonts.inter( fontSize: 14,
fontSize: 14,color: Color.fromRGBO(41, 45, 50, 1) color: Color.fromRGBO(41, 45, 50, 1),
,fontWeight: FontWeight.w500 fontWeight: FontWeight.w500,
),), ),
),
trailing: const Icon(Icons.arrow_forward_ios, size: 16), trailing: const Icon(Icons.arrow_forward_ios, size: 16),
onTap: onTap, onTap: onTap,
), ),

View File

@@ -1,16 +1,33 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:glowwheels/helpers/shopid_helper.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../provider/serviceboy_provider.dart';
import 'package:google_fonts/google_fonts.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 _formKey = GlobalKey<FormState>();
final TextEditingController nameController = TextEditingController(); final TextEditingController nameController = TextEditingController();
final TextEditingController phoneController = TextEditingController(); final TextEditingController phoneController = TextEditingController();
bool isLoading = false;
late String shopId='';
@override
void initState() {
// TODO: implement initState
shopId = getShopId(context)!;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final serviceBoyProvider = Provider.of<ServiceBoyProvider>(context, listen: false);
return Scaffold( return Scaffold(
backgroundColor: Color(0xFFF9FAF4), // light background as per screenshot backgroundColor: Color(0xFFF9FAF4),
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
elevation: 0, elevation: 0,
@@ -21,11 +38,11 @@ class AddServiceBoyScreen extends StatelessWidget {
title: Text( title: Text(
'Add Service boy', 'Add Service boy',
style: GoogleFonts.nunito( style: GoogleFonts.nunito(
fontSize: 18,color: Color.fromRGBO(26, 26, 26, 1) fontSize: 18,
,fontWeight: FontWeight.w500 color: Color.fromRGBO(26, 26, 26, 1),
) fontWeight: FontWeight.w500,
),
), ),
//centerTitle: true,
), ),
body: Padding( body: Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
@@ -33,15 +50,15 @@ class AddServiceBoyScreen extends StatelessWidget {
key: _formKey, key: _formKey,
child: Column( child: Column(
children: [ children: [
SizedBox(height: 30,), SizedBox(height: 30),
TextFormField( TextFormField(
controller: nameController, controller: nameController,
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Name', hintText: 'Name',
hintStyle: GoogleFonts.nunito( hintStyle: GoogleFonts.nunito(
fontSize: 14,color: Color.fromRGBO(26, 26, 26, 1) fontSize: 14,
,fontWeight: FontWeight.w400 color: Color.fromRGBO(26, 26, 26, 1),
fontWeight: FontWeight.w400,
), ),
border: OutlineInputBorder(), border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 14), contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 14),
@@ -55,8 +72,9 @@ class AddServiceBoyScreen extends StatelessWidget {
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Mobile Number', hintText: 'Mobile Number',
hintStyle: GoogleFonts.nunito( hintStyle: GoogleFonts.nunito(
fontSize: 14,color: Color.fromRGBO(26, 26, 26, 1) fontSize: 14,
,fontWeight: FontWeight.w400 color: Color.fromRGBO(26, 26, 26, 1),
fontWeight: FontWeight.w400,
), ),
border: OutlineInputBorder(), border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 14), contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 14),
@@ -67,28 +85,69 @@ class AddServiceBoyScreen extends StatelessWidget {
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: isLoading
/* if (_formKey.currentState!.validate()) { ? null
Provider.of<ServiceBoyProvider>(context, listen: false).addServiceBoy( : () async {
nameController.text, if (_formKey.currentState!.validate()) {
phoneController.text, 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); 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( style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF000B8C), // dark blue backgroundColor: Color(0xFF000B8C),
padding: EdgeInsets.symmetric(vertical: 14), padding: EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),
), ),
child: Text('Submit', style: child: isLoading
? SizedBox(
GoogleFonts.inter( height: 20,
fontSize: 18,color: Colors.white width: 20,
,fontWeight: FontWeight.w600 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:flutter/material.dart';
import 'package:glowwheels/helpers/shopid_helper.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../models/serviceboy_model.dart'; import '../models/serviceboy_model.dart';
import '../provider/serviceboy_provider.dart'; import '../provider/serviceboy_provider.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
class EditServiceBoyScreen extends StatelessWidget {
class EditServiceBoyScreen extends StatefulWidget {
final ServiceBoy serviceBoy; final ServiceBoy serviceBoy;
EditServiceBoyScreen({required this.serviceBoy}); EditServiceBoyScreen({required this.serviceBoy});
@override
_EditServiceBoyScreenState createState() => _EditServiceBoyScreenState();
}
class _EditServiceBoyScreenState extends State<EditServiceBoyScreen> {
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
late final TextEditingController nameController = late final TextEditingController nameController;
TextEditingController(text: serviceBoy.name); late final TextEditingController phoneController;
late final TextEditingController phoneController =
TextEditingController(text: serviceBoy.phone); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF9FAF4), // light background backgroundColor: const Color(0xFFF9FAF4),
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
elevation: 0, elevation: 0,
@@ -28,11 +72,10 @@ class EditServiceBoyScreen extends StatelessWidget {
title: Text( title: Text(
'Edit Service boy Details', 'Edit Service boy Details',
style: GoogleFonts.nunito( style: GoogleFonts.nunito(
fontSize: 18,color: Color.fromRGBO(26, 26, 26, 1) fontSize: 18,
,fontWeight: FontWeight.w500 color: Color.fromRGBO(26, 26, 26, 1),
) fontWeight: FontWeight.w500),
), ),
), ),
body: Padding( body: Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
@@ -40,50 +83,40 @@ class EditServiceBoyScreen extends StatelessWidget {
key: _formKey, key: _formKey,
child: Column( child: Column(
children: [ children: [
SizedBox(height: 30,), SizedBox(height: 30),
TextFormField( TextFormField(
controller: nameController, controller: nameController,
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Name', hintText: 'Name',
hintStyle: GoogleFonts.nunito( hintStyle: GoogleFonts.nunito(
fontSize: 14,color: Color.fromRGBO(26, 26, 26, 1) fontSize: 14,
,fontWeight: FontWeight.w400 color: Color.fromRGBO(26, 26, 26, 1),
), fontWeight: FontWeight.w400),
border: OutlineInputBorder(), 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( TextFormField(
controller: phoneController, controller: phoneController,
keyboardType: TextInputType.phone, keyboardType: TextInputType.phone,
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Mobile Number', hintText: 'Mobile Number',
hintStyle: GoogleFonts.nunito( hintStyle: GoogleFonts.nunito(
fontSize: 14,color: Color.fromRGBO(26, 26, 26, 1) fontSize: 14,
,fontWeight: FontWeight.w400 color: Color.fromRGBO(26, 26, 26, 1),
), fontWeight: FontWeight.w400),
border: OutlineInputBorder(), 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( SizedBox(
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: isLoading ? null : () => _saveDetails(context),
/* if (_formKey.currentState!.validate()) {
Provider.of<ServiceBoyProvider>(context, listen: false)
.editServiceBoy(
serviceBoy.id,
nameController.text,
phoneController.text,
);
Navigator.pop(context);
}*/
},
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF000B8C), backgroundColor: const Color(0xFF000B8C),
padding: const EdgeInsets.symmetric(vertical: 14), padding: const EdgeInsets.symmetric(vertical: 14),
@@ -91,10 +124,20 @@ class EditServiceBoyScreen extends StatelessWidget {
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),
), ),
child: Text('Save', style:GoogleFonts.inter( child: isLoading
fontSize: 18,color: Colors.white ? SizedBox(
,fontWeight: FontWeight.w600 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:flutter/material.dart';
import 'package:glowwheels/provider/shop_provider.dart';
import 'package:google_fonts/google_fonts.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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: Colors.white, backgroundColor: Colors.white,
body: SingleChildScrollView( body: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
// Top Banner
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
@@ -21,32 +58,32 @@ class LoginScreen extends StatelessWidget {
colors: [ colors: [
Color.fromRGBO(80, 166, 242, 0.66), Color.fromRGBO(80, 166, 242, 0.66),
Color.fromRGBO(168, 134, 255, 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), 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( child: Padding(
padding: const EdgeInsets.all(20.0), padding: const EdgeInsets.all(20.0),
child: Column( child: Column(
children: [ children: [
const SizedBox(height: 30), SizedBox(height: 30),
Image.asset('assets/images/signinlogo.png', height: 250), Image.asset('assets/images/signinlogo.png', height: 250),
const SizedBox(height: 20), SizedBox(height: 20),
const SizedBox(height: 30),
Align( Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text("Welcome back!", style: Text("Welcome back!",
GoogleFonts.istokWeb( style: GoogleFonts.istokWeb(
fontSize: 18,color: Color.fromRGBO(25, 25, 112, 0.87) fontSize: 18,
,fontWeight: FontWeight.w700 color: Color.fromRGBO(25, 25, 112, 0.87),
fontWeight: FontWeight.w700,
)), )),
Container( Container(
width: 200, width: 200,
child: Text("Please sign in to continue", child: Text("Please sign in to continue",
@@ -54,7 +91,7 @@ class LoginScreen extends StatelessWidget {
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
height: 0.9, 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(
padding: const EdgeInsets.symmetric(horizontal: 20.0), padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Form( child: Form(
key: _formKey,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox(height: 20),
const SizedBox(height: 20), Text('Enter Email Id', style: GoogleFonts.radioCanada(fontSize: 16)),
Text('Enter Email Id',style:GoogleFonts.radioCanada( SizedBox(height: 5),
fontSize: 16, TextFormField(
fontWeight: FontWeight.w400, controller: _emailController,
//color: Color.fromRGBO(25, 25, 112, 0.87) validator: (value) {
)), if (value == null || value.isEmpty) {
const SizedBox(height: 5), return 'Please enter your email';
TextField( }
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
return 'Enter a valid email';
}
return null;
},
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'abcd@gmail.com', hintText: 'abcd@gmail.com',
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15.0) borderRadius: BorderRadius.circular(15.0),
), ),
), ),
), ),
const SizedBox(height: 20), SizedBox(height: 20),
Text('Enter Password',style:GoogleFonts.radioCanada( Text('Enter Password', style: GoogleFonts.radioCanada(fontSize: 16)),
fontSize: 16, SizedBox(height: 5),
fontWeight: FontWeight.w400, TextFormField(
//color: Color.fromRGBO(25, 25, 112, 0.87) controller: _passwordController,
)), obscureText: _obscurePassword,
const SizedBox(height: 5), validator: (value) {
TextField( if (value == null || value.isEmpty) {
obscureText: true, return 'Please enter your password';
}
return null;
},
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Enter your password',
border: OutlineInputBorder( 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( SizedBox(
width: double.infinity, width: double.infinity,
height: 50, height: 50,
@@ -116,16 +173,16 @@ class LoginScreen extends StatelessWidget {
borderRadius: BorderRadius.circular(25), borderRadius: BorderRadius.circular(25),
), ),
), ),
onPressed: () { onPressed: _isLoading ? null : () => _handleLogin(context),
Navigator.push(context, MaterialPageRoute(builder: (context)=>MainScreen())); child: _isLoading
}, ? CircularProgressIndicator(color: Colors.white)
child: Text('Login', style: : Text(
'Login',
GoogleFonts.inter( style: GoogleFonts.inter(
fontSize: 18,color: Colors.white fontSize: 16,
,fontWeight: FontWeight.w600 color: Colors.white,
) fontWeight: FontWeight.w700,
),
), ),
), ),
) )

View File

@@ -1,43 +1,34 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:glowwheels/helpers/shopid_helper.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:glowwheels/models/order_model.dart';
import '../models/order_model.dart';
import '../provider/order_provider.dart'; import '../provider/order_provider.dart';
import '../widgets/order_card.dart'; import '../widgets/order_card.dart';
class OrdersScreen extends StatelessWidget { class OrdersScreen extends StatefulWidget {
final TextStyle labelStyle = TextStyle(fontWeight: FontWeight.w500); @override
final TextStyle valueStyle = TextStyle(fontWeight: FontWeight.normal); _OrdersScreenState createState() => _OrdersScreenState();
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 _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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -67,30 +58,52 @@ class OrdersScreen extends StatelessWidget {
), ),
body: Padding( body: Padding(
padding: const EdgeInsets.only(top: 8.0), padding: const EdgeInsets.only(top: 8.0),
child: Consumer<OrderProvider>( child: Consumer<OrdersProvider>(
builder: (context, orderProvider, _) { builder: (context, orderProvider, _) {
final orders = orderProvider.orders; final orders = orderProvider.orders;
if (orderProvider.isLoading && !orderProvider.isRefreshing) {
return Center(child: CircularProgressIndicator());
}
if (orders.isEmpty) { if (orders.isEmpty) {
return Center( return RefreshIndicator(
onRefresh: _refreshOrders,
child: ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: [
SizedBox(height: 100),
Center(
child: Image.asset( child: Image.asset(
'assets/images/noorder.png', 'assets/images/noorder.png',
width: 200, width: 200,
height: 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, itemCount: orders.length,
itemBuilder: (context, index) { 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:flutter/material.dart';
import 'package:glowwheels/helpers/shopid_helper.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.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 @override
Widget build(BuildContext context) { State<ServiceCenterDetailsScreen> createState() => _ServiceCenterDetailsScreenState();
final shop = Provider.of<ShopProvider>(context).shop;
if (shop == null) {
return Scaffold(
body: Center(child: CircularProgressIndicator()),
);
} }
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( return Scaffold(
backgroundColor: Color(0xFFEAF5FF), backgroundColor: const Color(0xFFEAF5FF),
body: SafeArea( body: SafeArea(
child: Container( child: Container(
decoration: BoxDecoration( decoration: const BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [Color.fromRGBO(208, 235, 255, 1), Colors.white], colors: [Color.fromRGBO(208, 235, 255, 1), Colors.white],
begin: Alignment.topCenter, begin: Alignment.topCenter,
@@ -35,9 +51,9 @@ class ServiceCenterDetailsScreen extends StatelessWidget {
children: [ children: [
GestureDetector( GestureDetector(
onTap: () => Navigator.pop(context), 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( Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.only(top: 15.0), 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( Expanded(
child: SingleChildScrollView( child: provider.isLoading
? _buildShimmer()
: shop == null
? const Center(child: Text('Failed to load shop details'))
: SingleChildScrollView(
child: Center( child: Center(
child: Container( child: Container(
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 10), margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
padding: EdgeInsets.all(8), padding: const EdgeInsets.all(8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
boxShadow: [ boxShadow: const [
BoxShadow( BoxShadow(
color: Colors.black12, color: Colors.black12,
blurRadius: 10, blurRadius: 10,
@@ -79,36 +98,54 @@ class ServiceCenterDetailsScreen extends StatelessWidget {
children: [ children: [
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
child: Image.asset( child: shop.images != null && shop.images!.isNotEmpty
"assets/images/shop_image.jpg", ? Image.network(
shop.images[0]!,
height: 230, height: 230,
width: double.infinity, width: double.infinity,
fit: BoxFit.cover, fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) => 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( Text(
shop.shopName, shop.name ?? 'No Shop Name',
style: GoogleFonts.inter( style: GoogleFonts.inter(
fontSize: 24, fontSize: 24,
color: Colors.black, color: Colors.black,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
), ),
SizedBox(height: 16), const SizedBox(height: 16),
InfoRow( InfoRow(
icon: "assets/icon/location_icon.png", icon: "assets/icon/location_icon.png",
text: shop.address ?? 'Address not available', text: shop.address ?? 'Address not available',
), ),
InfoRow( InfoRow(
icon: "assets/icon/contact_icon.png", icon: "assets/icon/contact_icon.png",
text: shop.mobile, text: shop.phone ?? 'Phone not available',
), ),
InfoRow( InfoRow(
icon: "assets/icon/Message_icon.png", 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 { class InfoRow extends StatelessWidget {
@@ -129,6 +242,7 @@ class InfoRow extends StatelessWidget {
final String text; final String text;
const InfoRow({ const InfoRow({
super.key,
required this.icon, required this.icon,
required this.text, required this.text,
}); });
@@ -141,7 +255,7 @@ class InfoRow extends StatelessWidget {
child: Row( child: Row(
children: [ children: [
ImageIcon(AssetImage(icon)), ImageIcon(AssetImage(icon)),
SizedBox(width: 10), const SizedBox(width: 10),
Expanded( Expanded(
child: Text( child: Text(
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:flutter/material.dart';
import 'package:glowwheels/helpers/shopid_helper.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../models/serviceboy_model.dart';
import '../provider/serviceboy_provider.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
class AssignServiceBoyDialog extends StatelessWidget { import 'package:glowwheels/models/serviceboy_model.dart';
final List<ServiceBoy> serviceBoys; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -19,14 +33,17 @@ class AssignServiceBoyDialog extends StatelessWidget {
return AlertDialog( return AlertDialog(
backgroundColor: Colors.white, backgroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), 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( content: SizedBox(
height: 200, height: 200,
width: double.maxFinite, width: double.maxFinite,
child: ListView.builder( child: ListView.builder(
itemCount: serviceBoys.length, itemCount: widget.serviceBoys.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final boy = serviceBoys[index]; final boy = widget.serviceBoys[index];
final isSelected = assignProvider.selectedBoy == boy; final isSelected = assignProvider.selectedBoy == boy;
return GestureDetector( return GestureDetector(
@@ -35,8 +52,9 @@ class AssignServiceBoyDialog extends StatelessWidget {
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
margin: const EdgeInsets.symmetric(vertical: 4), margin: const EdgeInsets.symmetric(vertical: 4),
decoration: BoxDecoration( 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), borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey.shade300),
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -64,28 +82,80 @@ class AssignServiceBoyDialog extends StatelessWidget {
), ),
actions: [ actions: [
TextButton( TextButton(
child: Text("CANCEL",style: GoogleFonts.poppins( onPressed: () => Navigator.of(context).pop(),
fontSize: 14,color: Color.fromRGBO(25, 25, 112, 1) child: Text(
,fontWeight: FontWeight.w400 "CANCEL",
),), style: GoogleFonts.poppins(
onPressed: () { fontSize: 14,
Navigator.of(context).pop(); // return null color: const Color.fromRGBO(25, 25, 112, 1),
}, fontWeight: FontWeight.w400,
),
),
), ),
ElevatedButton( ElevatedButton(
onPressed: assignProvider.selectedBoy != null onPressed: assignProvider.selectedBoy != null && !_isLoading
? () { ? () async {
Navigator.of(context).pop(assignProvider.selectedBoy); 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, : null,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF1B1464), backgroundColor: const Color(0xFF1B1464),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)),
), ),
child: Text("Assign",style: GoogleFonts.inter( child: _isLoading
fontSize: 12,color: Colors.white ? const SizedBox(
,fontWeight: FontWeight.w500 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/cupertino.dart';
import 'package:flutter/material.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 'package:google_fonts/google_fonts.dart';
import '../models/order_model.dart'; import 'package:glowwheels/models/order_model.dart';
import '../models/serviceboy_model.dart'; import 'package:provider/provider.dart';
import 'package:glowwheels/models/serviceboy_model.dart';
import 'assign_serviceboy_dialog.dart'; import 'assign_serviceboy_dialog.dart';
class OrderCard extends StatefulWidget { class OrderCard extends StatefulWidget {
@@ -16,12 +20,18 @@ class OrderCard extends StatefulWidget {
class _OrderCardState extends State<OrderCard> { class _OrderCardState extends State<OrderCard> {
ServiceBoy? assignedBoy; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -40,18 +50,31 @@ class _OrderCardState extends State<OrderCard> {
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ 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), SizedBox(width: 16),
Expanded( Expanded(
child: Column( child: Column(
children: [ children: [
_buildRow("Customer Name", widget.order.customerName), _buildRow("Customer Name", widget.order.user.name),
_buildRow("Mobile Number", widget.order.mobileNumber), _buildRow("Mobile Number", widget.order.user.phone.toString()),
_buildRow("Service Type", widget.order.serviceType), _buildRow("Service Type", widget.order.service.serviceType),
_buildRow("Service", widget.order.service), _buildRow("Service", widget.order.service.serviceName),
_buildRow("Price", widget.order.price), _buildRow("Price", widget.order.service.price.toString()),
_buildRow("Service Time", widget.order.time), _buildRow("Service Time", widget.order.timeSlot),
_buildRow("Service Date", widget.order.date), _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), SizedBox(height: 12),
Center( Center(
child: Text( child: Text(
widget.order.carName, widget.order.service.manufacture.manufacture +
" " +
widget.order.service.model.model,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
), ),
), ),
@@ -68,20 +93,103 @@ class _OrderCardState extends State<OrderCard> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( GestureDetector(
widget.order.status, 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( 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, fontWeight: FontWeight.bold,
fontSize: 16, 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>( final selected = await showDialog<ServiceBoy>(
context: context, context: context,
builder: (_) => AssignServiceBoyDialog(serviceBoys: serviceBoys), builder: (_) => AssignServiceBoyDialog(
serviceBoys: serviceBoyProvider.serviceBoys,
orderId: widget.order.id,
),
); );
if (selected != null) { if (selected != null) {
@@ -89,25 +197,106 @@ class _OrderCardState extends State<OrderCard> {
assignedBoy = selected; assignedBoy = selected;
}); });
} }
setState(() {
isLoading = false;
});
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF1B1464), 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)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)),
), ),
child: Text("Assign",style: GoogleFonts.inter( child: Text(
fontSize: 12,color: Colors.white "Cancel",
,fontWeight: FontWeight.w500 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, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text("Assigned to: ${assignedBoy!.name}", Text("Assigned to: ${assignedBoy!.name}",
style: const TextStyle(fontWeight: FontWeight.bold)), style: const TextStyle(fontWeight: FontWeight.bold)),
Text("Phone: ${assignedBoy!.phone}"), Text("Phone: ${assignedBoy!.phone}"),
], ],
), )
], ],
) )
], ],
@@ -121,18 +310,20 @@ class _OrderCardState extends State<OrderCard> {
padding: const EdgeInsets.symmetric(vertical: 3), padding: const EdgeInsets.symmetric(vertical: 3),
child: Row( child: Row(
children: [ children: [
Expanded(flex: 2, child: Text(label, style: GoogleFonts.inter( Expanded(
fontSize: 10,color: Colors.black flex: 2,
,fontWeight: FontWeight.w500 child: Text(label,
))), style: GoogleFonts.inter(
Expanded(flex: 3, child: Text(value, style: GoogleFonts.inter( fontSize: 10, color: Colors.black, fontWeight: FontWeight.w500)),
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:flutter/material.dart';
import 'package:shimmer/shimmer.dart';
import 'package:glowwheels/models/shop_profile_model.dart';
class ProfileHeader extends StatelessWidget { class ProfileHeader extends StatelessWidget {
const ProfileHeader({super.key}); final ShopProfileModel? shopProfile;
const ProfileHeader({super.key, this.shopProfile});
@override @override
Widget build(BuildContext context) { 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( return Container(
color: Colors.white, color: Colors.white,
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Row( child: Row(
children: [ children: [
// Circular Profile Image
CircleAvatar( CircleAvatar(
radius: 40, 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), const SizedBox(width: 16),
// Details Column
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: const [ children: [
Row( Row(
children: [ children: [
ImageIcon(AssetImage("assets/icon/account_icon.png")), const ImageIcon(AssetImage("assets/icon/account_icon.png")),
SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
child: Text( child: Text(
'Omkara Car Wash Center', name,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
), ),
), ),
], ],
), ),
SizedBox(height: 8), const SizedBox(height: 8),
Row( Row(
children: [ children: [
ImageIcon(AssetImage("assets/icon/contact_icon.png")), const ImageIcon(AssetImage("assets/icon/contact_icon.png")),
SizedBox(width: 8), const SizedBox(width: 8),
Text('+91 9999988888'), Text(phone),
], ],
), ),
SizedBox(height: 8), const SizedBox(height: 8),
Row( Row(
children: [ children: [
ImageIcon(AssetImage("assets/icon/Message_icon.png")), const ImageIcon(AssetImage("assets/icon/Message_icon.png")),
SizedBox(width: 8), const SizedBox(width: 8),
Expanded(child: Text('loremipsum@gmail.com')), 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" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" 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: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter

View File

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