Commit 5ac94fe2 authored by Sai Srinivas's avatar Sai Srinivas
Browse files

New Screen and apis

parent df116895
......@@ -25,7 +25,7 @@ android {
applicationId = "in.webgrid.genrentals"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
minSdk = 23
targetSdk = 36
versionCode = flutter.versionCode
versionName = flutter.versionName
......
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:label="Gen Rentals"
android:name="${applicationName}"
......
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.4376 1.70711C13.8281 1.31658 13.8281 0.683417 13.4376 0.292893C13.0471 -0.0976311 12.4139 -0.097631 12.0234 0.292893L12.7305 1L13.4376 1.70711ZM-3.43156e-05 12.7305C-3.40162e-05 13.2828 0.447681 13.7305 0.999966 13.7305L9.99997 13.7305C10.5523 13.7305 11 13.2828 11 12.7305C11 12.1782 10.5523 11.7305 9.99997 11.7305L1.99997 11.7305L1.99997 3.7305C1.99997 3.17822 1.55225 2.7305 0.999965 2.7305C0.447681 2.7305 -3.46572e-05 3.17822 -3.42735e-05 3.7305L-3.43156e-05 12.7305ZM12.7305 1L12.0234 0.292893L0.292859 12.0234L0.999966 12.7305L1.70707 13.4376L13.4376 1.70711L12.7305 1Z" fill="#4CAF50"/>
<path d="M13.4376 1.70711C13.8281 1.31658 13.8281 0.683417 13.4376 0.292893C13.0471 -0.0976311 12.4139 -0.097631 12.0234 0.292893L12.7305 1L13.4376 1.70711ZM-3.43156e-05 12.7305C-3.40162e-05 13.2828 0.447681 13.7305 0.999966 13.7305L9.99997 13.7305C10.5523 13.7305 11 13.2828 11 12.7305C11 12.1782 10.5523 11.7305 9.99997 11.7305L1.99997 11.7305L1.99997 3.7305C1.99997 3.17822 1.55225 2.7305 0.999965 2.7305C0.447681 2.7305 -3.46572e-05 3.17822 -3.42735e-05 3.7305L-3.43156e-05 12.7305ZM12.7305 1L12.0234 0.292893L0.292859 12.0234L0.999966 12.7305L1.70707 13.4376L13.4376 1.70711L12.7305 1Z" fill="#F00000"/>
</svg>
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.292893 12.0234C-0.0976311 12.4139 -0.0976311 13.0471 0.292893 13.4376C0.683418 13.8281 1.31658 13.8281 1.70711 13.4376L1 12.7305L0.292893 12.0234ZM13.7305 0.999965C13.7305 0.44768 13.2828 -3.50516e-05 12.7305 -3.53045e-05L3.7305 -3.45037e-05C3.17822 -3.48408e-05 2.7305 0.447681 2.7305 0.999965C2.7305 1.55225 3.17822 1.99996 3.7305 1.99997L11.7305 1.99996L11.7305 9.99996C11.7305 10.5522 12.1782 11 12.7305 11C13.2828 11 13.7305 10.5522 13.7305 9.99997L13.7305 0.999965ZM1 12.7305L1.70711 13.4376L13.4376 1.70707L12.7305 0.999965L12.0234 0.292858L0.292893 12.0234L1 12.7305Z" fill="#ED3424"/>
<path d="M0.292893 12.0234C-0.0976311 12.4139 -0.0976311 13.0471 0.292893 13.4376C0.683418 13.8281 1.31658 13.8281 1.70711 13.4376L1 12.7305L0.292893 12.0234ZM13.7305 0.999965C13.7305 0.44768 13.2828 -3.50516e-05 12.7305 -3.53045e-05L3.7305 -3.45037e-05C3.17822 -3.48408e-05 2.7305 0.447681 2.7305 0.999965C2.7305 1.55225 3.17822 1.99996 3.7305 1.99997L11.7305 1.99996L11.7305 9.99996C11.7305 10.5522 12.1782 11 12.7305 11C13.2828 11 13.7305 10.5522 13.7305 9.99997L13.7305 0.999965ZM1 12.7305L1.70711 13.4376L13.4376 1.70707L12.7305 0.999965L12.0234 0.292858L0.292893 12.0234L1 12.7305Z" fill="#4CAF50"/>
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_591_316)">
<mask id="mask0_591_316" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="16">
<path d="M16 0H0V16H16V0Z" fill="white"/>
</mask>
<g mask="url(#mask0_591_316)">
<path d="M14.6666 8.66699V10.0003C14.6666 13.3337 13.3333 14.667 9.99992 14.667H5.99992C2.66659 14.667 1.33325 13.3337 1.33325 10.0003V8.98699" stroke="#2D2D2D" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.33325 1.33301H5.99992C2.66659 1.33301 1.33325 2.66634 1.33325 5.99967" stroke="#2D2D2D" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.2866 6.00586L13.9866 5.30586C14.8933 4.39919 15.3199 3.34586 13.9866 2.01253C12.6533 0.679192 11.5999 1.10586 10.6933 2.01253L5.43992 7.26587C5.23992 7.46587 5.03992 7.85921 4.99992 8.14587L4.71325 10.1525C4.60659 10.8792 5.11992 11.3859 5.84659 11.2859L7.85327 10.9992C8.13327 10.9592 8.5266 10.7592 8.73327 10.5592L10.8533 8.43921L11.3399 7.95254" stroke="#2D2D2D" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.94019 2.7666C10.3869 4.35993 11.6335 5.6066 13.2335 6.05993" stroke="#2D2D2D" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</g>
<defs>
<clipPath id="clip0_591_316">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_383_1601)">
<path d="M4.00647 4.47004C4.00647 6.93496 6.0116 8.94009 8.47651 8.94009C10.9414 8.94009 12.9466 6.93496 12.9466 4.47004C12.9466 2.00513 10.9414 0 8.47651 0C6.0116 0 4.00647 2.00513 4.00647 4.47004Z" fill="#45C1F1"/>
<path d="M12.9466 4.47004C12.9466 2.00513 10.9415 0 8.47656 0V8.94009C10.9415 8.94009 12.9466 6.93496 12.9466 4.47004Z" fill="#44A4EC"/>
<path d="M1.02649 16.4565C1.02649 16.7311 1.24863 16.9532 1.52316 16.9532H15.43C15.7045 16.9532 15.9266 16.7311 15.9266 16.4565C15.9266 12.8964 13.03 9.93359 9.4699 9.93359H7.48322C3.92308 9.93359 1.02649 12.8964 1.02649 16.4565Z" fill="#45C1F1"/>
<path d="M9.46991 9.93359H8.47656V16.9532H15.43C15.7045 16.9532 15.9266 16.7311 15.9266 16.4565C15.9266 12.8964 13.03 9.93359 9.46991 9.93359Z" fill="#44A4EC"/>
</g>
<defs>
<clipPath id="clip0_383_1601">
<rect width="16.9531" height="16.9531" fill="white"/>
</clipPath>
</defs>
</svg>
......@@ -6,16 +6,17 @@ class CommonResponse {
CommonResponse({this.error, this.balance, this.message});
CommonResponse.fromJson(Map<String, dynamic> json) {
error = json['error'];
balance = json['balance'];
message = json['message'];
error = int.tryParse(json['error']?.toString() ?? '');
balance = int.tryParse(json['balance']?.toString() ?? '');
message = json['message']?.toString();
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['error'] = this.error;
data['balance'] = this.balance;
data['message'] = this.message;
return data;
return {
'error': error,
'balance': balance,
'message': message,
};
}
}
class TicketChatDisplayResponse {
String? status;
String? ticketId;
List<FeedBacks>? feedBacks;
String? error;
String? message;
TicketChatDisplayResponse(
{this.status, this.ticketId, this.feedBacks, this.error, this.message});
TicketChatDisplayResponse.fromJson(Map<String, dynamic> json) {
status = json['status'];
ticketId = json['ticket_id'];
if (json['feed_backs'] != null) {
feedBacks = <FeedBacks>[];
json['feed_backs'].forEach((v) {
feedBacks!.add(new FeedBacks.fromJson(v));
});
}
error = json['error'];
message = json['message'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['status'] = this.status;
data['ticket_id'] = this.ticketId;
if (this.feedBacks != null) {
data['feed_backs'] = this.feedBacks!.map((v) => v.toJson()).toList();
}
data['error'] = this.error;
data['message'] = this.message;
return data;
}
}
class FeedBacks {
String? id;
String? text;
List<String>? imgNames;
List<String>? images;
String? user;
String? userName;
String? createdDatetime;
String? userImg;
FeedBacks(
{this.id,
this.text,
this.imgNames,
this.images,
this.user,
this.userName,
this.createdDatetime});
FeedBacks.fromJson(Map<String, dynamic> json) {
id = json['id'];
text = json['text'];
imgNames = json['img_names'].cast<String>();
images = json['images'].cast<String>();
user = json['user'];
userName = json['user_name'];
createdDatetime = json['created_datetime'];
userImg = json['user_img'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['text'] = this.text;
data['img_names'] = this.imgNames;
data['images'] = this.images;
data['user'] = this.user;
data['user_name'] = this.userName;
data['created_datetime'] = this.createdDatetime;
data['user_img'] = this.userImg;
return data;
}
}
class TicketListResponse {
final Tickets? tickets;
final String? error;
final String? message;
TicketListResponse({this.tickets, this.error, this.message});
factory TicketListResponse.fromJson(Map<String, dynamic> json) {
return TicketListResponse(
tickets:
json['tickets'] != null ? Tickets.fromJson(json['tickets']) : null,
error: json['error']?.toString(),
message: json['message']?.toString(),
);
}
Map<String, dynamic> toJson() => {
if (tickets != null) 'tickets': tickets!.toJson(),
'error': error,
'message': message,
};
}
class Tickets {
final List<TicketItem>? closed;
final List<TicketItem>? inProgress;
Tickets({this.closed, this.inProgress});
factory Tickets.fromJson(Map<String, dynamic> json) {
return Tickets(
closed: (json['closed'] as List?)
?.map((v) => TicketItem.fromJson(v))
.toList(),
inProgress: (json['in_progress'] as List?)
?.map((v) => TicketItem.fromJson(v))
.toList(),
);
}
Map<String, dynamic> toJson() => {
if (closed != null)
'closed': closed!.map((v) => v.toJson()).toList(),
if (inProgress != null)
'in_progress': inProgress!.map((v) => v.toJson()).toList(),
};
}
class TicketItem {
final String? id;
final String? ticketNumber;
final String? type;
final String? date;
TicketItem({this.id, this.ticketNumber, this.type, this.date});
factory TicketItem.fromJson(Map<String, dynamic> json) {
return TicketItem(
id: json['id']?.toString(),
ticketNumber: json['ticket_number']?.toString(),
type: json['type']?.toString(),
date: json['date']?.toString(),
);
}
Map<String, dynamic> toJson() => {
'id': id,
'ticket_number': ticketNumber,
'type': type,
'date': date,
};
}
class TicketChatDisplayResponse {
List<Ticket>? ticket;
int? error;
String? message;
TicketChatDisplayResponse({this.ticket, this.error, this.message});
TicketChatDisplayResponse.fromJson(Map<String, dynamic> json) {
if (json['ticket'] != null) {
ticket = <Ticket>[];
json['ticket'].forEach((v) {
ticket!.add(new Ticket.fromJson(v));
});
}
error = json['error'];
message = json['message'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
if (this.ticket != null) {
data['ticket'] = this.ticket!.map((v) => v.toJson()).toList();
}
data['error'] = this.error;
data['message'] = this.message;
return data;
}
}
class Ticket {
String? tid;
String? datetime;
String? msg;
String? type;
Ticket({this.tid, this.datetime, this.msg, this.type});
Ticket.fromJson(Map<String, dynamic> json) {
tid = json['tid'];
datetime = json['datetime'];
msg = json['msg'];
type = json['type'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['tid'] = this.tid;
data['datetime'] = this.datetime;
data['msg'] = this.msg;
data['type'] = this.type;
return data;
}
}
class TicketListResponse {
List<Ticket>? ticket;
int? error;
String? message;
TicketListResponse({this.ticket, this.error, this.message});
TicketListResponse.fromJson(Map<String, dynamic> json) {
if (json['ticket'] != null) {
ticket = <Ticket>[];
json['ticket'].forEach((v) {
ticket!.add(new Ticket.fromJson(v));
});
}
error = json['error'];
message = json['message'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
if (this.ticket != null) {
data['ticket'] = this.ticket!.map((v) => v.toJson()).toList();
}
data['error'] = this.error;
data['message'] = this.message;
return data;
}
}
class Ticket {
String? tid;
String? datetime;
String? status;
String? subject;
Ticket({this.tid, this.datetime, this.status, this.subject});
Ticket.fromJson(Map<String, dynamic> json) {
tid = json['tid'];
datetime = json['datetime'];
status = json['status'];
subject = json['subject'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['tid'] = this.tid;
data['datetime'] = this.datetime;
data['status'] = this.status;
data['subject'] = this.subject;
return data;
}
}
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:gen_rentals/Services/api_calling.dart';
import '../Models/HelpAndEnquiryModels/TicketChatDisplayResponse.dart';
import '../Models/HelpAndEnquiryModels/ticketListResponse.dart';
import '../Utility/CustomSnackbar.dart';
class HelpAndEnquiryProvider extends ChangeNotifier {
bool _isLoading = false;
bool get isLoading => _isLoading;
String? _message;
String? get message => _message;
int? _balance;
int? get balance => _balance;
Future<bool> submitEnquiry({
required String sessionId,
required String accId,
required String name,
required String email,
required String mobile,
required String requirement,
required String note,
}) async {
try {
_isLoading = true;
_message = null;
notifyListeners();
final response = await ApiCalling.submitEnquiryApi(
sessionId,
accId,
name,
email,
mobile,
requirement,
note,
);
_isLoading = false;
if (response != null) {
_message = response.message;
_balance = response.balance;
notifyListeners();
if (response.error == 0) {
// success
debugPrint("✅ Enquiry submitted successfully: ${response.message}");
return true;
} else {
// failed
debugPrint("⚠️ Failed to submit enquiry: ${response.message}");
return false;
}
} else {
_isLoading = false;
notifyListeners();
debugPrint("❌ Null response received.");
return false;
}
} catch (e) {
_isLoading = false;
notifyListeners();
debugPrint("❌ Exception in submitEnquiry: $e");
return false;
}
}
String? _errorMessage;
TicketListResponse? _ticketListResponse;
String? get errorMessage => _errorMessage;
TicketListResponse? get ticketListResponse => _ticketListResponse;
/// ✅ Fetch ticket list from API
Future<void> fetchTicketList({
required String sessionId,
required String accId,
}) async {
_isLoading = true;
_errorMessage = null;
notifyListeners();
try {
final response =
await ApiCalling.fetchTicketListApi(sessionId, accId,);
if (response != null && response.error == "0") {
_ticketListResponse = response;
} else {
_errorMessage = response?.message ?? "Something went wrong";
}
} catch (e) {
debugPrint("❌ Provider Error (fetchTicketList): $e");
_errorMessage = e.toString();
} finally {
_isLoading = false;
notifyListeners();
}
}
/// ✅ Optional: Clear state (useful on logout or refresh)
void clearTickets() {
_ticketListResponse = null;
_errorMessage = null;
notifyListeners();
}
TicketChatDisplayResponse? _chatResponse;
TicketChatDisplayResponse? get chatResponse => _chatResponse;
/// Fetch chat details for a specific ticket
Future<void> fetchTicketChatDisplay({
required String sessionId,
required String accId,
required String ticketId,
}) async {
_isLoading = true;
_errorMessage = null;
notifyListeners();
try {
final response = await ApiCalling.fetchTicketChatDisplayApi(
sessionId,
accId,
ticketId,
);
if (response != null && response.error == "0") {
_chatResponse = response;
_errorMessage = null;
} else {
_chatResponse = null;
_errorMessage = response?.message ?? "Something went wrong";
}
} catch (e) {
debugPrint("❌ Provider Error (fetchTicketChatDisplay): $e");
_errorMessage = e.toString();
_chatResponse = null;
} finally {
_isLoading = false;
notifyListeners();
}
}
/// Optional: Clear data (for refresh or reset)
void clearChatData() {
_chatResponse = null;
_errorMessage = null;
notifyListeners();
}
bool _isSending = false;
bool get isSending => _isSending;
void _setSending(bool value) {
_isSending = value;
notifyListeners();
}
Future<void> sendMessage(
BuildContext context, {
required String sessionId,
required String accId,
required String ticketId,
required String msgText,
required List<File> images,
}) async {
_setSending(true);
try {
final response = await ApiCalling.addMessageApi(
sessionId,
accId,
ticketId,
msgText,
images,
);
// Check if widget is still mounted before showing dialogs
if (!context.mounted) return;
if (response != null || response?.error == 0) {
CustomSnackBar.showSuccess(
context: context,
message: response?.message ?? "Message sent successfully!",
);
// Refresh the chat after sending message
if (context.mounted) {
fetchTicketChatDisplay(
sessionId: sessionId,
accId: accId,
ticketId: ticketId,
);
}
} else {
CustomSnackBar.showError(
context: context,
message: response?.message ?? "Failed to send message!",
);
}
} catch (e) {
debugPrint("❌ sendMessage error: $e");
// Check if widget is still mounted before showing error
if (context.mounted) {
CustomSnackBar.showError(
context: context,
message: "Error sending message",
);
}
} finally {
_setSending(false);
}
}
}
......@@ -17,25 +17,24 @@ class _BillDetailListScreenState extends State<BillDetailListScreen> {
top: false,
child: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Colors.white,
elevation: 0,
title: Row(
children: [
InkResponse(
onTap: () => Navigator.pop(context),
onTap: () => Navigator.pop(context, true),
child: SvgPicture.asset(
"assets/svg/continue_left_ic.svg",
height: 25,
width: 25,
),
),
const SizedBox(width: 12),
const SizedBox(width: 10),
const Text(
"Bill Details",
"Bill List",
style: TextStyle(
fontSize: 16,
fontFamily: "Poppins",
fontWeight: FontWeight.w500,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
......@@ -126,16 +125,41 @@ class _BillDetailListScreenState extends State<BillDetailListScreen> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Bill Cycle",
"Invoice raised against",
style: TextStyle(
fontFamily: "Poppins",
color: AppColors.subtitleText,
color: AppColors.normalText,
fontWeight: FontWeight.w400,
fontSize: 12,
),
),
Text(
"7th Sep 2025 - 7th Oct 2025", // Fixed date range
"#1253", // Fixed date range
style: TextStyle(
fontFamily: "Poppins",
color: AppColors.amountText,
fontWeight: FontWeight.w400, // Medium for dates
fontSize: 12,
),
),
],
),
SizedBox(height: 4,),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"order ID: 1253",
style: TextStyle(
fontFamily: "Poppins",
color: AppColors.normalText,
fontWeight: FontWeight.w400,
fontSize: 12,
),
),
Text(
"7th Oct 2025", // Fixed date range
style: TextStyle(
fontFamily: "Poppins",
color: AppColors.normalText,
......@@ -190,7 +214,8 @@ class _BillDetailListScreenState extends State<BillDetailListScreen> {
}
Widget _buildBillItem({
String title = "Bill Cycle",
String orderId = "#1253",
String title = "Invoice raised against",
required String fromDate,
required String toDate,
required String amount,
......@@ -216,16 +241,42 @@ class _BillDetailListScreenState extends State<BillDetailListScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
orderId,
style: TextStyle(
fontFamily: "Poppins",
color: AppColors.amountText,
fontWeight: FontWeight.w400,
fontSize: 12,
),
),
const SizedBox(height: 2),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: TextStyle(
fontFamily: "Poppins",
color: AppColors.subtitleText,
color: AppColors.normalText,
fontWeight: FontWeight.w400,
fontStyle: FontStyle.normal,
fontSize: 12,
fontSize: 14,
),
),
Text(
"₹$amount",
style: TextStyle(
fontFamily: "Poppins",
color: AppColors.amountText,
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
],
),
const SizedBox(height: 2),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
......
......@@ -4,6 +4,7 @@ import 'package:gen_rentals/Screens/HelpScreens/EnquiryScreen.dart';
import 'package:gen_rentals/Screens/HelpScreens/HelpScreen.dart';
import 'package:gen_rentals/Screens/ProductsDetailScreen.dart';
import 'package:gen_rentals/Screens/TransactionsScreen.dart';
import 'package:gen_rentals/Utility/AppColors.dart';
import 'package:provider/provider.dart';
import '../Models/DashboardResponse.dart';
import '../Notifier/DashboardProvider.dart';
......@@ -199,7 +200,17 @@ class _DashboardScreenState extends State<DashboardScreen> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
InkResponse(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => TransactionsScreen(
sessionId: widget.sessionId,
accId: widget.accId,
)),
);
},
child: Row(
children: [
const Text(
"Balance Amount",
......@@ -219,6 +230,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
),
],
),
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
......@@ -341,7 +353,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => EnquiryScreen()
MaterialPageRoute(builder: (context) => EnquiryScreen(sessionId: widget.sessionId, accId: widget.accId,)
)
);
},
......@@ -429,7 +441,10 @@ class _DashboardScreenState extends State<DashboardScreen> {
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => HelpScreen()),
MaterialPageRoute(builder: (context) => HelpScreen(
sessionId: widget.sessionId,
accId: widget.accId,
)),
);
},
child: Column(
......@@ -835,14 +850,152 @@ class _DashboardScreenState extends State<DashboardScreen> {
// }
// }
void showPaymentBottomSheet(BuildContext context) {
void showPaymentBottomSheet(
BuildContext context, {
String? payTotal = "4218",
String? payBill = "",
}) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
isScrollControlled: true, // This is important
backgroundColor: Colors.transparent,
isDismissible: true,
enableDrag: true,
builder: (BuildContext context) {
return SafeArea(
bottom: true,
return PaymentBottomSheetContent(
payTotal: payTotal,
payBill: payBill,
flag: false,
);
},
);
}
}
class PaymentBottomSheetContent extends StatefulWidget {
final String? payTotal;
final String? payBill;
final bool flag;
const PaymentBottomSheetContent({
super.key,
this.payTotal,
this.payBill,
required this.flag,
});
@override
State<PaymentBottomSheetContent> createState() => _PaymentBottomSheetContentState();
}
class _PaymentBottomSheetContentState extends State<PaymentBottomSheetContent> {
int selectedOption = -1; // -1 = none, 0 = total, 1 = bill, 2 = part
final TextEditingController partAmountController = TextEditingController();
final FocusNode partAmountFocusNode = FocusNode();
@override
void initState() {
super.initState();
// Auto-focus when part payment is selected
partAmountFocusNode.addListener(() {
if (selectedOption == 2 && partAmountFocusNode.hasFocus) {
// Ensure the bottom sheet scrolls to show the text field
Future.delayed(const Duration(milliseconds: 300), () {
Scrollable.ensureVisible(
partAmountFocusNode.context!,
duration: const Duration(milliseconds: 300),
);
});
}
});
}
@override
void dispose() {
partAmountController.dispose();
partAmountFocusNode.dispose();
super.dispose();
}
void _handleRadioChange(int? value) {
setState(() {
selectedOption = value!;
});
// Auto-focus on part amount field when part payment is selected
if (value == 2) {
Future.delayed(const Duration(milliseconds: 100), () {
partAmountFocusNode.requestFocus();
});
} else {
// Clear focus when other options are selected
partAmountFocusNode.unfocus();
}
}
void _handleContinuePayment() {
// ✅ Validation
if (selectedOption == -1) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Please select a payment option."),
backgroundColor: Colors.redAccent,
),
);
return;
}
if (selectedOption == 2) {
if (partAmountController.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Please enter amount for part payment."),
backgroundColor: Colors.redAccent,
),
);
return;
}
final amount = double.tryParse(partAmountController.text.trim());
if (amount == null || amount <= 0) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Please enter a valid amount."),
backgroundColor: Colors.redAccent,
),
);
return;
}
}
Navigator.pop(context);
String selectedText = selectedOption == 0
? "Pay Total ₹${widget.payTotal}"
: selectedOption == 1
? "Pay Bill ₹${widget.payBill}"
: "Part Payment ₹${partAmountController.text}";
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Selected: $selectedText"),
backgroundColor: Colors.green,
),
);
}
@override
Widget build(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
final bottomPadding = mediaQuery.viewInsets.bottom;
return AnimatedPadding(
padding: EdgeInsets.only(bottom: bottomPadding),
duration: const Duration(milliseconds: 300),
child: SafeArea(
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
......@@ -851,13 +1004,15 @@ class _DashboardScreenState extends State<DashboardScreen> {
topRight: Radius.circular(24),
),
),
child: SingleChildScrollView(
// Add this to make it scrollable when keyboard is open
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header - Drag handle
// Header handle
Center(
child: Container(
width: 40,
......@@ -870,57 +1025,166 @@ class _DashboardScreenState extends State<DashboardScreen> {
),
const SizedBox(height: 20),
// Pay Amount Section
Text(
"Balance Amount Bill",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.normalText,
),
),
const SizedBox(height: 16),
const Divider(height: 1, color: Color(0xFFEEEEEE)),
const SizedBox(height: 16),
// ====== PAY OPTIONS ======
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Pay Total
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Column(
Row(
children: [
Radio<int>(
value: 0,
groupValue: selectedOption,
onChanged: _handleRadioChange,
activeColor: const Color(0xFF008CDE),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Pay",
const Text(
"Pay Total",
style: TextStyle(
fontSize: 16,
fontSize: 14,
fontWeight: FontWeight.w400,
color: Color(0xFF777777),
color: Colors.black87,
),
),
SizedBox(height: 4),
Text(
"₹4218",
"Avoid late payment fees.",
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w600,
fontSize: 13,
fontWeight: FontWeight.w400,
color: Colors.green.shade600,
),
),
],
),
],
),
Text(
"₹${widget.payTotal}",
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.black,
),
),
],
),
// Rent Amount Section
Column(
crossAxisAlignment: CrossAxisAlignment.end,
// Pay Bill
if (widget.flag == true)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Radio<int>(
value: 1,
groupValue: selectedOption,
onChanged: _handleRadioChange,
activeColor: const Color(0xFF008CDE),
),
const Text(
"Rent Amount",
"Pay Bill",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Color(0xFF777777),
color: Colors.black87,
),
),
const SizedBox(height: 4),
GestureDetector(
onTap: () {
// Handle view bill details
},
child: Text(
"View Bill Details",
],
),
Text(
"₹${widget.payBill}",
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.black,
),
),
],
),
// Part Payment
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Row(
children: [
Radio<int>(
value: 2,
groupValue: selectedOption,
onChanged: _handleRadioChange,
activeColor: const Color(0xFF008CDE),
),
const Text(
"Part Payment",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Colors.black87,
),
),
const SizedBox(width: 10),
if (selectedOption == 2)
Expanded(
child: Container(
height: 50,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(12),
),
child: TextField(
controller: partAmountController,
focusNode: partAmountFocusNode,
keyboardType: TextInputType.number,
textAlign: TextAlign.right,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Color(0xFF008CDE),
color: Colors.black,
),
decoration: const InputDecoration(
hintText: "Enter amount",
hintStyle: TextStyle(
color: Colors.grey,
fontSize: 14,
),
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
prefixText: "₹",
prefixStyle: TextStyle(
color: Colors.black,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
onChanged: (value) {
// Optional: Add real-time validation if needed
setState(() {});
},
),
),
),
],
),
),
],
......@@ -928,7 +1192,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
],
),
const SizedBox(height: 10),
const SizedBox(height: 18),
const Divider(height: 1, color: Color(0xFFEEEEEE)),
const SizedBox(height: 18),
......@@ -936,13 +1200,10 @@ class _DashboardScreenState extends State<DashboardScreen> {
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
// Handle continue payment
},
onPressed: _handleContinuePayment,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF008CDE),
foregroundColor: Colors.white,
disabledBackgroundColor: const Color(0xFF266E99),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
......@@ -956,13 +1217,13 @@ class _DashboardScreenState extends State<DashboardScreen> {
const Text(
"Continue Payment",
style: TextStyle(
color: Color(0xFFFFFFFF),
color: Colors.white,
fontSize: 16,
),
),
SvgPicture.asset(
"assets/svg/continue_ic.svg",
color: Color(0xFFFFFFFF),
color: Colors.white,
height: 25,
width: 25,
),
......@@ -971,12 +1232,15 @@ class _DashboardScreenState extends State<DashboardScreen> {
),
),
),
// Add extra space at bottom for better visibility
SizedBox(height: mediaQuery.viewInsets.bottom > 0 ? 20 : 0),
],
),
),
),
);
},
),
),
);
}
}
\ No newline at end of file
......@@ -17,6 +17,28 @@ class _HelpTicketScreenState extends State<HelpTicketScreen> {
List<String> _selectedImages = [];
String _selectedReason = 'Payment Issue';
// Dummy data for help - with proper null safety
final List<Map<String, dynamic>> createNewTickets = [
{
'title': 'Payment Issues',
'description': 'Get help with payment related problems',
'icon': "assets/svg/rupee_coin_ic.svg",
'color': Color(0xFFFFEFBE),
},
{
'title': 'Bill Related Issues',
'description': 'Resolve bill and invoice matters',
'icon': "assets/svg/know_pay.svg",
'color': Color(0xFFCEF9FF),
},
{
'title': 'Other Issues',
'description': 'Any other support you need',
'icon': 'assets/svg/help_ic.svg',
'color': Color(0xFFE4E5FF),
},
];
@override
void initState() {
super.initState();
......@@ -27,7 +49,6 @@ class _HelpTicketScreenState extends State<HelpTicketScreen> {
@override
Widget build(BuildContext context) {
final isEditable = widget.reason == null;
final showOtherReasonField = _selectedReason == 'Other Issues';
return SafeArea(
......@@ -66,54 +87,49 @@ class _HelpTicketScreenState extends State<HelpTicketScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Section Title
const SectionHeading(title: 'Create New Ticket'),
const SizedBox(height: 12),
/// Reason Label
_fieldLabel("Reason"),
const SizedBox(height: 6),
/// Reason Dropdown
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SectionHeading(title: 'Create New Ticket'),
Container(
width: double.infinity,
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration(
color: Color(0xffE0E0E0),
borderRadius: BorderRadius.circular(12),
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(10),
),
child: isEditable
? DropdownButtonFormField<String>(
value: _selectedReason,
items: [
'Payment Issue',
'Bill Related Issues',
'Other Issues',
].map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(
value,
style: const TextStyle(
"order #1235",
style: TextStyle(
fontSize: 14,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w500,
color: Colors.black87,
),
),
);
}).toList(),
onChanged: (newValue) {
setState(() {
_selectedReason = newValue!;
});
},
decoration: const InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.zero,
),
)
: Text(
],
),
const SizedBox(height: 12),
/// Reason Label
_fieldLabel("Reason"),
const SizedBox(height: 6),
/// Reason Selection Button - Opens Bottom Sheet
GestureDetector(
onTap: _showReasonBottomSheet,
child: Container(
width: 200,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: Color(0xffFFF3D1),
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
_selectedReason,
style: const TextStyle(
fontSize: 14,
......@@ -122,6 +138,13 @@ class _HelpTicketScreenState extends State<HelpTicketScreen> {
color: Colors.black87,
),
),
SvgPicture.asset(
"assets/svg/edit_ic.svg",
height: 25,
),
],
),
),
),
const SizedBox(height: 16),
......@@ -267,7 +290,7 @@ class _HelpTicketScreenState extends State<HelpTicketScreen> {
return Text(
text,
style: TextStyle(
fontSize: 12,
fontSize: 14,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w500,
color: Colors.grey[700],
......@@ -341,4 +364,120 @@ class _HelpTicketScreenState extends State<HelpTicketScreen> {
_otherReasonController.dispose();
super.dispose();
}
void _showReasonBottomSheet() {
showModalBottomSheet(
context: context,
backgroundColor: Colors.white,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
),
builder: (context) {
return Container(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Select Your Reason",
style: TextStyle(
fontSize: 18,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
const SizedBox(height: 16),
GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
childAspectRatio: 0.99,
),
itemCount: createNewTickets.length,
itemBuilder: (context, index) {
final ticket = createNewTickets[index];
final String title = ticket['title'] ?? 'Unknown';
final String icon = ticket['icon'] ?? 'assets/svg/help_ic.svg';
final Color color = ticket['color'] ?? Colors.grey;
return _buildReasonCard(
title: title,
icon: icon,
color: color,
);
},
),
const SizedBox(height: 24),
],
),
);
},
);
}
Widget _buildReasonCard({
required String title,
required String icon,
required Color color,
}) {
return GestureDetector(
onTap: () {
setState(() {
_selectedReason = title;
});
Navigator.pop(context); // Close the bottom sheet
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 2, vertical: 1),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Icon container
Container(
width: 88,
height: 88,
decoration: BoxDecoration(
color: color.withOpacity(0.12), // Fixed opacity
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: SizedBox(
height: 40,
width: 40,
child: SvgPicture.asset(
icon,
fit: BoxFit.fitWidth,
),
),
),
),
const SizedBox(height: 8),
// Title
SizedBox(
child: Text(
title,
textAlign: TextAlign.center,
style: TextStyle(
color: AppColors.nearDarkText,
fontSize: 14,
fontWeight: FontWeight.w400,
fontFamily: "Plus Jakarta Sans",
),
),
),
const SizedBox(height: 4),
],
),
),
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
import '../../Notifier/HelpAndEnquiryProvider.dart';
import '../../Utility/AppColors.dart';
import '../../Utility/Reusablewidgets.dart';
import '../../Utility/CustomSnackbar.dart';
class EnquiryScreen extends StatefulWidget {
const EnquiryScreen({super.key});
final String sessionId;
final String accId;
const EnquiryScreen({
super.key,
required this.sessionId,
required this.accId,
});
@override
State<EnquiryScreen> createState() => _EnquiryScreenState();
}
class _EnquiryScreenState extends State<EnquiryScreen> {
final _formKey = GlobalKey<FormState>();
final TextEditingController nameController = TextEditingController();
final TextEditingController emailController = TextEditingController();
final TextEditingController phoneController = TextEditingController();
......@@ -19,13 +30,15 @@ class _EnquiryScreenState extends State<EnquiryScreen> {
@override
Widget build(BuildContext context) {
final enquiryProvider = Provider.of<HelpAndEnquiryProvider>(context);
return SafeArea(
top: false,
child: Scaffold(
backgroundColor: AppColors.backgroundRegular,
backgroundColor: Color(0xFFffffff),
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Colors.white,
backgroundColor: Color(0xFFFCFCFC),
elevation: 0,
title: Row(
children: [
......@@ -53,30 +66,69 @@ class _EnquiryScreenState extends State<EnquiryScreen> {
// Main Body
body: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_labelText("Name"),
_textField(nameController, "Enter Name"),
_textField(
controller: nameController,
hint: "Enter Name",
fieldName: "Name",
),
const SizedBox(height: 16),
_labelText("Email Id"),
_textField(emailController, "Enter Email ID"),
_textField(
controller: emailController,
hint: "Enter Email ID",
fieldName: "Email",
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.trim().isEmpty) {
return "Please enter your email";
}
if (!RegExp(r'^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$')
.hasMatch(value.trim())) {
return "Enter a valid email";
}
return null;
},
),
const SizedBox(height: 16),
_labelText("Phone No."),
_textField(phoneController, "Enter Phone Number",
keyboardType: TextInputType.phone),
_textField(
controller: phoneController,
hint: "Enter Phone Number",
fieldName: "Phone Number",
keyboardType: TextInputType.phone,
validator: (value) {
if (value == null || value.trim().isEmpty) {
return "Please enter your phone number";
}
if (value.trim().length < 10) {
return "Enter a valid phone number";
}
return null;
},
),
const SizedBox(height: 16),
_labelText("Requirement"),
_textField(requirementController, "Enter Requirement"),
_textField(
controller: requirementController,
hint: "Enter Requirement",
fieldName: "Requirement",
),
const SizedBox(height: 16),
_labelText("Note"),
_textField(
noteController,
"Write a short note",
controller: noteController,
hint: "Write a short note",
fieldName: "Note",
maxLines: 5,
),
const SizedBox(height: 32),
......@@ -85,9 +137,45 @@ class _EnquiryScreenState extends State<EnquiryScreen> {
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
// Submit action
onPressed: enquiryProvider.isLoading
? null
: () async {
FocusScope.of(context).unfocus();
if (!_formKey.currentState!.validate()) return;
final success =
await enquiryProvider.submitEnquiry(
sessionId: widget.sessionId,
accId: widget.accId,
name: nameController.text.trim(),
email: emailController.text.trim(),
mobile: phoneController.text.trim(),
requirement: requirementController.text.trim(),
note: noteController.text.trim(),
);
if (!mounted) return;
if (success) {
CustomSnackBar.showSuccess(
context: context,
message: enquiryProvider.message ??
"Enquiry submitted successfully!",
);
_formKey.currentState!.reset();
nameController.clear();
emailController.clear();
phoneController.clear();
requirementController.clear();
noteController.clear();
} else {
CustomSnackBar.showError(
context: context,
message: enquiryProvider.message ??
"Failed to submit enquiry!",
);
}
},
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.buttonColor,
......@@ -98,7 +186,16 @@ class _EnquiryScreenState extends State<EnquiryScreen> {
),
elevation: 0,
),
child: const Text(
child: enquiryProvider.isLoading
? const SizedBox(
height: 22,
width: 22,
child: CircularProgressIndicator(
strokeWidth: 2.5,
color: Colors.white,
),
)
: const Text(
"Submit",
style: TextStyle(
fontSize: 16,
......@@ -108,11 +205,11 @@ class _EnquiryScreenState extends State<EnquiryScreen> {
),
),
),
const SizedBox(height: 16),
],
),
),
),
),
);
}
......@@ -129,22 +226,41 @@ class _EnquiryScreenState extends State<EnquiryScreen> {
);
}
/// Rounded Input Field
Widget _textField(
TextEditingController controller,
String hint, {
/// Text Field with error message shown below box
Widget _textField({
required TextEditingController controller,
required String hint,
required String fieldName,
TextInputType keyboardType = TextInputType.text,
int maxLines = 1,
String? Function(String?)? validator,
}) {
return Container(
return FormField<String>(
validator: validator ??
(value) {
if (controller.text.trim().isEmpty) {
return '$fieldName is required';
}
return null;
},
builder: (field) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
color: Color(0xffF6F6F8),
borderRadius: BorderRadius.circular(12),
// border: Border.all(
// color: field.hasError ? Colors.red : Colors.transparent,
// width: 1,
// ),
),
child: TextFormField(
controller: controller,
keyboardType: keyboardType,
maxLines: maxLines,
onChanged: (_) => field.didChange(controller.text),
decoration: InputDecoration(
hintText: hint,
hintStyle: TextStyle(
......@@ -157,6 +273,22 @@ class _EnquiryScreenState extends State<EnquiryScreen> {
border: InputBorder.none,
),
),
),
if (field.hasError)
Padding(
padding: const EdgeInsets.only(top: 5, left: 4),
child: Text(
field.errorText ?? '',
style: const TextStyle(
color: Colors.red,
fontSize: 12,
fontFamily: "Plus Jakarta Sans",
),
),
),
],
);
},
);
}
......
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:gen_rentals/Screens/HelpScreens/CreateTicketScreen.dart';
import 'package:gen_rentals/Screens/HelpScreens/OrderHelpScreen.dart';
import 'package:gen_rentals/Screens/HelpScreens/ProcessTicketScreen.dart';
import 'package:gen_rentals/Utility/Reusablewidgets.dart';
import '../../Notifier/HelpAndEnquiryProvider.dart';
import '../../Utility/AppColors.dart';
import 'package:provider/provider.dart';
class HelpScreen extends StatefulWidget {
const HelpScreen({super.key});
final String sessionId;
final String accId;
HelpScreen({
super.key,
required this.sessionId,
required this.accId,
});
@override
State<HelpScreen> createState() => _HelpScreenState();
}
class _HelpScreenState extends State<HelpScreen> {
// Dummy data for help - with proper null safety
@override
void initState() {
super.initState();
/// ✅ Fetch ticket list on screen load
Future.microtask(() async {
final provider = Provider.of<HelpAndEnquiryProvider>(context, listen: false);
await provider.fetchTicketList(
sessionId: widget.sessionId,
accId: widget.accId,
);
});
}
// ✅ (unchanged)
final List<Map<String, dynamic>> createNewTickets = [
{
'title': 'Payment Issues',
......@@ -36,16 +59,6 @@ class _HelpScreenState extends State<HelpScreen> {
},
];
final List<Map<String, String>> processingTickets = [
{'title': 'Payment Issue', 'date': '25th Jan 2025', 'status': 'In Process'},
];
final List<Map<String, String>> closedTickets = [
{'title': 'Bill Payments', 'date': '25th Jan 2025'},
{'title': 'Others', 'date': '25th Jan 2025'},
{'title': 'Payment Issue', 'date': '25th Jan 2025'},
];
@override
Widget build(BuildContext context) {
return SafeArea(
......@@ -65,7 +78,7 @@ class _HelpScreenState extends State<HelpScreen> {
),
),
const SizedBox(width: 10),
Text(
const Text(
"Help?",
style: TextStyle(
fontSize: 16,
......@@ -77,14 +90,32 @@ class _HelpScreenState extends State<HelpScreen> {
],
),
),
// Main content
body: SingleChildScrollView(
// ✅ Provider Consumer used here
body: Consumer<HelpAndEnquiryProvider>(
builder: (context, provider, _) {
if (provider.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (provider.errorMessage != null) {
return Center(
child: Text(provider.errorMessage!,
style: const TextStyle(color: Colors.red)),
);
}
final ticketData = provider.ticketListResponse?.tickets;
final processingTickets = ticketData?.inProgress ?? [];
final closedTickets = ticketData?.closed ?? [];
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Create New Ticket Section
SectionHeading(
const SectionHeading(
title: 'Create New Ticket',
padding: EdgeInsets.symmetric(horizontal: 2, vertical: 4),
),
......@@ -93,129 +124,126 @@ class _HelpScreenState extends State<HelpScreen> {
const SizedBox(height: 12),
// Processing Tickets Section
SectionHeading(
const SectionHeading(
title: 'Processing Tickets',
padding: EdgeInsets.symmetric(horizontal: 2, vertical: 4),
),
const SizedBox(height: 2),
_buildProcessingTicketsSection(),
_buildProcessingTicketsSection(processingTickets),
const SizedBox(height: 10),
// Closed Tickets Section
SectionHeading(
const SectionHeading(
title: 'Closed Tickets',
padding: EdgeInsets.symmetric(horizontal: 2, vertical: 4),
),
const SizedBox(height: 2),
_buildClosedTicketsSection(),
_buildClosedTicketsSection(closedTickets),
],
),
);
},
),
),
);
}
Widget _buildCreateNewTicketSection() {
return GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
childAspectRatio: 0.99,
),
itemCount: createNewTickets.length,
itemBuilder: (context, index) {
final ticket = createNewTickets[index];
final String title = ticket['title'] ?? 'Unknown';
final String description = ticket['description'] ?? '';
final String icon = ticket['icon'] ?? 'assets/svg/help_ic.svg';
final Color color = ticket['color'] ?? Colors.grey;
return _buildFeatureCard(
title: title,
description: description,
icon: icon,
color: color,
);
},
);
}
Widget _buildFeatureCard({
required String title,
required String description,
required String icon,
required Color color,
}) {
return GestureDetector(
return InkResponse(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => HelpTicketScreen(reason: title,))
MaterialPageRoute(builder: (context) => OrderHelpScreen(sessionId: widget.sessionId, accId: widget.accId))
);
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 2, vertical: 1),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Icon container
Container(
width: 88,
height: 88,
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 14),
decoration: BoxDecoration(
color: color.withOpacity(0.7),
borderRadius: BorderRadius.circular(12),
color: Colors.white,
borderRadius: BorderRadius.circular(14),
),
child: Center(
child: SizedBox(
height: 40,
width: 40,
child: Row(
children: [
SizedBox(
height: 42,
width: 42,
child: SvgPicture.asset(
icon,
fit: BoxFit.fitWidth,
"assets/svg/help_ic.svg",
height: 30,
width: 30,
fit: BoxFit.contain,
),
),
const SizedBox(width: 12),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Get help for an order",
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontFamily: "Poppins",
fontSize: 14,
color: Colors.black,
),
),
const SizedBox(height: 8),
// Title
SizedBox(
child: Text(
title,
textAlign: TextAlign.center,
SizedBox(height: 4),
Text(
"Select an order",
style: TextStyle(
color: AppColors.nearDarkText,
fontSize: 14,
fontWeight: FontWeight.w400,
fontFamily: "Plus Jakarta Sans",
fontFamily: "Poppins",
fontSize: 12,
color: Colors.grey,
),
),
],
),
),
SizedBox(
height: 30,
width: 30,
child: SvgPicture.asset(
"assets/svg/continue_ic.svg",
color: Color(0xFF000000),
height: 18,
width: 18,
),
),
const SizedBox(height: 4),
],
),
),
);
}
Widget _buildProcessingTicketsSection() {
/// ✅ Processing tickets from provider
Widget _buildProcessingTicketsSection(List<dynamic> tickets) {
if (tickets.isEmpty) {
return const Center(child: Text("No processing tickets"));
}
return Container(
padding: const EdgeInsets.all(4),
child: Column(
children: processingTickets.map((ticket) {
children: tickets.map((ticket) {
return Padding(
padding: const EdgeInsets.only(bottom: 4),
child: CommonListItem(
title: ticket['title']!,
date: ticket['date']!,
status: ticket['status']!,
orderId: ticket.ticketNumber ?? '',
title: ticket.type ?? 'Untitled',
date: ticket.date ?? '',
status: 'In Process',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ProcessTicketChatScreen())
MaterialPageRoute(
builder: (context) => TicketChatScreen(
sessionId: widget.sessionId,
accId: widget.accId,
ticketId: ticket.id,
status: "In Process",
),
),
);
},
),
......@@ -225,19 +253,35 @@ class _HelpScreenState extends State<HelpScreen> {
);
}
Widget _buildClosedTicketsSection() {
/// ✅ Closed tickets from provider
Widget _buildClosedTicketsSection(List<dynamic> tickets) {
if (tickets.isEmpty) {
return const Center(child: Text("No closed tickets"));
}
return Container(
padding: const EdgeInsets.all(4),
child: Column(
children: closedTickets.map((ticket) {
children: tickets.map((ticket) {
return Padding(
padding: const EdgeInsets.only(bottom: 4),
child: CommonListItem(
title: ticket['title']!,
date: ticket['date']!,
status: "", // Empty status for closed tickets
orderId: ticket.ticketNumber ?? '',
title: ticket.type ?? 'Untitled',
date: ticket.date ?? '',
status: "",
onTap: () {
// Handle closed ticket tap
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TicketChatScreen(
sessionId: widget.sessionId,
accId: widget.accId,
ticketId: ticket.id,
status: "Closed",
),
),
);
},
),
);
......@@ -247,7 +291,9 @@ class _HelpScreenState extends State<HelpScreen> {
}
}
class CommonListItem extends StatelessWidget {
final String orderId;
final String title;
final String date;
final String status;
......@@ -255,6 +301,7 @@ class CommonListItem extends StatelessWidget {
const CommonListItem({
Key? key,
required this.orderId,
required this.title,
required this.date,
required this.status,
......@@ -265,7 +312,7 @@ class CommonListItem extends StatelessWidget {
Widget build(BuildContext context) {
return Column(
children: [
const SizedBox(height: 4),
const SizedBox(height: 6),
Material(
color: Colors.transparent,
child: InkWell(
......@@ -276,10 +323,20 @@ class CommonListItem extends StatelessWidget {
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 16),
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 17),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"#${orderId}",
style: const TextStyle(
fontSize: 12,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400,
color: AppColors.subtitleText,
),
),
SizedBox(width: 10,),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
......@@ -294,6 +351,7 @@ class CommonListItem extends StatelessWidget {
),
),
const SizedBox(height: 4),
if(status.isNotEmpty)
Text(
date,
style: TextStyle(
......@@ -323,6 +381,16 @@ class CommonListItem extends StatelessWidget {
),
),
),
if(status.isEmpty)
Text(
date,
style: TextStyle(
fontSize: 12,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400,
color: Colors.grey[600],
),
),
],
),
),
......
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:gen_rentals/Screens/HelpScreens/ProcessTicketScreen.dart';
import 'package:gen_rentals/Utility/Reusablewidgets.dart';
import '../../Models/DashboardResponse.dart';
import '../../Notifier/DashboardProvider.dart';
import '../../Notifier/HelpAndEnquiryProvider.dart';
import '../../Utility/AppColors.dart';
import 'package:provider/provider.dart';
import '../ProductsDetailScreen.dart';
import 'CreateTicketScreen.dart';
class OrderHelpScreen extends StatefulWidget {
final String sessionId;
final String accId;
OrderHelpScreen({
super.key,
required this.sessionId,
required this.accId,
});
@override
State<OrderHelpScreen> createState() => _OrderHelpScreenState();
}
class _OrderHelpScreenState extends State<OrderHelpScreen> {
@override
void initState() {
super.initState();
/// ✅ Fetch ticket list on screen load
Future.microtask(() async {
final provider = Provider.of<HelpAndEnquiryProvider>(context, listen: false);
await provider.fetchTicketList(
sessionId: widget.sessionId,
accId: widget.accId,
);
});
}
// ✅ (unchanged)
final List<Map<String, dynamic>> createNewTickets = [
{
'title': 'Payment Issues',
'description': 'Get help with payment related problems',
'icon': "assets/svg/rupee_coin_ic.svg",
'color': Color(0xFFFFEFBE),
},
{
'title': 'Bill Related Issues',
'description': 'Resolve bill and invoice matters',
'icon': "assets/svg/know_pay.svg",
'color': Color(0xFFCEF9FF),
},
{
'title': 'Other Issues',
'description': 'Any other support you need',
'icon': 'assets/svg/help_ic.svg',
'color': Color(0xFFE4E5FF),
},
];
@override
Widget build(BuildContext context) {
final dashboardProvider = Provider.of<DashboardProvider>(context);
final dashboardData = dashboardProvider.dashboardData;
double screenWidth = MediaQuery.of(context).size.width;
double screenHeight = MediaQuery.of(context).size.height;
double bottomPadding = MediaQuery.of(context).padding.bottom;
return SafeArea(
top: false,
child: Scaffold(
backgroundColor: AppColors.backgroundRegular,
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Colors.white,
title: Row(
children: [
InkResponse(
onTap: () => Navigator.pop(context, true),
child: SvgPicture.asset(
"assets/svg/continue_left_ic.svg",
height: 25,
),
),
const SizedBox(width: 10),
const Text(
"Help?",
style: TextStyle(
fontSize: 16,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
],
),
),
// ✅ Provider Consumer used here
body: Consumer<HelpAndEnquiryProvider>(
builder: (context, provider, _) {
if (provider.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (provider.errorMessage != null) {
return Center(
child: Text(provider.errorMessage!,
style: const TextStyle(color: Colors.red)),
);
}
final ticketData = provider.ticketListResponse?.tickets;
final processingTickets = ticketData?.inProgress ?? [];
final closedTickets = ticketData?.closed ?? [];
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Create New Ticket Section
const SectionHeading(
title: 'Select the order you are having issues with',
padding: EdgeInsets.symmetric(horizontal: 2, vertical: 4),
),
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 2, vertical: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Section Title
// Text(
// "Subscribed Orders",
// style: TextStyle(
// fontFamily: "Poppins",
// color: Colors.grey.shade800,
// fontSize: 18,
// fontWeight: FontWeight.w600,
// ),
// ),
const SizedBox(height: 16),
// Show loading or products list
if (dashboardProvider.isLoading && dashboardData == null)
const Center(
child: CircularProgressIndicator(),
)
else if (dashboardData?.orders == null || dashboardData!.orders!.isEmpty)
const Text(
"No products subscribed",
style: TextStyle(
fontFamily: "Poppins",
color: Colors.grey,
fontSize: 14,
),
)
else
// List of subscribed products from API
Column(
children: dashboardData!.orders!.map((product) {
return Column(
children: [
InkResponse(
onTap: () =>_showReasonBottomSheet(),
child: _buildProductItemFromApi(product),
),
const SizedBox(height: 16),
],
);
}).toList(),
),
],
),
),
],
),
);
},
),
),
);
}
// Helper widget for product item from API data
Widget _buildProductItemFromApi(Orders product) {
final bool hasPending = product.hasPendingPayment == true;
final productList = product.products ?? [];
return Container(
margin: const EdgeInsets.symmetric(vertical: 6),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.15),
blurRadius: 6,
offset: const Offset(0, 2),
),
],
),
child: Stack(
children: [
// ===== Red Strip (Behind Card) =====
if (hasPending)
Positioned.fill(
top: null,
child: Align(
alignment: Alignment.bottomCenter,
child: Container(
height: 45,
decoration: const BoxDecoration(
color: Color(0xFFFFE2E0),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(15),
bottomRight: Radius.circular(15),
),
),
child: Row(
children: [
const SizedBox(width: 12),
const Icon(Icons.info_outline, color: Colors.red, size: 18),
const SizedBox(width: 6),
Expanded(
child: Text(
product.pendingPaymentText ??
"Payment Pending. Please Pay before incurring fines.",
style: const TextStyle(
fontFamily: "Poppins",
color: Colors.red,
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
),
const SizedBox(width: 12),
],
),
),
),
),
// ===== Main White Card =====
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15),
),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Header Row (image, order id, date, badge)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xffF2F2F2),
borderRadius: BorderRadius.circular(16),
),
child: Image.network(
product.productImage ?? "",
height: 40,
width: 40,
fit: BoxFit.contain,
errorBuilder: (context, error, stack) =>
Image.asset('assets/images/gene_png.png',
height: 40, width: 40),
),
),
const SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"#${product.orderid ?? "0"}",
style: const TextStyle(
fontFamily: "Poppins",
color: Color(0xFF008CDE),
fontSize: 16,
fontWeight: FontWeight.w500,
height: 1.2,
),
),
Text(
product.rentedDate ?? "Rented date not available",
style: TextStyle(
fontFamily: "Poppins",
color: Colors.grey.shade600,
fontSize: 12,
),
),
],
),
],
),
// ✅ Gradient expiry badge
if (product.expiringText != null &&
product.expiringText!.isNotEmpty)
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 6),
decoration: BoxDecoration(
gradient: _getGradientByColor(product.expiringInColor),
borderRadius: BorderRadius.circular(8),
),
child: Text(
product.expiringText!,
style: const TextStyle(
color: Colors.black87,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
),
],
),
const SizedBox(height: 6),
const Divider(),
/// ===== Product List (with +3 More on same line) =====
Builder(
builder: (context) {
final visibleItems = productList.take(2).toList();
final remaining = productList.length - visibleItems.length;
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Left side → Product list (bulleted)
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (int i = 0; i < visibleItems.length; i++)
Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(width: 8),
const Text(
"• ",
style: TextStyle(color: Colors.black, fontSize: 16),
),
Expanded(
child: Text(
visibleItems[i],
style: const TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.w400,
),
),
),
],
),
),
],
),
),
// Right side → +x More (vertically centered)
if (remaining > 0)
Padding(
padding: const EdgeInsets.only(left: 8, right: 4),
child: Align(
alignment: Alignment.center,
child: Text(
"+$remaining More",
style: const TextStyle(
fontFamily: "Poppins",
color: Color(0xFF008CDE),
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
),
),
],
);
},
),
],
),
),
],
),
);
}
// Gradient helper
LinearGradient _getGradientByColor(String? color) {
switch (color) {
case "Red":
return const LinearGradient(
colors: [Color(0xFFFFE0E0), Color(0xFFFFC0C0)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
case "Green":
default:
return const LinearGradient(
colors: [Color(0xFFE9FFDD), Color(0xFFB5FFD1)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
}
}
void _showReasonBottomSheet() {
// Your existing bottom sheet implementation
showModalBottomSheet(
context: context,
backgroundColor: Colors.white,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
),
builder: (context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 14),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Select Your Reason",
style: TextStyle(
fontSize: 18,
fontFamily: "Poppins",
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
const SizedBox(height: 24),
GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
childAspectRatio: 0.99,
),
itemCount: createNewTickets.length,
itemBuilder: (context, index) {
final ticket = createNewTickets[index];
final String title = ticket['title'] ?? 'Unknown';
final String description = ticket['description'] ?? '';
final String icon = ticket['icon'] ?? 'assets/svg/help_ic.svg';
final Color color = ticket['color'] ?? Colors.grey;
return _buildFeatureCard(
title: title,
description: description,
icon: icon,
color: color,
);
},
),
const SizedBox(height: 24),
],
),
);
},
);
}
Widget _buildFeatureCard({
required String title,
required String description,
required String icon,
required Color color,
}) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => HelpTicketScreen(reason: title,))
);
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 2, vertical: 1),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Icon container
Container(
width: 88,
height: 88,
decoration: BoxDecoration(
color: color.withOpacity(0.7),
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: SizedBox(
height: 40,
width: 40,
child: SvgPicture.asset(
icon,
fit: BoxFit.fitWidth,
),
),
),
),
const SizedBox(height: 8),
// Title
SizedBox(
child: Text(
title,
textAlign: TextAlign.center,
style: TextStyle(
color: AppColors.nearDarkText,
fontSize: 14,
fontWeight: FontWeight.w400,
fontFamily: "Plus Jakarta Sans",
),
),
),
const SizedBox(height: 4),
],
),
),
);
}
}
......@@ -100,7 +100,40 @@ class _ProductsDetailScreenState extends State<ProductsDetailScreen> {
],
),
),
backgroundColor: AppColors.backgroundRegular,
body: _buildBody(provider, screenHeight, bottomPadding),
bottomNavigationBar: Container(
height: 80,
padding: EdgeInsets.symmetric(horizontal: 14, vertical: 10),
width: double.infinity,
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => BillDetailListScreen())
);
// Handle view bill action
FocusScope.of(context).unfocus();
},
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.buttonColor,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(28),
),
elevation: 0,
),
child: const Text(
"View Bill",
style: TextStyle(
fontSize: 16,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
),
),
),
),
),
);
},
......@@ -153,7 +186,7 @@ class _ProductsDetailScreenState extends State<ProductsDetailScreen> {
final order = provider.orderDetails!;
return Container(
color: const Color(0xFFF3F3F3),
color: AppColors.backgroundRegular,
height: screenHeight,
child: SingleChildScrollView(
child: Column(
......@@ -192,19 +225,20 @@ class _ProductsDetailScreenState extends State<ProductsDetailScreen> {
const SizedBox(height: 8),
Text(
order.rentedDate ?? 'Date not available',
style: const TextStyle(
style: TextStyle(
fontSize: 14,
fontFamily: "Poppins",
fontWeight: FontWeight.w400,
color: Colors.grey,
color: AppColors.subtitleText,
),
),
const SizedBox(height: 14),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: _getExpiringColor(order.expiringInColor),
borderRadius: BorderRadius.circular(16),
gradient: _getGradientByColor(order.expiringInColor),
borderRadius: BorderRadius.circular(8),
),
child: Text(
order.expiringText ?? 'Expiring info not available',
......@@ -212,7 +246,7 @@ class _ProductsDetailScreenState extends State<ProductsDetailScreen> {
fontSize: 12,
fontFamily: "Poppins",
fontWeight: FontWeight.w500,
color: Colors.white,
color: Colors.black87,
),
),
),
......@@ -264,51 +298,29 @@ class _ProductsDetailScreenState extends State<ProductsDetailScreen> {
onTap: () => _showReasonBottomSheet(),
child: Row(
children: [
SvgPicture.asset(
"assets/svg/have_compaints.svg",
height: 30,
width: 30,
),
SizedBox(width: 8,),
Text(
"Need help with this order?",
style: TextStyle(
fontSize: 14,
fontFamily: "Poppins",
fontWeight: FontWeight.w500,
color: Colors.grey,
fontWeight: FontWeight.w400,
color: AppColors.amountText,
),
),
],
),
),
const SizedBox(height: 16),
// View Bill button
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => BillDetailListScreen())
);
// Handle view bill action
FocusScope.of(context).unfocus();
},
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.buttonColor,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(28),
),
elevation: 0,
),
child: const Text(
"View Bill",
style: TextStyle(
fontSize: 16,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
),
),
),
),
],
),
)
......@@ -347,12 +359,12 @@ class _ProductsDetailScreenState extends State<ProductsDetailScreen> {
children: [
// Product ID and Name
Text(
product.idName ?? product.id ?? 'N/A',
style: const TextStyle(
"#${product.idName}",
style: TextStyle(
fontSize: 14,
fontFamily: "Poppins",
fontWeight: FontWeight.w500,
color: Colors.grey,
color: AppColors.amountText,
),
),
const SizedBox(height: 4),
......@@ -381,11 +393,11 @@ class _ProductsDetailScreenState extends State<ProductsDetailScreen> {
product.dispatchDate != null ?
"Dispatched On ${product.dispatchDate!}" :
"Dispatch date not available",
style: const TextStyle(
style: TextStyle(
fontSize: 12,
fontFamily: "Poppins",
fontWeight: FontWeight.w400,
color: Colors.grey,
color: AppColors.subtitleText,
),
),
),
......@@ -445,16 +457,21 @@ class _ProductsDetailScreenState extends State<ProductsDetailScreen> {
);
}
Color _getExpiringColor(String? colorString) {
if (colorString == null || colorString.isEmpty) {
return const Color(0xFFFFEBEB); // Default color
}
try {
// Assuming colorString is in format like "FFFF5757"
return Color(int.parse('FF$colorString', radix: 16));
} catch (e) {
return const Color(0xFFFFEFEF); // Default color on error
LinearGradient _getGradientByColor(String? color) {
switch (color) {
case "Red":
return const LinearGradient(
colors: [Color(0xFFFFE0E0), Color(0xFFFFC0C0)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
case "Green":
default:
return const LinearGradient(
colors: [Color(0xFFE9FFDD), Color(0xFFB5FFD1)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
}
}
......
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