Commit 97403192 authored by Sai Srinivas's avatar Sai Srinivas Committed by Sai Srinivas
Browse files

Icon inserted and few changes

parent 5a62c920
class allJobCardListResponse {
String? error;
List<JobCardList>? jobCardList;
String? message;
allJobCardListResponse({this.error, this.jobCardList, this.message});
allJobCardListResponse.fromJson(Map<String, dynamic> json) {
error = json['error'];
if (json['job_card_list'] != null) {
jobCardList = <JobCardList>[];
json['job_card_list'].forEach((v) {
jobCardList!.add(new JobCardList.fromJson(v));
});
}
message = json['message'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['error'] = this.error;
if (this.jobCardList != null) {
data['job_card_list'] = this.jobCardList!.map((v) => v.toJson()).toList();
}
data['message'] = this.message;
return data;
}
}
class JobCardList {
String? id;
String? complaintId;
String? extraDescription;
String? date;
String? totalPrice;
JobCardList(
{this.id,
this.complaintId,
this.extraDescription,
this.date,
this.totalPrice});
JobCardList.fromJson(Map<String, dynamic> json) {
id = json['id'];
complaintId = json['complaint_id'];
extraDescription = json['extra_description'];
date = json['date'];
totalPrice = json['total_price'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['complaint_id'] = this.complaintId;
data['extra_description'] = this.extraDescription;
data['date'] = this.date;
data['total_price'] = this.totalPrice;
return data;
}
}
class allServiceListResponse {
String? error;
TechDetails? techDetails;
List<AllServiceList>? allServiceList;
String? message;
allServiceListResponse(
{this.error, this.techDetails, this.allServiceList, this.message});
allServiceListResponse.fromJson(Map<String, dynamic> json) {
error = json['error'];
techDetails = json['tech_details'] != null
? new TechDetails.fromJson(json['tech_details'])
: null;
if (json['all_service_list'] != null) {
allServiceList = <AllServiceList>[];
json['all_service_list'].forEach((v) {
allServiceList!.add(new AllServiceList.fromJson(v));
});
}
message = json['message'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['error'] = this.error;
if (this.techDetails != null) {
data['tech_details'] = this.techDetails!.toJson();
}
if (this.allServiceList != null) {
data['all_service_list'] =
this.allServiceList!.map((v) => v.toJson()).toList();
}
data['message'] = this.message;
return data;
}
}
class TechDetails {
String? id;
String? empName;
String? techRoleName;
String? mobNum;
String? lat;
String? lng;
String? profileImg;
TechDetails(
{this.id,
this.empName,
this.techRoleName,
this.mobNum,
this.lat,
this.lng,
this.profileImg});
TechDetails.fromJson(Map<String, dynamic> json) {
id = json['id'];
empName = json['emp_name'];
techRoleName = json['tech_role_name'];
mobNum = json['mob_num'];
lat = json['lat'];
lng = json['lng'];
profileImg = json['profile_img'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['emp_name'] = this.empName;
data['tech_role_name'] = this.techRoleName;
data['mob_num'] = this.mobNum;
data['lat'] = this.lat;
data['lng'] = this.lng;
data['profile_img'] = this.profileImg;
return data;
}
}
class AllServiceList {
String? id;
String? empName;
String? date;
String? techRoleName;
String? mobNum;
String? profileImg;
String? feedback;
String? openStatus;
String? customerServiceRating;
String? inOrOutTime;
String? runningHrs;
String? fsrExt;
String? fsrNo;
AllServiceList(
{this.id,
this.empName,
this.date,
this.techRoleName,
this.mobNum,
this.profileImg,
this.feedback,
this.openStatus,
this.customerServiceRating,
this.inOrOutTime,
this.runningHrs,
this.fsrExt,
this.fsrNo});
AllServiceList.fromJson(Map<String, dynamic> json) {
id = json['id'];
empName = json['emp_name'];
date = json['date'];
techRoleName = json['tech_role_name'];
mobNum = json['mob_num'];
profileImg = json['profile_img'];
feedback = json['feedback'];
openStatus = json['open_status'];
customerServiceRating = json['customer_service_rating'];
inOrOutTime = json['in_or_out_time'];
runningHrs = json['running_hrs'];
fsrExt = json['fsr_ext'];
fsrNo = json['fsr_no'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['emp_name'] = this.empName;
data['date'] = this.date;
data['tech_role_name'] = this.techRoleName;
data['mob_num'] = this.mobNum;
data['profile_img'] = this.profileImg;
data['feedback'] = this.feedback;
data['open_status'] = this.openStatus;
data['customer_service_rating'] = this.customerServiceRating;
data['in_or_out_time'] = this.inOrOutTime;
data['running_hrs'] = this.runningHrs;
data['fsr_ext'] = this.fsrExt;
data['fsr_no'] = this.fsrNo;
return data;
}
}
class complaintDetailsResponse {
String? error;
List<ComplaintDetails>? complaintDetails;
List<JobCardList>? jobCardList;
List<ServiceDetails>? serviceDetails;
String? message;
String? sessionExists;
complaintDetailsResponse(
{this.error,
this.complaintDetails,
this.jobCardList,
this.serviceDetails,
this.message,
this.sessionExists});
complaintDetailsResponse.fromJson(Map<String, dynamic> json) {
error = json['error'];
if (json['complaint_details'] != null) {
complaintDetails = <ComplaintDetails>[];
json['complaint_details'].forEach((v) {
complaintDetails!.add(new ComplaintDetails.fromJson(v));
});
}
if (json['job_card_list'] != null) {
jobCardList = <JobCardList>[];
json['job_card_list'].forEach((v) {
jobCardList!.add(new JobCardList.fromJson(v));
});
}
if (json['service_details'] != null) {
serviceDetails = <ServiceDetails>[];
json['service_details'].forEach((v) {
serviceDetails!.add(new ServiceDetails.fromJson(v));
});
}
message = json['message'];
sessionExists = json['session_exists'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['error'] = this.error;
if (this.complaintDetails != null) {
data['complaint_details'] =
this.complaintDetails!.map((v) => v.toJson()).toList();
}
if (this.jobCardList != null) {
data['job_card_list'] = this.jobCardList!.map((v) => v.toJson()).toList();
}
if (this.serviceDetails != null) {
data['service_details'] =
this.serviceDetails!.map((v) => v.toJson()).toList();
}
data['message'] = this.message;
data['session_exists'] = this.sessionExists;
return data;
}
}
class ComplaintDetails {
String? id;
String? openStatus;
String? registredDate;
String? hashId;
String? productName;
String? complaintName;
String? complaintDesc;
String? complaintNote;
ComplaintDetails(
{this.id,
this.openStatus,
this.registredDate,
this.hashId,
this.productName,
this.complaintName,
this.complaintDesc,
this.complaintNote});
ComplaintDetails.fromJson(Map<String, dynamic> json) {
id = json['id'];
openStatus = json['open_status'];
registredDate = json['registred_date'];
hashId = json['hash_id'];
productName = json['product_name'];
complaintName = json['complaint_name'];
complaintDesc = json['complaint_desc'];
complaintNote = json['complaint_note'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['open_status'] = this.openStatus;
data['registred_date'] = this.registredDate;
data['hash_id'] = this.hashId;
data['product_name'] = this.productName;
data['complaint_name'] = this.complaintName;
data['complaint_desc'] = this.complaintDesc;
data['complaint_note'] = this.complaintNote;
return data;
}
}
class JobCardList {
String? id;
String? complaintId;
String? extraDescription;
String? date;
String? totalPrice;
JobCardList(
{this.id,
this.complaintId,
this.extraDescription,
this.date,
this.totalPrice});
JobCardList.fromJson(Map<String, dynamic> json) {
id = json['id'];
complaintId = json['complaint_id'];
extraDescription = json['extra_description'];
date = json['date'];
totalPrice = json['total_price'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['complaint_id'] = this.complaintId;
data['extra_description'] = this.extraDescription;
data['date'] = this.date;
data['total_price'] = this.totalPrice;
return data;
}
}
class ServiceDetails {
String? id;
String? empName;
String? lat;
String? lng;
String? paymentStatus;
String? date;
String? techRoleName;
String? mobNum;
String? profileImg;
String? feedback;
String? openStatus;
String? rating;
String? inOrOutTime;
String? runningHrs;
String? fsrExt;
String? fsrNo;
ServiceDetails(
{this.id,
this.empName,
this.lat,
this.lng,
this.paymentStatus,
this.date,
this.techRoleName,
this.mobNum,
this.profileImg,
this.feedback,
this.openStatus,
this.rating,
this.inOrOutTime,
this.runningHrs,
this.fsrExt,
this.fsrNo});
ServiceDetails.fromJson(Map<String, dynamic> json) {
id = json['id'];
empName = json['emp_name'];
lat = json['lat'];
lng = json['lng'];
paymentStatus = json['payment_status'];
date = json['date'];
techRoleName = json['tech_role_name'];
mobNum = json['mob_num'];
profileImg = json['profile_img'];
feedback = json['feedback'];
openStatus = json['open_status'];
rating = json['rating'];
inOrOutTime = json['in_or_out_time'];
runningHrs = json['running_hrs'];
fsrExt = json['fsr_ext'];
fsrNo = json['fsr_no'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['emp_name'] = this.empName;
data['lat'] = this.lat;
data['lng'] = this.lng;
data['payment_status'] = this.paymentStatus;
data['date'] = this.date;
data['tech_role_name'] = this.techRoleName;
data['mob_num'] = this.mobNum;
data['profile_img'] = this.profileImg;
data['feedback'] = this.feedback;
data['open_status'] = this.openStatus;
data['rating'] = this.rating;
data['in_or_out_time'] = this.inOrOutTime;
data['running_hrs'] = this.runningHrs;
data['fsr_ext'] = this.fsrExt;
data['fsr_no'] = this.fsrNo;
return data;
}
}
class jobCardProductsResponse {
String? error;
List<JobCardProducts>? jobCardProducts;
String? message;
jobCardProductsResponse({this.error, this.jobCardProducts, this.message});
jobCardProductsResponse.fromJson(Map<String, dynamic> json) {
error = json['error'];
if (json['job_card_products'] != null) {
jobCardProducts = <JobCardProducts>[];
json['job_card_products'].forEach((v) {
jobCardProducts!.add(new JobCardProducts.fromJson(v));
});
}
message = json['message'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['error'] = this.error;
if (this.jobCardProducts != null) {
data['job_card_products'] =
this.jobCardProducts!.map((v) => v.toJson()).toList();
}
data['message'] = this.message;
return data;
}
}
class JobCardProducts {
String? id;
String? partName;
String? qty;
String? price;
String? totalPrice;
JobCardProducts(
{this.id, this.partName, this.qty, this.price, this.totalPrice});
JobCardProducts.fromJson(Map<String, dynamic> json) {
id = json['id'];
partName = json['part_name'];
qty = json['qty'];
price = json['price'];
totalPrice = json['total_price'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['part_name'] = this.partName;
data['qty'] = this.qty;
data['price'] = this.price;
data['total_price'] = this.totalPrice;
return data;
}
}
class ratingResponse {
String? error;
String? message;
String? sessionExists;
ratingResponse({this.error, this.message, this.sessionExists});
ratingResponse.fromJson(Map<String, dynamic> json) {
error = json['error'];
message = json['message'];
sessionExists = json['session_exists'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['error'] = this.error;
data['message'] = this.message;
data['session_exists'] = this.sessionExists;
return data;
}
}
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:gen_service/Models/HelpAndComplaintModels/complaintDetailsResponse.dart';
import 'package:gen_service/Models/HelpAndComplaintModels/jobCardProductsResponse.dart';
import 'package:gen_service/Services/api_calling.dart';
import 'package:provider/provider.dart';
import '../Models/CommonResponse.dart';
import '../Models/HelpAndComplaintModels/ComplaintListResponse.dart';
......@@ -12,6 +16,9 @@ class HelpAndComplaintProvider extends ChangeNotifier {
String? _errorMessage;
ComplaintListResponse? _complaintListResponse;
complaintDetailsResponse? _complaintDetailsResponse;
jobCardProductsResponse? _jobCardResponse;
bool get isLoading => _isLoading;
String? get errorMessage => _errorMessage;
ComplaintListResponse? get complaintListResponse => _complaintListResponse;
......@@ -23,6 +30,36 @@ class HelpAndComplaintProvider extends ChangeNotifier {
DropDownsListResponse? _dropDownsListResponse;
DropDownsListResponse? get dropDownsListResponse => _dropDownsListResponse;
complaintDetailsResponse? get compDetailsResponse => _complaintDetailsResponse;
jobCardProductsResponse? get jobCardResponse => _jobCardResponse;
List<bool> _starStates = [false, false, false, false, false];
var _rating = 0;
List<bool> get starStates => _starStates;
int get rating => _rating;
set rating(int value) {
_rating = value;
notifyListeners();
}
set starStates(List<bool> value) {
_starStates = value;
notifyListeners();
}
void updateStarStates(int rating, List<bool> starStates) {
starStates.fillRange(0, starStates.length, false);
for (int i = 0; i < rating; i++) {
starStates[i] = true;
}
notifyListeners();
}
///----------------------------------------------
/// Fetch Complaints List
......@@ -140,6 +177,80 @@ class HelpAndComplaintProvider extends ChangeNotifier {
Future<void> fetchComplaintDetailsAPI(String accId, String sessionId,complaintId) async {
_isLoading = true;
_errorMessage = null;
notifyListeners();
try {
final response = await ApiCalling.complaintDetailsAPI(accId, sessionId,complaintId);
if (response != null) {
if (response.error == "0") {
_complaintDetailsResponse = response;
updateStarStates(int.parse(response.serviceDetails?.firstOrNull?.rating.toString()??"0"), _starStates);
} else {
_errorMessage = response.message ?? "Something went wrong!";
}
} else {
_errorMessage = "No response from server.";
}
} catch (e) {
_errorMessage = "Failed to fetch dashboard: $e";
} finally {
_isLoading = false;
notifyListeners();
}
}
Future<void> updateRatingForTechnician(String accId, String sessionId,complaintId,rating) async {
// _errorMessage = null;
notifyListeners();
try {
final response = await ApiCalling.updateTechRatingAPI(accId, sessionId,complaintId,rating);
if (response != null) {
if (response.error == "0") {
fetchComplaintDetailsAPI(accId, sessionId,complaintId);
} else {
// _errorMessage = response.message ?? "Something went wrong!";
}
} else {
// _errorMessage = "No response from server.";
}
} catch (e) {
_errorMessage = "Failed to fetch dashboard: $e";
} finally {
_isLoading = false;
notifyListeners();
}
}
Future<void> fetchJobCardProductDetails(String accId, String sessionId,jobCardId) async {
_errorMessage = null;
notifyListeners();
try {
final response = await ApiCalling.jobCardProductDetailsAPI(accId, sessionId,jobCardId);
if (response != null) {
if (response.error == "0") {
_jobCardResponse = response;
} else {
_errorMessage = response.message ?? "Something went wrong!";
}
} else {
_errorMessage = "No response from server.";
}
} catch (e) {
_errorMessage = "Failed to fetch dashboard: $e";
} finally {
_isLoading = false;
notifyListeners();
}
}
///----------------------------------------------
/// Private Helpers
......
import 'dart:async';
import 'dart:io';
import 'dart:ui' as ui;
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:gen_service/Models/GetInTouchListResponse.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:geocoding/geocoding.dart' as geocoding;
import 'package:location/location.dart' as Location;
import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';
import '../Services/api_calling.dart';
class MapProvider extends ChangeNotifier {
GetInTouchListResponse? _response;
List<GetInTouchList> _locationsList = [];
final String _googleApikey = "AIzaSyBGzvgMMKwPBAANTwaoRsAnrCpiWCj8wVs";
GoogleMapController? _mapController;
CameraPosition? _cameraPosition;
final LatLng _startLocation = const LatLng(
17.439112226708446,
78.43292499146135,
);
String _latlongs = "";
List<Marker> _markers = [];
List<String> _addresses = [];
Location.LocationData? _currentLocation;
bool _isLocationEnabled = false;
bool _hasLocationPermission = false;
Timer? _timer;
bool _isLoading = true;
Timer? _debounceTimer;
LatLng? _mapCenter;
GetInTouchListResponse? get response => _response;
List<GetInTouchList> get locationsList => _locationsList;
String get googleAPIKey => _googleApikey;
GoogleMapController? get mapController => _mapController;
CameraPosition? get cameraPosition => _cameraPosition;
LatLng get startLocation => _startLocation;
String get latlongs => _latlongs;
List<Marker> get markers => _markers;
List<String> get addresses => _addresses;
Location.LocationData? get currentLocation => _currentLocation;
bool get isLocationEnabled => _isLocationEnabled;
bool get hasLocationPermission => _hasLocationPermission;
bool get isLoading => _isLoading;
Timer? get timer => _timer;
set markers(List<Marker> value) {
_markers = value;
if (value.isNotEmpty) {
_isLoading = false; // Mark screen as loaded when markers are added
}
notifyListeners();
}
set mapController(GoogleMapController? value) {
_mapController = value;
notifyListeners();
}
set mapCenter(LatLng? value) {
_mapCenter = value;
_latlongs = value != null ? '${value.latitude},${value.longitude}' : '';
notifyListeners();
}
set isLoading(bool value) {
_isLoading = value;
notifyListeners();
}
void resetAll() {
_markers = [];
_addresses = [];
_mapCenter = null;
_isLoading = true;
notifyListeners();
}
Future<void> getLocationPermission(BuildContext context, empId,
session,) async {
_isLocationEnabled = await Geolocator.isLocationServiceEnabled();
LocationPermission permission = await Geolocator.checkPermission();
_hasLocationPermission =
permission == LocationPermission.always ||
permission == LocationPermission.whileInUse;
final Location.Location location = Location.Location();
bool serviceEnabled;
Location.PermissionStatus permissionGranted;
serviceEnabled = await location.serviceEnabled();
if (!serviceEnabled) {
serviceEnabled = await location.requestService();
if (!serviceEnabled) {
_isLoading = false;
return;
}
}
permissionGranted = await location.hasPermission();
if (permissionGranted == Location.PermissionStatus.denied) {
permissionGranted = await location.requestPermission();
if (permissionGranted != Location.PermissionStatus.granted) {
// toast(context, 'Location permission denied.');
_isLoading = false;
return;
}
}
final Location.LocationData locData = await location.getLocation();
_currentLocation = locData;
if (_currentLocation != null && _mapCenter == null) {
_mapCenter = LatLng(
_currentLocation!.latitude!,
_currentLocation!.longitude!,
);
_latlongs =
'${_currentLocation!.latitude},${_currentLocation!.longitude}';
_mapController?.animateCamera(CameraUpdate.newLatLng(_mapCenter!));
await nearbyServiceLocations(context, empId,
session,);
}
notifyListeners();
}
void onCameraMove(BuildContext context, CameraPosition position, empId,
session) {
_timer?.cancel();
_mapCenter = position.target;
_latlongs = '${_mapCenter!.latitude},${_mapCenter!.longitude}';
_timer = Timer(Duration(seconds: 1), () {
nearbyServiceLocations(context, empId,
session,);
});
notifyListeners();
}
void debounce(VoidCallback callback, Duration duration) {
_debounceTimer?.cancel();
_debounceTimer = Timer(duration, callback);
}
Future<void> nearbyServiceLocations(
BuildContext context,
empId,
session,
) async {
try {
final data = await ApiCalling.fetchGetInTouchListApi(
empId,
session,
);
if (data != null) {
if (data.error == "0") {
_isLoading = false;
_response = data;
_locationsList = data.getInTouchList ?? [];
await updateMarkersFromApiResponse(context, data.getInTouchList??[]);
if (_locationsList.isEmpty) {
// toast(context, 'No leads found within the selected radius.');
}
}
else {
// toast(context, data.message ?? 'Failed to load leads.');
_isLoading = false;
notifyListeners();
}
} else {
// toast(context, "Something went wrong, please try again.");
_isLoading = false;
notifyListeners();
}
} on Exception catch (e, s) {
print("nearbyServiceLocations error: $e, stack: $s");
// toast(context, 'An error occurred while loading leads.');
_isLoading = false;
notifyListeners();
}
}
Future<void> updateMarkersFromApiResponse(
BuildContext context,
List<GetInTouchList> locationsList,
) async {
print("markers updating");
_markers = await createMarkersFromApiResponse(context, locationsList);
_addresses.clear();
await Future.forEach(locationsList, (store) async {
String address = await _getAddressFromLatLng(store.loc);
_addresses.add(address);
print(_addresses);
});
notifyListeners();
}
Future<List<Marker>> createMarkersFromApiResponse(
BuildContext context,
List<GetInTouchList> locationsList,
) async {
List<Marker> markers = [];
ByteData data = await rootBundle.load("assets/images/maps_ic.png");
Uint8List bytes = data.buffer.asUint8List();
await Future.forEach(locationsList, (leads) async {
if (leads.loc == null || leads.id == null || leads.branchName == null) {
print(
'Skipping invalid lead: id=${leads.id}, name=${leads.branchName}, loc=${leads.loc}',
);
return;
}
ui.Codec codec = await ui.instantiateImageCodec(bytes);
ui.FrameInfo fi = await codec.getNextFrame();
Uint8List resizedBytes =
(await fi.image.toByteData(
format: ui.ImageByteFormat.png,
))!.buffer.asUint8List();
markers.add(
Marker(
markerId: MarkerId(leads.id.toString()),
position: _parseLatLng(leads.loc),
icon: BitmapDescriptor.fromBytes(resizedBytes),
infoWindow: InfoWindow(
title: leads.branchName ?? 'Unknown Lead',
snippet: leads.address ?? 'No address available',
anchor: const Offset(0.5, 1.0),
),
zIndex: 100,
onTap: () {
_mapController?.showMarkerInfoWindow(MarkerId(leads.id.toString()));
print('Marker tapped: id=${leads.id}, name=${leads.branchName}');
},
),
);
});
return markers;
}
LatLng _parseLatLng(String? location) {
if (location != null) {
List<String> parts = location.split(',');
if (parts.length == 2) {
double lat = double.tryParse(parts[0]) ?? 0.0;
double lng = double.tryParse(parts[1]) ?? 0.0;
if (lat != 0.0 && lng != 0.0) {
return LatLng(lat, lng);
}
}
}
print('Invalid location string: $location');
return const LatLng(0.0, 0.0);
}
Future<String> _getAddressFromLatLng(String? location) async {
if (location != null) {
List<String> parts = location.split(',');
if (parts.length == 2) {
double lat = double.tryParse(parts[0]) ?? 0.0;
double lng = double.tryParse(parts[1]) ?? 0.0;
if (lat != 0.0 && lng != 0.0) {
try {
List<geocoding.Placemark> placemarks = await geocoding
.placemarkFromCoordinates(lat, lng);
if (placemarks.isNotEmpty) {
final placemark = placemarks.first;
String address =
'${placemark.street ?? ''}, '
'${placemark.thoroughfare ?? ''} '
'${placemark.subLocality ?? ''}, '
'${placemark.locality ?? ''}, '
'${placemark.administrativeArea ?? ''}, '
'${placemark.subAdministrativeArea ?? ''} '
'${placemark.postalCode ?? ''}, '
'${placemark.country ?? ''}';
return address.trim();
}
} catch (e) {
print('Geocoding error for location $location: $e');
}
}
}
}
return "Address not found";
}
}
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:gen_service/Models/HelpAndComplaintModels/allJobCardListResponse.dart';
import 'package:gen_service/Models/HelpAndComplaintModels/allServiceListResponse.dart';
import 'package:gen_service/Services/api_calling.dart';
class ServiceAndJobCardListProvider extends ChangeNotifier {
bool _isLoading = false;
String? _errorMessage;
allJobCardListResponse? _allJobCardResponse;
allServiceListResponse? _allServiceResponse;
bool get isLoading => _isLoading;
String? get errorMessage => _errorMessage;
allJobCardListResponse? get allJobCardResponse => _allJobCardResponse;
allServiceListResponse? get allServiceResponse => _allServiceResponse;
Map<String, int> _localRatings = {};
Map<String, List<bool>> _starStates = {};
int getRating(String serviceId) => _localRatings[serviceId] ?? 0;
List<bool> getStarStates(String serviceId) {
return _starStates.putIfAbsent(serviceId, () => List.filled(5, false));
}
void setRating(String serviceId, int rating, {bool notify = true}) {
_localRatings[serviceId] = rating;
final stars = getStarStates(serviceId);
for (int i = 0; i < 5; i++) {
stars[i] = i < rating;
}
if (notify) notifyListeners();
}
void initRatingsFromData(List<AllServiceList> services) {
for (var service in services) {
final id = service.id?.toString() ?? service.id.toString();
final backendRating = int.parse(service.customerServiceRating.toString()) ?? 0;
setRating(id, backendRating, notify: false);
}
notifyListeners();
}
Future<void> fetchAllJobCardsListAPI(String accId, String sessionId,complaintId) async {
_isLoading = true;
_errorMessage = null;
notifyListeners();
try {
final response = await ApiCalling.jobCardsListAPI(accId, sessionId,complaintId);
if (response != null) {
if (response.error == "0") {
_allJobCardResponse = response;
} else {
_errorMessage = response.message ?? "Something went wrong!";
}
} else {
_errorMessage = "No response from server.";
}
} catch (e) {
_errorMessage = "Failed to fetch dashboard: $e";
} finally {
_isLoading = false;
notifyListeners();
}
}
Future<void> fetchServiceDetailsAPI(String accId, String sessionId,complaintId) async {
_isLoading = true;
_errorMessage = null;
notifyListeners();
try {
final response = await ApiCalling.serviceListAPI(accId, sessionId,complaintId);
if (response != null) {
if (response.error == "0") {
_allServiceResponse = response;
initRatingsFromData(response.allServiceList??[]);
} else {
_errorMessage = response.message ?? "Something went wrong!";
}
} else {
_errorMessage = "No response from server.";
}
} catch (e) {
_errorMessage = "Failed to fetch dashboard: $e";
} finally {
_isLoading = false;
notifyListeners();
}
}
Future<void> updateRatingForTechnician(String accId, String sessionId,complaintId,rating) async {
// _errorMessage = null;
notifyListeners();
try {
final response = await ApiCalling.updateTechRatingAPI(accId, sessionId,complaintId,rating);
if (response != null) {
if (response.error == "0") {
fetchServiceDetailsAPI(accId, sessionId,complaintId);
setRating(complaintId, rating);
} else {
// _errorMessage = response.message ?? "Something went wrong!";
}
} else {
// _errorMessage = "No response from server.";
}
} catch (e) {
_errorMessage = "Failed to fetch dashboard: $e";
} finally {
_isLoading = false;
notifyListeners();
}
}
void _setLoading(bool value) {
_isLoading = value;
notifyListeners();
}
void clearData() {
_allJobCardResponse = null;
_allServiceResponse = null;
_errorMessage = null;
notifyListeners();
}
}
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final TextEditingController _mobileController = TextEditingController();
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return Scaffold(
resizeToAvoidBottomInset: true,
backgroundColor: Colors.blue,
body: Stack(
children: [
/// 🔹 Background image
Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/images/background_png.png"),
fit: BoxFit.cover,
),
),
),
/// 🔹 Main content (scrollable & keyboard-safe)
SafeArea(
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom, // moves up with keyboard
),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const SizedBox(height: 80),
/// 🔹 Logo
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(width: 20,),
SvgPicture.asset(
"assets/svg/genesis_logo_2io.svg",
height: 48,
color: Colors.white,
),
]
),
const SizedBox(height: 12),
const Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 26, vertical: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Login to",
style: TextStyle(
fontSize: 48,
fontFamily: "PoppinsLight",
fontWeight: FontWeight.w100,
color: Colors.white,
),
),
Text(
"continue",
style: TextStyle(
fontSize: 48,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
],
),
),
],
),
const SizedBox(height: 20),
/// 🔹 Bottom Sheet style area
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(24),
topRight: Radius.circular(24),
),
),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
const Text(
"Enter Registered Mobile No.",
style: TextStyle(
fontSize: 14,
color: Colors.black54,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 10),
/// 🔹 Mobile Field
TextFormField(
controller: _mobileController,
keyboardType: TextInputType.phone,
maxLength: 10,
decoration: InputDecoration(
hintText: "Enter Mobile No.",
counterText: "",
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(50),
borderSide: BorderSide(color: Colors.grey.shade300),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(50),
borderSide: const BorderSide(color: Colors.blue, width: 1.2),
),
filled: true,
fillColor: Colors.grey.shade100,
),
validator: (value) {
if (value == null || value.isEmpty) {
return "Please enter mobile number";
} else if (value.length != 10) {
return "Enter valid 10-digit number";
}
return null;
},
),
const SizedBox(height: 20),
/// 🔹 Continue Button (Always visible)
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0086F1),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
),
onPressed: () {
if (_formKey.currentState!.validate()) {
FocusScope.of(context).unfocus();
// TODO: Add API call here
}
},
child: const Text(
"Continue",
style: TextStyle(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.w600,
),
),
),
),
],
),
),
),
],
),
),
),
);
},
),
),
],
),
);
}
}
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
import 'package:gen_service/Notifiers/HelpAndComplaintProvider.dart';
import 'package:gen_service/Screens/HelpAndComplaintScreens/jobcardListScreen.dart';
import 'package:gen_service/Screens/HelpAndComplaintScreens/serviceListScreen.dart';
import 'package:provider/provider.dart';
import 'package:razorpay_flutter/razorpay_flutter.dart';
import '../../Notifiers/PayAmountProvider.dart';
import '../../Utility/AppColors.dart';
import '../../Utility/CustomSnackbar.dart';
import '../../Utility/SharedpreferencesService.dart';
class ComplaintDetailsScreen extends StatefulWidget {
final accId;
final sessionId;
final complaintId;
const ComplaintDetailsScreen({
super.key,
required this.accId,
required this.sessionId,
required this.complaintId,
});
@override
State<ComplaintDetailsScreen> createState() => _ComplaintDetailsScreenState();
}
class _ComplaintDetailsScreenState extends State<ComplaintDetailsScreen> {
late Razorpay _razorpay;
bool? isSuccess;
var paymentMethod = "";
var User_contact = "0";
bool bottomSheetButtonClicked = false;
final prefs = SharedPreferencesService.instance;
@override
void initState() {
// TODO: implement initState
super.initState();
_razorpay = Razorpay();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
final provider = Provider.of<HelpAndComplaintProvider>(
context,
listen: false,
);
provider.fetchComplaintDetailsAPI(
widget.accId,
widget.sessionId,
widget.complaintId,
);
});
}
//_________________________________________________________
void _handlePaymentSuccess(PaymentSuccessResponse response) {
setState(() async {
final provider = Provider.of<PayAmountProvider>(context, listen: false);
await provider.getPaymentStatus(
sessionId: widget.sessionId,
empId: widget.accId,
razorpayOrderId: response.orderId.toString(),
);
final data = provider.statusResponse;
// Navigator.push(
// context,
// MaterialPageRoute(builder: (context) => PaymentSuccessFaillScreen(
// total: "${data?.amount}",
// date: "${data?.date}",
// payMode: "UPI",
// status: "Success",
// )),
// );
_razorpay.clear();
CustomSnackBar.showSuccess(
context: context,
message: data?.message ?? "Payment Success!",
);
// buttonLoading = false;
});
}
void _handlePaymentError(PaymentFailureResponse response) {
setState(() async {
CustomSnackBar.showError(
context: context,
message: "Payment failed, please try again.",
);
});
_razorpay.clear();
CustomSnackBar.showError(
context: context,
message: "Payment failed, please try again.",
);
}
void _handleExternalWallet(ExternalWalletResponse response) {
_razorpay.clear();
}
Future<void> payAmountFunction(String amount) async {
try {
final provider = Provider.of<PayAmountProvider>(context, listen: false);
await provider.payAmount(
sessionId: widget.sessionId,
empId: widget.accId,
amount: amount,
refType: "Gen Service",
refId: "1",
);
final data = provider.payResponse;
if (data != null) {
if (data.error == "0") {
openCheckout(data.orderId, data.razorKey!);
} else {
CustomSnackBar.showError(
context: context,
message: "${data.message}",
);
debugPrint("❌ Could not Complete Payment: ${data.message}");
}
} else {
debugPrint("❌ No response received from PayAmount API");
}
} catch (e) {
debugPrint("❌ 'Error occurred: $e'");
}
}
//razorpay payments__________________________________________________________
void openCheckout(razorPayOrderId, String razorpayKey) async {
final String? mobNumber = await prefs.getString("mob_number");
_razorpay.on(Razorpay.EVENT_PAYMENT_SUCCESS, _handlePaymentSuccess);
_razorpay.on(Razorpay.EVENT_PAYMENT_ERROR, _handlePaymentError);
_razorpay.on(Razorpay.EVENT_EXTERNAL_WALLET, _handleExternalWallet);
// _buildCheckWidget();
Map<String, dynamic> options = {
'key': razorpayKey,
'amount': int.parse("${((0) * 100).round()}"),
'name': 'Gen Service',
'order_id': razorPayOrderId,
'description': "Bill",
'currency': 'INR',
'method': 'upi',
'prefill': {'contact': mobNumber, 'email': ''},
};
// print(options);
try {
_razorpay.open(options);
} catch (e, s) {
// FirebaseCrashlytics.instance.log('Error occurred: $e');
// FirebaseCrashlytics.instance.recordError(e, s);
debugPrint(e.toString());
}
}
void verifyPayment(String orderId) {
isSuccess = true;
setState(() {
// toast(context, "Order Placed Successfully");
// print("Verify Payment");
});
_razorpay.clear();
}
// void onError(CFErrorResponse errorResponse, String orderId) {
// isSuccess = false;
// setState(() {
// // print(errorResponse.getMessage());
// // print("Error while making payment");
// });
// }
@override
Widget build(BuildContext context) {
return Consumer<HelpAndComplaintProvider>(
builder: (context, provider, child) {
final isLoading = provider.isLoading;
final error = provider.errorMessage;
final data = provider.compDetailsResponse;
final complaintData = data?.complaintDetails?.firstOrNull;
final jobCardsData = data?.jobCardList?.firstOrNull;
final serviceData = data?.serviceDetails?.firstOrNull;
List<String> headings = ["Generator", "Description", "Complaint Note"];
List<String> subHeadings = [
complaintData?.productName ?? "-",
complaintData?.complaintDesc ?? "-",
complaintData?.complaintNote ?? "-",
];
List<String> serviceHeadings = [
"Date",
"In/Out Time",
"Running Hrs.",
"FSR File",
"FSR Number",
"Feedback",
];
List<String> serviceSubHeadings = [
serviceData?.date ?? "-",
serviceData?.inOrOutTime ?? "-",
serviceData?.runningHrs ?? "-",
serviceData?.fsrExt ?? "-",
serviceData?.fsrNo ?? "-",
serviceData?.feedback ?? "-",
];
if (isLoading) {
return const Scaffold(
backgroundColor: AppColors.backgroundRegular,
body: Center(
child: CircularProgressIndicator(color: AppColors.buttonColor),
),
);
}
if (error != null) {
return Scaffold(
backgroundColor: AppColors.backgroundRegular,
body: Center(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Error Icon
Container(
width: 120,
height: 120,
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
shape: BoxShape.circle,
),
child: const Icon(
Icons.error_outline_rounded,
size: 60,
color: Colors.red,
),
),
const SizedBox(height: 24),
// Error Title
const Text(
"Oops! Something went wrong",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Colors.black87,
fontFamily: "Poppins",
),
),
const SizedBox(height: 12),
// Error Message
Text(
error,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
fontFamily: "Poppins",
height: 1.4,
),
),
const SizedBox(height: 32),
// Retry Button
ElevatedButton.icon(
onPressed: () async {
// Show loading state
setState(() {});
await Future.delayed(const Duration(milliseconds: 300));
// Retry fetching data
final provider = Provider.of<HelpAndComplaintProvider>(
context,
listen: false,
);
await provider.fetchComplaintDetailsAPI(
widget.accId,
widget.sessionId,
widget.complaintId,
);
},
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.buttonColor,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25),
),
elevation: 2,
),
icon: const Icon(Icons.refresh_rounded, size: 20),
label: const Text(
"Try Again",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
fontFamily: "Poppins",
),
),
),
const SizedBox(height: 16),
// Alternative Action
TextButton(
onPressed: () {
// Go back or navigate to home
Navigator.maybePop(context);
},
child: const Text(
"Go Back",
style: TextStyle(
fontSize: 14,
color: Colors.grey,
fontFamily: "Poppins",
),
),
),
],
),
),
),
);
}
if (data == null) {
return SafeArea(
maintainBottomViewPadding: true,
top: false,
bottom: Platform.isIOS ? false : true,
child: const Scaffold(
backgroundColor: AppColors.backgroundRegular,
body: Center(child: Text("No data found.")),
),
);
}
return SafeArea(
maintainBottomViewPadding: true,
top: false,
bottom: Platform.isIOS ? false : true,
child: RefreshIndicator.adaptive(
color: AppColors.amountText,
onRefresh: () async {
await Future.delayed(const Duration(milliseconds: 600));
},
child: Scaffold(
backgroundColor: AppColors.backgroundRegular,
body: CustomScrollView(
slivers: [
SliverAppBar(
stretch: true,
pinned: true,
expandedHeight: 175,
backgroundColor: AppColors.backgroundRegular,
elevation: 0,
leading: Container(),
toolbarHeight: 0,
collapsedHeight: 0,
flexibleSpace: FlexibleSpaceBar(
stretchModes: const [StretchMode.zoomBackground],
background: Container(
decoration: BoxDecoration(
gradient: AppColors.balanceBarGradientA,
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 20,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 0,
vertical: 20,
),
child: SizedBox(
child: Row(
mainAxisAlignment:
MainAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.center,
children: [
InkResponse(
onTap: () {
HapticFeedback.selectionClick();
Navigator.pop(context, true);
},
child: SvgPicture.asset(
"assets/svg/appbar_back.svg",
height: 25,
),
),
SizedBox(width: 10),
Expanded(
flex: 4,
child: InkResponse(
onTap: () {
HapticFeedback.selectionClick();
Navigator.pop(context, true);
},
child: Text(
"Complaint Details",
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: TextStyle(
fontSize: 16,
color: Colors.white,
height: 1.1,
),
),
),
),
],
),
),
),
const SizedBox(height: 20),
Row(
children: [
Expanded(
flex: 5,
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
RichText(
text: TextSpan(
style: const TextStyle(
fontFamily: 'Poppins',
color: Color(0xFF48F3FF),
fontSize: 12,
),
children: [
TextSpan(
text: "#${complaintData?.id!}",
),
TextSpan(
text:
(complaintData?.complaintName??"").isNotEmpty
? " | ${complaintData?.complaintName!}"
: "",
),
],
),
),
Text(
"${complaintData?.registredDate??""}",
maxLines: 2,
style: const TextStyle(
color: Colors.white,
fontSize: 14,
),
),
],
),
),
Expanded(
flex: 2,
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 15,
vertical: 7,
),
decoration: BoxDecoration(
color:
complaintData?.openStatus == "Open"
? AppColors.successBG
: AppColors.yellowBG,
borderRadius: BorderRadius.circular(
10,
),
),
child: Center(
child: Text(
"${complaintData?.openStatus}",
style: TextStyle(
fontFamily: "Poppins",
fontSize: 14,
fontWeight: FontWeight.w400,
color:
complaintData?.openStatus ==
"Open"
? AppColors.success
: AppColors.normalText,
),
),
),
),
),
],
),
],
),
),
),
),
),
),
SliverToBoxAdapter(
child: Container(
color: Color(0xFF4076FF),
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 8,
),
decoration: const BoxDecoration(
color: AppColors.backgroundRegular,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (data.jobCardList!.isNotEmpty) ...[
sectionHeading("Job Cards"),
Container(
margin: const EdgeInsets.all(5),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFFFFFFFF),
borderRadius: BorderRadius.circular(16),
border: Border.all(
width: 1.1,
color: AppColors.buttonColor,
),
),
child: Column(
children: [
Row(
children: [
Expanded(
flex: 5,
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
"${jobCardsData?.date}",
maxLines: 2,
style: const TextStyle(
color: AppColors.subtitleText,
fontSize: 12,
),
),
SizedBox(height: 4),
Row(
mainAxisAlignment:
MainAxisAlignment.start,
children: [
Text(
"Job Card",
style: TextStyle(
fontSize: 14,
color:
AppColors
.nearDarkText,
),
),
InkResponse(
onTap:
bottomSheetButtonClicked
? null
: () {
HapticFeedback.mediumImpact();
setState(() {
bottomSheetButtonClicked =
true;
});
provider.fetchJobCardProductDetails(
widget.accId,
widget
.sessionId,
jobCardsData!
.id,
);
Future.delayed(
Duration(
milliseconds:
600,
),
() {
_showJobCardProductSheet(
context,
);
},
);
},
child: Text(
" ⓘ View Details",
style: TextStyle(
fontSize: 14,
color:
AppColors
.buttonColor,
),
),
),
],
),
],
),
),
Expanded(
flex: 2,
child: Text(
"₹${jobCardsData?.totalPrice}",
textAlign: TextAlign.right,
style: TextStyle(
fontFamily: "Poppins",
fontSize: 14,
fontWeight: FontWeight.w400,
color: AppColors.nearDarkText,
),
),
),
],
),
Divider(
thickness: 0.3,
color: AppColors.subtitleText,
),
InkResponse(
onTap: ()=> _openPaymentSheet(context, jobCardsData!.totalPrice.toString()),
child: Container(
padding: EdgeInsets.symmetric(
vertical: 16,
horizontal: 10,
),
decoration: BoxDecoration(
color: AppColors.buttonColor,
borderRadius: BorderRadius.circular(
25,
),
),
child: Center(
child: Text(
"Pay Now",
style: TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
),
),
],
),
),
],
if (data.complaintDetails!.isNotEmpty) ...[
sectionHeading("Complaint Details"),
Container(
margin: const EdgeInsets.all(5),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFFFFFFFF),
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: [
...List.generate(headings.length, (i) {
return Padding(
padding: const EdgeInsets.symmetric(
vertical: 3,
),
child: Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
headings[i] ?? "-",
style: const TextStyle(
fontSize: 14,
color: AppColors.subtitleText,
),
),
),
Expanded(
child: Text(
subHeadings[i].isEmpty
? "-"
: subHeadings[i] ?? "-",
maxLines: 3,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.end,
style: const TextStyle(
fontSize: 14,
color: AppColors.nearDarkText,
),
),
),
],
),
);
}),
],
),
),
],
if (data.serviceDetails!.isNotEmpty) ...[
sectionHeading("Service Details"),
Container(
margin: const EdgeInsets.all(5),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFFFFFFFF),
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: [
...List.generate(serviceHeadings.length, (
i,
) {
return Padding(
padding: const EdgeInsets.symmetric(
vertical: 3,
),
child: Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
serviceHeadings[i] ?? "-",
style: const TextStyle(
fontSize: 14,
color: AppColors.subtitleText,
),
),
),
Expanded(
child: Text(
serviceSubHeadings[i].isEmpty
? "-"
: serviceSubHeadings[i] ??
"-",
maxLines: 3,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.end,
style: const TextStyle(
fontSize: 14,
color: AppColors.nearDarkText,
),
),
),
],
),
);
}),
SizedBox(
height: 5,
),
Container(
padding: EdgeInsets.symmetric(
horizontal: 0,
vertical: 15,
),
decoration: BoxDecoration(
color: Color(0xFFE8F6FF),
borderRadius: BorderRadius.circular(16),
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 1,
child: CircleAvatar(
backgroundImage:
serviceData?.profileImg ==
"https://erp.gengroup.in/"
? AssetImage(
"assets/images/user_img.png",
)
: NetworkImage(
"${serviceData?.profileImg}",
),
),
),
Expanded(
flex: 3,
child: SizedBox(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
"${serviceData?.empName}",
maxLines: 2,
overflow:
TextOverflow.ellipsis,
style: TextStyle(
color:
AppColors
.nearDarkText,
fontSize: 14,
),
),
Text(
"${serviceData?.techRoleName}",
maxLines: 1,
overflow:
TextOverflow.ellipsis,
style: TextStyle(
color:
AppColors
.subtitleText,
fontSize: 12,
),
),
],
),
),
),
Expanded(
flex: 3,
child: SizedBox(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
provider.starStates.length,
(index) => InkWell(
onTap: () {
provider.rating = index + 1;
for (int i = 0;
i < provider.starStates.length;
i++) {
provider.starStates[i] = i <= index;
}
provider.notifyListeners();
provider.updateRatingForTechnician(widget.accId,widget.sessionId,widget.complaintId,provider.rating);
},
child: Row(
children: [
Icon(
Icons.star_rate_rounded,
color: provider.starStates[index]
? Color(0xffFFB703)
: Color(0xffCECECE),
size: 25,
),
],
),
),
),
),
Text(
"Your Rating",
maxLines: 1,
overflow:
TextOverflow.ellipsis,
style: TextStyle(
color:
AppColors
.subtitleText,
fontSize: 12,
),
),
],
),
),
),
],
),
),
],
),
),
],
],
),
),
),
),
],
),
),
),
);
},
);
}
Widget sectionHeading(text) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
text,
style: TextStyle(color: AppColors.nearDarkText, fontSize: 14),
),
if (text != "Complaint Details") ...[
InkResponse(
onTap: () async {
var redirectScreen;
switch (text) {
case "Job Cards":
redirectScreen = Jobcardlistscreen(
accId: widget.accId,
sessionId: widget.sessionId,
complaintId: widget.complaintId,
);
break;
case "Service Details":
redirectScreen = serviceListScreen(
accId: widget.accId,
sessionId: widget.sessionId,
complaintId: widget.complaintId,
);
break;
default:
break;
}
await Navigator.push(
context,
MaterialPageRoute(builder: (context) => redirectScreen),
);
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Text(
"See All",
style: TextStyle(color: AppColors.buttonColor, fontSize: 14),
),
),
),
],
],
),
);
}
Future<void> _showJobCardProductSheet(context) {
return showModalBottomSheet(
useSafeArea: true,
isDismissible: true,
isScrollControlled: true,
showDragHandle: true,
backgroundColor: Colors.white,
enableDrag: true,
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setState) {
return SafeArea(
child: Consumer<HelpAndComplaintProvider>(
builder: (context, provider, child) {
final jobcardProductData = provider.jobCardResponse;
final list = jobcardProductData?.jobCardProducts;
final data = provider.compDetailsResponse;
final jobCardsData = data!.jobCardList?.firstOrNull;
return Container(
margin: EdgeInsets.only(
bottom: 15,
left: 15,
right: 15,
top: 10,
),
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${jobCardsData?.date}",
style: TextStyle(
color: AppColors.subtitleText,
fontSize: 12,
),
),
Text(
"Job Card Details",
style: TextStyle(
color: AppColors.nearDarkText,
fontSize: 14,
),
),
],
),
),
SizedBox(
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
"Total Amount",
style: TextStyle(
color: AppColors.subtitleText,
fontSize: 12,
),
),
Text(
"₹${jobCardsData?.totalPrice}",
style: TextStyle(
color: AppColors.buttonColor,
fontSize: 14,
),
),
],
),
),
],
),
Divider(
thickness: 0.3,
color: AppColors.subtitleText,
),
ListView.builder(
shrinkWrap: true,
itemCount: list?.length,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int j) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 5),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
"${j + 1}. ${list?[j].partName}",
style: TextStyle(
fontSize: 14,
color: AppColors.nearDarkText,
),
),
Text(
"₹${list?[j].price} * ${list?[j].qty}",
style: TextStyle(
fontSize: 14,
color: AppColors.nearDarkText,
),
),
Text(
"₹${list?[j].totalPrice}",
style: TextStyle(
fontSize: 14,
color: AppColors.nearDarkText,
),
),
],
),
);
},
),
],
),
),
);
},
),
);
},
);
},
).whenComplete(() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
setState(() {
bottomSheetButtonClicked = false;
});
});
});
}
void _openPaymentSheet(BuildContext context, String totalAmountStr) {
TextEditingController amountController = TextEditingController();
bool isPartPayment = false;
final double totalAmount = double.tryParse(totalAmountStr) ?? 0;
showModalBottomSheet(
isScrollControlled: true,
backgroundColor: Colors.white,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setState) {
return Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
top: 16,
left: 16,
right: 16,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Handle Bar
Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(4),
),
),
),
const SizedBox(height: 16),
// Title
const Text(
"Balance Amount Bill",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
fontFamily: "Poppins",
),
),
const SizedBox(height: 6),
Divider(),
const SizedBox(height: 10),
// Pay Total Option
GestureDetector(
onTap: () {
setState(() => isPartPayment = false);
},
child: Row(
children: [
Radio<bool>(
value: false,
groupValue: isPartPayment,
onChanged: (v) => setState(() => isPartPayment = v!),
activeColor: const Color(0xFF008CDE),
),
// Radio<bool>(
// value: false,
// groupValue: isPartPayment,
// onChanged: (v) => setState(() => isPartPayment = v!),
// ),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Pay Total",
style: TextStyle(
fontSize: 14,
fontFamily: "Poppins",
fontWeight: FontWeight.w400,
),
),
Text(
"Avoid late payment fees.",
style: TextStyle(
color: Color(0xff5FBB54),
fontSize: 12,
fontFamily: "Poppins",
),
),
],
),
const Spacer(),
Text(
"₹${totalAmount.toStringAsFixed(0)}",
style: const TextStyle(
fontSize: 14,
fontFamily: "Poppins",
fontWeight: FontWeight.w400,
),
),
],
),
),
const SizedBox(height: 10),
// Part Payment Option
GestureDetector(
onTap: () {
setState(() => isPartPayment = true);
},
child: Row(
children: [
Radio<bool>(
value: true,
groupValue: isPartPayment,
onChanged: (v) => setState(() => isPartPayment = v!),
activeColor: const Color(0xFF008CDE),
),
const Text(
"Part Payment",
style: TextStyle(
fontSize: 14,
fontFamily: "Poppins",
fontWeight: FontWeight.w400,
),
),
const SizedBox(width: 24),
Expanded(
child: Container(
height: 50,
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(12),
),
child: TextFormField(
controller: amountController,
enabled: isPartPayment,
style: const TextStyle(
fontSize: 14,
fontFamily: "Poppins",
color: Colors.black87,
),
keyboardType: TextInputType.number,
decoration: const InputDecoration(
hintText: "Enter amount",
hintStyle: TextStyle(
fontSize: 14,
fontFamily: "Poppins",
color: Colors.grey,
),
border: InputBorder.none,
),
),
),
),
],
),
),
const SizedBox(height: 6),
Divider(),
const SizedBox(height: 6),
// Continue Button
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
double enteredAmount =
isPartPayment
? double.tryParse(amountController.text) ?? 0
: totalAmount;
if (enteredAmount <= 0) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Please enter a valid amount"),
),
);
return;
}
if (isPartPayment && enteredAmount > totalAmount) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
"Entered amount cannot exceed total amount",
),
),
);
return;
}
Navigator.pop(context);
// Pass selected amount to your payAmountFunction
payAmountFunction(enteredAmount.toStringAsFixed(2));
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF008CDE),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
padding: const EdgeInsets.symmetric(vertical: 16),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 22),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
"Continue Payment",
style: TextStyle(
fontFamily: "Poppins",
color: Colors.white,
fontSize: 16,
),
),
SvgPicture.asset(
"assets/svg/continue_ic.svg",
color: Colors.white,
height: 25,
width: 25,
),
],
),
),
),
),
const SizedBox(height: 16),
],
),
);
},
);
},
);
}
}
......@@ -5,6 +5,7 @@ import 'package:gen_service/Screens/HelpAndComplaintScreens/SelectOrderHelpScree
import 'package:gen_service/Screens/ProfileScreen.dart';
import 'package:provider/provider.dart';
import '../../Utility/AppColors.dart';
import 'ComplaintDetailsScreen.dart';
class ComplaintListScreen extends StatefulWidget {
final String accId;
......@@ -27,8 +28,10 @@ class _ComplaintListScreenState extends State<ComplaintListScreen> {
void initState() {
super.initState();
Future.microtask(() {
final provider =
Provider.of<HelpAndComplaintProvider>(context, listen: false);
final provider = Provider.of<HelpAndComplaintProvider>(
context,
listen: false,
);
provider.fetchComplaintsList(
accId: widget.accId,
sessionId: widget.sessionId,
......@@ -57,28 +60,133 @@ class _ComplaintListScreenState extends State<ComplaintListScreen> {
return Scaffold(
backgroundColor: AppColors.backgroundRegular,
body: Center(
child: Text(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Error Icon
Container(
width: 120,
height: 120,
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
shape: BoxShape.circle,
),
child: const Icon(
Icons.error_outline_rounded,
size: 60,
color: Colors.red,
),
),
const SizedBox(height: 24),
// Error Title
const Text(
"Oops! Something went wrong",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Colors.black87,
fontFamily: "Poppins",
),
),
const SizedBox(height: 12),
// Error Message
Text(
error,
style: const TextStyle(color: Colors.red, fontSize: 16),
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
fontFamily: "Poppins",
height: 1.4,
),
),
const SizedBox(height: 32),
// Retry Button
ElevatedButton.icon(
onPressed: () async {
// Show loading state
setState(() {});
await Future.delayed(const Duration(milliseconds: 300));
// Retry fetching data
final provider = Provider.of<HelpAndComplaintProvider>(
context,
listen: false,
);
}
provider.fetchComplaintsList(
accId: widget.accId,
sessionId: widget.sessionId,
);
},
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.buttonColor,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25),
),
elevation: 2,
),
icon: const Icon(Icons.refresh_rounded, size: 20),
label: const Text(
"Try Again",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
fontFamily: "Poppins",
),
),
),
const SizedBox(height: 16),
// Alternative Action
TextButton(
onPressed: () {
// Go back or navigate to home
Navigator.pop(context);
},
child: const Text(
"Go Back",
style: TextStyle(
fontSize: 14,
color: Colors.grey,
fontFamily: "Poppins",
),
),
),
],
),
),
),
);
}
if (data == null) {
return const Scaffold(
backgroundColor: AppColors.backgroundRegular,
body: Center(
child: Text("No data found."),
),
body: Center(child: Text("No data found.")),
);
}
// Separate open and closed complaints
final openComplaints = data.complaintList!
final openComplaints =
data.complaintList!
.where((c) => c.openStatus?.toLowerCase() == "open")
.toList();
final closedComplaints = data.complaintList!
final closedComplaints =
data.complaintList!
.where((c) => c.openStatus?.toLowerCase() == "closed")
.toList();
......@@ -113,12 +221,12 @@ class _ComplaintListScreenState extends State<ComplaintListScreen> {
StretchMode.blurBackground,
],
background: Container(
decoration:
const BoxDecoration(
gradient: AppColors.commonAppBarGradient
),
decoration: const BoxDecoration(color: AppColors.primary),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
......@@ -151,9 +259,12 @@ class _ComplaintListScreenState extends State<ComplaintListScreen> {
SliverToBoxAdapter(
child: Container(
padding: const EdgeInsets.only(top: 1),
color: AppColors.backgroundBottom,
color: AppColors.primary,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 20,
),
decoration: const BoxDecoration(
color: AppColors.backgroundRegular,
borderRadius: BorderRadius.only(
......@@ -169,7 +280,13 @@ class _ComplaintListScreenState extends State<ComplaintListScreen> {
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context)=> SelectOrderHelpScreen(accId: widget.accId, sessionId: widget.sessionId))
MaterialPageRoute(
builder:
(context) => SelectOrderHelpScreen(
accId: widget.accId,
sessionId: widget.sessionId,
),
),
).then((_) async {
await provider.fetchComplaintsList(
accId: widget.accId,
......@@ -179,7 +296,9 @@ class _ComplaintListScreenState extends State<ComplaintListScreen> {
},
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 16, horizontal: 14),
vertical: 16,
horizontal: 14,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(14),
......@@ -247,6 +366,8 @@ class _ComplaintListScreenState extends State<ComplaintListScreen> {
const SizedBox(height: 8),
...openComplaints.map(
(c) => ComplaintCard(
accId: widget.accId,
sessionId: widget.sessionId,
title: c.complaintName ?? "-",
id: c.id ?? "-",
product: c.productName ?? "",
......@@ -272,6 +393,8 @@ class _ComplaintListScreenState extends State<ComplaintListScreen> {
const SizedBox(height: 8),
...closedComplaints.map(
(c) => ComplaintCard(
accId: widget.accId,
sessionId: widget.sessionId,
title: c.complaintName ?? "-",
id: c.id ?? "",
product: c.productName ?? "",
......@@ -294,7 +417,8 @@ class _ComplaintListScreenState extends State<ComplaintListScreen> {
/// Reusable Complaint Item Card
class ComplaintCard extends StatelessWidget {
final String accId;
final String sessionId;
final String title;
final String id;
final String product;
......@@ -310,24 +434,40 @@ class ComplaintCard extends StatelessWidget {
required this.status,
required this.date,
required this.engModel,
required this.accId,
required this.sessionId,
});
@override
Widget build(BuildContext context) {
return Container(
return InkResponse(
onTap: () async {
await Navigator.push(
context,
MaterialPageRoute(
builder:
(context) => ComplaintDetailsScreen(
accId: accId,
sessionId: sessionId,
complaintId: id,
),
),
);
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 6),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
// border: Border.all(color: Colors.grey.shade200),
// boxShadow: [
// BoxShadow(
// color: Colors.grey.shade200,
// blurRadius: 4,
// offset: const Offset(0, 2),
// )
// ],
border: Border.all(color: Colors.grey.shade200),
boxShadow: [
BoxShadow(
color: Colors.grey.shade200,
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: [
......@@ -370,13 +510,15 @@ class ComplaintCard extends StatelessWidget {
),
),
const SizedBox(height: 2),
],
),
Container(
padding: EdgeInsets.symmetric(horizontal: 15, vertical: 7),
decoration: BoxDecoration(
color: status == "Open" ? AppColors.successBG : AppColors.yellowBG,
color:
status == "Open"
? AppColors.successBG
: AppColors.yellowBG,
borderRadius: BorderRadius.circular(10),
),
child: Text(
......@@ -385,7 +527,10 @@ class ComplaintCard extends StatelessWidget {
fontFamily: "Poppins",
fontSize: 14,
fontWeight: FontWeight.w400,
color: status == "Open" ? AppColors.success : AppColors.normalText,
color:
status == "Open"
? AppColors.success
: AppColors.normalText,
),
),
),
......@@ -421,6 +566,7 @@ class ComplaintCard extends StatelessWidget {
),
],
),
),
);
}
}
......@@ -58,9 +58,115 @@ class _SelectOrderHelpScreenState extends State<SelectOrderHelpScreen> {
return Scaffold(
backgroundColor: AppColors.backgroundRegular,
body: Center(
child: Text(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Error Icon
Container(
width: 120,
height: 120,
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
shape: BoxShape.circle,
),
child: const Icon(
Icons.error_outline_rounded,
size: 60,
color: Colors.red,
),
),
const SizedBox(height: 24),
// Error Title
const Text(
"Oops! Something went wrong",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Colors.black87,
fontFamily: "Poppins",
),
),
const SizedBox(height: 12),
// Error Message
Text(
error,
style: const TextStyle(color: Colors.red, fontSize: 16),
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
fontFamily: "Poppins",
height: 1.4,
),
),
const SizedBox(height: 32),
// Retry Button
ElevatedButton.icon(
onPressed: () async {
// Show loading state
setState(() {});
await Future.delayed(const Duration(milliseconds: 300));
// Retry fetching data
final provider = Provider.of<HelpAndComplaintProvider>(
context,
listen: false,
);
provider.fetchGeneratorList(
accId: widget.accId,
sessionId: widget.sessionId,
);
},
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.buttonColor,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25),
),
elevation: 2,
),
icon: const Icon(Icons.refresh_rounded, size: 20),
label: const Text(
"Try Again",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
fontFamily: "Poppins",
),
),
),
const SizedBox(height: 16),
// Alternative Action
TextButton(
onPressed: () {
// Go back or navigate to home
Navigator.pop(context);
},
child: const Text(
"Go Back",
style: TextStyle(
fontSize: 14,
color: Colors.grey,
fontFamily: "Poppins",
),
),
),
],
),
),
),
);
......
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
import 'package:provider/provider.dart';
import 'package:razorpay_flutter/razorpay_flutter.dart';
import '../../Notifiers/HelpAndComplaintProvider.dart';
import '../../Notifiers/PayAmountProvider.dart';
import '../../Notifiers/serviceAndJobCardListProvier.dart';
import '../../Utility/AppColors.dart';
import '../../Utility/CustomSnackbar.dart';
import '../../Utility/SharedpreferencesService.dart';
class Jobcardlistscreen extends StatefulWidget {
final accId;
final sessionId;
final complaintId;
const Jobcardlistscreen({
super.key,
required this.accId,
required this.sessionId,
required this.complaintId,
});
@override
State<Jobcardlistscreen> createState() => _JobcardlistscreenState();
}
class _JobcardlistscreenState extends State<Jobcardlistscreen> {
late Razorpay _razorpay;
bool? isSuccess;
var paymentMethod = "";
var User_contact = "0";
bool bottomSheetButtonClicked = false;
final prefs = SharedPreferencesService.instance;
@override
void initState() {
// TODO: implement initState
super.initState();
_razorpay = Razorpay();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
final provider = Provider.of<ServiceAndJobCardListProvider>(
context,
listen: false,
);
provider.fetchAllJobCardsListAPI(
widget.accId,
widget.sessionId,
widget.complaintId,
);
});
}
//_________________________________________________________
void _handlePaymentSuccess(PaymentSuccessResponse response) {
setState(() async {
final provider = Provider.of<PayAmountProvider>(context, listen: false);
await provider.getPaymentStatus(
sessionId: widget.sessionId,
empId: widget.accId,
razorpayOrderId: response.orderId.toString(),
);
final data = provider.statusResponse;
// Navigator.push(
// context,
// MaterialPageRoute(builder: (context) => PaymentSuccessFaillScreen(
// total: "${data?.amount}",
// date: "${data?.date}",
// payMode: "UPI",
// status: "Success",
// )),
// );
_razorpay.clear();
CustomSnackBar.showSuccess(
context: context,
message: data?.message ?? "Payment Success!",
);
// buttonLoading = false;
});
}
void _handlePaymentError(PaymentFailureResponse response) {
setState(() async {
CustomSnackBar.showError(
context: context,
message: "Payment failed, please try again.",
);
});
_razorpay.clear();
CustomSnackBar.showError(
context: context,
message: "Payment failed, please try again.",
);
}
void _handleExternalWallet(ExternalWalletResponse response) {
_razorpay.clear();
}
Future<void> payAmountFunction(String amount) async {
try {
final provider = Provider.of<PayAmountProvider>(context, listen: false);
await provider.payAmount(
sessionId: widget.sessionId,
empId: widget.accId,
amount: amount,
refType: "Gen Service",
refId: "1",
);
final data = provider.payResponse;
if (data != null) {
if (data.error == "0") {
openCheckout(data.orderId, data.razorKey!);
} else {
CustomSnackBar.showError(
context: context,
message: "${data.message}",
);
debugPrint("❌ Could not Complete Payment: ${data.message}");
}
} else {
debugPrint("❌ No response received from PayAmount API");
}
} catch (e) {
debugPrint("❌ 'Error occurred: $e'");
}
}
//razorpay payments__________________________________________________________
void openCheckout(razorPayOrderId, String razorpayKey) async {
final String? mobNumber = await prefs.getString("mob_number");
_razorpay.on(Razorpay.EVENT_PAYMENT_SUCCESS, _handlePaymentSuccess);
_razorpay.on(Razorpay.EVENT_PAYMENT_ERROR, _handlePaymentError);
_razorpay.on(Razorpay.EVENT_EXTERNAL_WALLET, _handleExternalWallet);
// _buildCheckWidget();
Map<String, dynamic> options = {
'key': razorpayKey,
'amount': int.parse("${((0) * 100).round()}"),
'name': 'Gen Service',
'order_id': razorPayOrderId,
'description': "Bill",
'currency': 'INR',
'method': 'upi',
'prefill': {'contact': mobNumber, 'email': ''},
};
// print(options);
try {
_razorpay.open(options);
} catch (e, s) {
// FirebaseCrashlytics.instance.log('Error occurred: $e');
// FirebaseCrashlytics.instance.recordError(e, s);
debugPrint(e.toString());
}
}
void verifyPayment(String orderId) {
isSuccess = true;
setState(() {
// toast(context, "Order Placed Successfully");
// print("Verify Payment");
});
_razorpay.clear();
}
// void onError(CFErrorResponse errorResponse, String orderId) {
// isSuccess = false;
// setState(() {
// // print(errorResponse.getMessage());
// // print("Error while making payment");
// });
// }
@override
Widget build(BuildContext context) {
return Consumer2<ServiceAndJobCardListProvider, HelpAndComplaintProvider>(
builder: (context, provider, hcProvider, child) {
final isLoading = provider.isLoading;
final error = provider.errorMessage;
final response = provider.allJobCardResponse;
final data = response?.jobCardList ?? [];
if (isLoading) {
return const Scaffold(
backgroundColor: AppColors.backgroundRegular,
body: Center(
child: CircularProgressIndicator(color: AppColors.buttonColor),
),
);
}
if (error != null) {
return Scaffold(
backgroundColor: AppColors.backgroundRegular,
body: Center(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Error Icon
Container(
width: 120,
height: 120,
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
shape: BoxShape.circle,
),
child: const Icon(
Icons.error_outline_rounded,
size: 60,
color: Colors.red,
),
),
const SizedBox(height: 24),
// Error Title
const Text(
"Oops! Something went wrong",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Colors.black87,
fontFamily: "Poppins",
),
),
const SizedBox(height: 12),
// Error Message
Text(
error,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
fontFamily: "Poppins",
height: 1.4,
),
),
const SizedBox(height: 32),
// Retry Button
ElevatedButton.icon(
onPressed: () async {
// Show loading state
setState(() {});
await Future.delayed(const Duration(milliseconds: 300));
// Retry fetching data
final provider = Provider.of<ServiceAndJobCardListProvider>(
context,
listen: false,
);
await provider.fetchAllJobCardsListAPI(
widget.accId,
widget.sessionId,
widget.complaintId,
);
},
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.buttonColor,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25),
),
elevation: 2,
),
icon: const Icon(Icons.refresh_rounded, size: 20),
label: const Text(
"Try Again",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
fontFamily: "Poppins",
),
),
),
const SizedBox(height: 16),
// Alternative Action
TextButton(
onPressed: () {
// Go back or navigate to home
Navigator.maybePop(context);
},
child: const Text(
"Go Back",
style: TextStyle(
fontSize: 14,
color: Colors.grey,
fontFamily: "Poppins",
),
),
),
],
),
),
),
);
}
if (data == null) {
return const Scaffold(
backgroundColor: AppColors.backgroundRegular,
body: Center(child: Text("No data found.")),
);
}
return RefreshIndicator.adaptive(
color: AppColors.amountText,
onRefresh: () async {
await Future.delayed(const Duration(milliseconds: 600));
},
child: Scaffold(
backgroundColor: AppColors.backgroundRegular,
body: CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
SliverAppBar(
stretch: true,
pinned: true,
expandedHeight: 75,
backgroundColor: AppColors.backgroundRegular,
elevation: 0,
// Remove shadow
automaticallyImplyLeading: false,
toolbarHeight: 0,
// Remove toolbar space
collapsedHeight: 0,
// Completely collapse to 0 height
flexibleSpace: FlexibleSpaceBar(
stretchModes: const [StretchMode.fadeTitle],
background: Container(
decoration: BoxDecoration(
gradient: AppColors.balanceBarGradientA,
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 20,
),
child: SizedBox(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
InkResponse(
onTap: () {
HapticFeedback.selectionClick();
Navigator.pop(context, true);
},
child: SvgPicture.asset(
"assets/svg/appbar_back.svg",
height: 25,
),
),
SizedBox(width: 10),
Expanded(
flex: 4,
child: InkResponse(
onTap: () {
HapticFeedback.selectionClick();
Navigator.pop(context, true);
},
child: Text(
"Job Cards List",
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: TextStyle(
fontSize: 16,
color: Colors.white,
height: 1.1,
),
),
),
),
],
),
),
),
),
),
),
),
SliverToBoxAdapter(
child: Container(
color: Color(0xFF4076FF),
child: Container(
decoration: const BoxDecoration(
color: AppColors.backgroundRegular,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 4),
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: data!.length,
itemBuilder: (context, j) {
return Container(
margin: const EdgeInsets.all(5),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFFFFFFFF),
borderRadius: BorderRadius.circular(16),
border: Border.all(
width: 1.1,
color: AppColors.buttonColor,
),
),
child: Column(
children: [
Row(
children: [
Expanded(
flex: 5,
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
data[j].date!,
maxLines: 2,
style: const TextStyle(
color: AppColors.subtitleText,
fontSize: 12,
),
),
SizedBox(height: 4),
Row(
mainAxisAlignment:
MainAxisAlignment.start,
children: [
Text(
"Job Card",
style: TextStyle(
fontSize: 14,
color:
AppColors
.nearDarkText,
),
),
InkResponse(
onTap: () {
hcProvider
.fetchJobCardProductDetails(
widget.accId,
widget.sessionId,
data[j]!.id,
);
Future.delayed(
Duration(
milliseconds: 400,
),
() {
_showJobCardProductSheet(
context,
data[j]!.date,
data[j]!.totalPrice
);
},
);
},
child: Text(
" ⓘ View Details",
style: TextStyle(
fontSize: 14,
color:
AppColors
.buttonColor,
),
),
),
],
),
],
),
),
Expanded(
flex: 2,
child: Text(
"₹${data[j].totalPrice}",
textAlign: TextAlign.right,
style: TextStyle(
fontFamily: "Poppins",
fontSize: 14,
fontWeight: FontWeight.w400,
color: AppColors.nearDarkText,
),
),
),
],
),
Divider(
thickness: 0.3,
color: AppColors.subtitleText,
),
InkResponse(
onTap: () => _openPaymentSheet(context, data[j].totalPrice.toString()),
child: Container(
padding: EdgeInsets.symmetric(
vertical: 16,
horizontal: 10,
),
decoration: BoxDecoration(
color: AppColors.buttonColor,
borderRadius: BorderRadius.circular(
25,
),
),
child: Center(
child: Text(
"Pay Now",
style: TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
),
),
],
),
);
},
),
],
),
),
),
),
],
),
),
);
},
);
}
Future<void> _showJobCardProductSheet(context,date,amount) {
return showModalBottomSheet(
useSafeArea: true,
isDismissible: true,
isScrollControlled: true,
showDragHandle: true,
backgroundColor: Colors.white,
enableDrag: true,
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setState) {
return SafeArea(
child: Consumer<HelpAndComplaintProvider>(
builder: (context, provider, child) {
final jobcardProductData = provider.jobCardResponse;
final list = jobcardProductData?.jobCardProducts;
final data = provider.compDetailsResponse;
final jobCardsData = data!.jobCardList?.firstOrNull;
return Container(
margin: EdgeInsets.only(
bottom: 15,
left: 15,
right: 15,
top: 10,
),
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${date}",
style: TextStyle(
color: AppColors.subtitleText,
fontSize: 12,
),
),
Text(
"Job Card Details",
style: TextStyle(
color: AppColors.nearDarkText,
fontSize: 14,
),
),
],
),
),
SizedBox(
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
"Total Amount",
style: TextStyle(
color: AppColors.subtitleText,
fontSize: 12,
),
),
Text(
"₹${amount}",
style: TextStyle(
color: AppColors.buttonColor,
fontSize: 14,
),
),
],
),
),
],
),
Divider(
thickness: 0.3,
color: AppColors.subtitleText,
),
ListView.builder(
shrinkWrap: true,
itemCount: list?.length,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int j) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 5),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
"${j + 1}. ${list?[j].partName}",
style: TextStyle(
fontSize: 14,
color: AppColors.nearDarkText,
),
),
Text(
"₹${list?[j].price} * ${list?[j].qty}",
style: TextStyle(
fontSize: 14,
color: AppColors.nearDarkText,
),
),
Text(
"₹${list?[j].totalPrice}",
style: TextStyle(
fontSize: 14,
color: AppColors.nearDarkText,
),
),
],
),
);
},
),
],
),
),
);
},
),
);
},
);
},
).whenComplete(() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
setState(() {});
});
});
}
void _openPaymentSheet(BuildContext context, String totalAmountStr) {
TextEditingController amountController = TextEditingController();
bool isPartPayment = false;
final double totalAmount = double.tryParse(totalAmountStr) ?? 0;
showModalBottomSheet(
isScrollControlled: true,
backgroundColor: Colors.white,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setState) {
return Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
top: 16,
left: 16,
right: 16,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Handle Bar
Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(4),
),
),
),
const SizedBox(height: 16),
// Title
const Text(
"Balance Amount Bill",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
fontFamily: "Poppins",
),
),
const SizedBox(height: 6),
Divider(),
const SizedBox(height: 10),
// Pay Total Option
GestureDetector(
onTap: () {
setState(() => isPartPayment = false);
},
child: Row(
children: [
Radio<bool>(
value: false,
groupValue: isPartPayment,
onChanged: (v) => setState(() => isPartPayment = v!),
activeColor: const Color(0xFF008CDE),
),
// Radio<bool>(
// value: false,
// groupValue: isPartPayment,
// onChanged: (v) => setState(() => isPartPayment = v!),
// ),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Pay Total",
style: TextStyle(
fontSize: 14,
fontFamily: "Poppins",
fontWeight: FontWeight.w400,
),
),
Text(
"Avoid late payment fees.",
style: TextStyle(
color: Color(0xff5FBB54),
fontSize: 12,
fontFamily: "Poppins",
),
),
],
),
const Spacer(),
Text(
"₹${totalAmount.toStringAsFixed(0)}",
style: const TextStyle(
fontSize: 14,
fontFamily: "Poppins",
fontWeight: FontWeight.w400,
),
),
],
),
),
const SizedBox(height: 10),
// Part Payment Option
GestureDetector(
onTap: () {
setState(() => isPartPayment = true);
},
child: Row(
children: [
Radio<bool>(
value: true,
groupValue: isPartPayment,
onChanged: (v) => setState(() => isPartPayment = v!),
activeColor: const Color(0xFF008CDE),
),
const Text(
"Part Payment",
style: TextStyle(
fontSize: 14,
fontFamily: "Poppins",
fontWeight: FontWeight.w400,
),
),
const SizedBox(width: 24),
Expanded(
child: Container(
height: 50,
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(12),
),
child: TextFormField(
controller: amountController,
enabled: isPartPayment,
style: const TextStyle(
fontSize: 14,
fontFamily: "Poppins",
color: Colors.black87,
),
keyboardType: TextInputType.number,
decoration: const InputDecoration(
hintText: "Enter amount",
hintStyle: TextStyle(
fontSize: 14,
fontFamily: "Poppins",
color: Colors.grey,
),
border: InputBorder.none,
),
),
),
),
],
),
),
const SizedBox(height: 6),
Divider(),
const SizedBox(height: 6),
// Continue Button
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
double enteredAmount =
isPartPayment
? double.tryParse(amountController.text) ?? 0
: totalAmount;
if (enteredAmount <= 0) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Please enter a valid amount"),
),
);
return;
}
if (isPartPayment && enteredAmount > totalAmount) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
"Entered amount cannot exceed total amount",
),
),
);
return;
}
Navigator.pop(context);
// Pass selected amount to your payAmountFunction
payAmountFunction(enteredAmount.toStringAsFixed(2));
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF008CDE),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
padding: const EdgeInsets.symmetric(vertical: 16),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 22),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
"Continue Payment",
style: TextStyle(
fontFamily: "Poppins",
color: Colors.white,
fontSize: 16,
),
),
SvgPicture.asset(
"assets/svg/continue_ic.svg",
color: Colors.white,
height: 25,
width: 25,
),
],
),
),
),
),
const SizedBox(height: 16),
],
),
);
},
);
},
);
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment