"git@183.82.99.133:saisrinivas/gen_services.git" did not exist on "166fed3d544e1d395fdcd9fce383f145c7e2cf4e"
Commit 5ac94fe2 authored by Sai Srinivas's avatar Sai Srinivas
Browse files

New Screen and apis

parent df116895
...@@ -25,7 +25,7 @@ android { ...@@ -25,7 +25,7 @@ android {
applicationId = "in.webgrid.genrentals" applicationId = "in.webgrid.genrentals"
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config. // For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion minSdk = 23
targetSdk = 36 targetSdk = 36
versionCode = flutter.versionCode versionCode = flutter.versionCode
versionName = flutter.versionName versionName = flutter.versionName
......
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application <application
android:label="Gen Rentals" android:label="Gen Rentals"
android:name="${applicationName}" android:name="${applicationName}"
......
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"> <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>
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/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>
<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 { ...@@ -6,16 +6,17 @@ class CommonResponse {
CommonResponse({this.error, this.balance, this.message}); CommonResponse({this.error, this.balance, this.message});
CommonResponse.fromJson(Map<String, dynamic> json) { CommonResponse.fromJson(Map<String, dynamic> json) {
error = json['error']; error = int.tryParse(json['error']?.toString() ?? '');
balance = json['balance']; balance = int.tryParse(json['balance']?.toString() ?? '');
message = json['message']; message = json['message']?.toString();
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>(); return {
data['error'] = this.error; 'error': error,
data['balance'] = this.balance; 'balance': balance,
data['message'] = this.message; 'message': message,
return data; };
} }
} }
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> { ...@@ -17,25 +17,24 @@ class _BillDetailListScreenState extends State<BillDetailListScreen> {
top: false, top: false,
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Colors.white, backgroundColor: Colors.white,
elevation: 0,
title: Row( title: Row(
children: [ children: [
InkResponse( InkResponse(
onTap: () => Navigator.pop(context), onTap: () => Navigator.pop(context, true),
child: SvgPicture.asset( child: SvgPicture.asset(
"assets/svg/continue_left_ic.svg", "assets/svg/continue_left_ic.svg",
height: 25, height: 25,
width: 25,
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 10),
const Text( const Text(
"Bill Details", "Bill List",
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontFamily: "Poppins", fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w500, fontWeight: FontWeight.w600,
color: Colors.black87, color: Colors.black87,
), ),
), ),
...@@ -126,16 +125,41 @@ class _BillDetailListScreenState extends State<BillDetailListScreen> { ...@@ -126,16 +125,41 @@ class _BillDetailListScreenState extends State<BillDetailListScreen> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
"Bill Cycle", "Invoice raised against",
style: TextStyle( style: TextStyle(
fontFamily: "Poppins", fontFamily: "Poppins",
color: AppColors.subtitleText, color: AppColors.normalText,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
fontSize: 12, fontSize: 12,
), ),
), ),
Text( 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( style: TextStyle(
fontFamily: "Poppins", fontFamily: "Poppins",
color: AppColors.normalText, color: AppColors.normalText,
...@@ -190,7 +214,8 @@ class _BillDetailListScreenState extends State<BillDetailListScreen> { ...@@ -190,7 +214,8 @@ class _BillDetailListScreenState extends State<BillDetailListScreen> {
} }
Widget _buildBillItem({ Widget _buildBillItem({
String title = "Bill Cycle", String orderId = "#1253",
String title = "Invoice raised against",
required String fromDate, required String fromDate,
required String toDate, required String toDate,
required String amount, required String amount,
...@@ -216,16 +241,42 @@ class _BillDetailListScreenState extends State<BillDetailListScreen> { ...@@ -216,16 +241,42 @@ class _BillDetailListScreenState extends State<BillDetailListScreen> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ 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( Text(
title, title,
style: TextStyle( style: TextStyle(
fontFamily: "Poppins", fontFamily: "Poppins",
color: AppColors.subtitleText, color: AppColors.normalText,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
fontStyle: FontStyle.normal, 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), const SizedBox(height: 2),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
......
...@@ -4,6 +4,7 @@ import 'package:gen_rentals/Screens/HelpScreens/EnquiryScreen.dart'; ...@@ -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/HelpScreens/HelpScreen.dart';
import 'package:gen_rentals/Screens/ProductsDetailScreen.dart'; import 'package:gen_rentals/Screens/ProductsDetailScreen.dart';
import 'package:gen_rentals/Screens/TransactionsScreen.dart'; import 'package:gen_rentals/Screens/TransactionsScreen.dart';
import 'package:gen_rentals/Utility/AppColors.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../Models/DashboardResponse.dart'; import '../Models/DashboardResponse.dart';
import '../Notifier/DashboardProvider.dart'; import '../Notifier/DashboardProvider.dart';
...@@ -199,7 +200,17 @@ class _DashboardScreenState extends State<DashboardScreen> { ...@@ -199,7 +200,17 @@ class _DashboardScreenState extends State<DashboardScreen> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( InkResponse(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => TransactionsScreen(
sessionId: widget.sessionId,
accId: widget.accId,
)),
);
},
child: Row(
children: [ children: [
const Text( const Text(
"Balance Amount", "Balance Amount",
...@@ -219,6 +230,7 @@ class _DashboardScreenState extends State<DashboardScreen> { ...@@ -219,6 +230,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
), ),
], ],
), ),
),
const SizedBox(height: 8), const SizedBox(height: 8),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
...@@ -341,7 +353,7 @@ class _DashboardScreenState extends State<DashboardScreen> { ...@@ -341,7 +353,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
onTap: () { onTap: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute(builder: (context) => EnquiryScreen() MaterialPageRoute(builder: (context) => EnquiryScreen(sessionId: widget.sessionId, accId: widget.accId,)
) )
); );
}, },
...@@ -429,7 +441,10 @@ class _DashboardScreenState extends State<DashboardScreen> { ...@@ -429,7 +441,10 @@ class _DashboardScreenState extends State<DashboardScreen> {
onTap: () { onTap: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute(builder: (context) => HelpScreen()), MaterialPageRoute(builder: (context) => HelpScreen(
sessionId: widget.sessionId,
accId: widget.accId,
)),
); );
}, },
child: Column( child: Column(
...@@ -835,14 +850,152 @@ class _DashboardScreenState extends State<DashboardScreen> { ...@@ -835,14 +850,152 @@ class _DashboardScreenState extends State<DashboardScreen> {
// } // }
// } // }
void showPaymentBottomSheet(BuildContext context) { void showPaymentBottomSheet(
BuildContext context, {
String? payTotal = "4218",
String? payBill = "",
}) {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true, // This is important
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
isDismissible: true,
enableDrag: true,
builder: (BuildContext context) { builder: (BuildContext context) {
return SafeArea( return PaymentBottomSheetContent(
bottom: true, 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( child: Container(
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: Colors.white, color: Colors.white,
...@@ -851,13 +1004,15 @@ class _DashboardScreenState extends State<DashboardScreen> { ...@@ -851,13 +1004,15 @@ class _DashboardScreenState extends State<DashboardScreen> {
topRight: Radius.circular(24), topRight: Radius.circular(24),
), ),
), ),
child: SingleChildScrollView(
// Add this to make it scrollable when keyboard is open
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Header - Drag handle // Header handle
Center( Center(
child: Container( child: Container(
width: 40, width: 40,
...@@ -870,57 +1025,166 @@ class _DashboardScreenState extends State<DashboardScreen> { ...@@ -870,57 +1025,166 @@ class _DashboardScreenState extends State<DashboardScreen> {
), ),
const SizedBox(height: 20), 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( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
const Column( Row(
children: [
Radio<int>(
value: 0,
groupValue: selectedOption,
onChanged: _handleRadioChange,
activeColor: const Color(0xFF008CDE),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( const Text(
"Pay", "Pay Total",
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 14,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: Color(0xFF777777), color: Colors.black87,
), ),
), ),
SizedBox(height: 4),
Text( Text(
"₹4218", "Avoid late payment fees.",
style: TextStyle( style: TextStyle(
fontSize: 24, fontSize: 13,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w400,
color: Colors.green.shade600,
),
),
],
),
],
),
Text(
"₹${widget.payTotal}",
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.black, color: Colors.black,
), ),
), ),
], ],
), ),
// Rent Amount Section // Pay Bill
Column( if (widget.flag == true)
crossAxisAlignment: CrossAxisAlignment.end, Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Row(
children: [
Radio<int>(
value: 1,
groupValue: selectedOption,
onChanged: _handleRadioChange,
activeColor: const Color(0xFF008CDE),
),
const Text( const Text(
"Rent Amount", "Pay Bill",
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: Color(0xFF777777), color: Colors.black87,
), ),
), ),
const SizedBox(height: 4), ],
GestureDetector( ),
onTap: () { Text(
// Handle view bill details "₹${widget.payBill}",
}, style: const TextStyle(
child: Text( fontSize: 14,
"View Bill Details", 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( 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, fontSize: 14,
fontWeight: FontWeight.w500, 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> { ...@@ -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 Divider(height: 1, color: Color(0xFFEEEEEE)),
const SizedBox(height: 18), const SizedBox(height: 18),
...@@ -936,13 +1200,10 @@ class _DashboardScreenState extends State<DashboardScreen> { ...@@ -936,13 +1200,10 @@ class _DashboardScreenState extends State<DashboardScreen> {
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: _handleContinuePayment,
// Handle continue payment
},
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF008CDE), backgroundColor: const Color(0xFF008CDE),
foregroundColor: Colors.white, foregroundColor: Colors.white,
disabledBackgroundColor: const Color(0xFF266E99),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30), borderRadius: BorderRadius.circular(30),
), ),
...@@ -956,13 +1217,13 @@ class _DashboardScreenState extends State<DashboardScreen> { ...@@ -956,13 +1217,13 @@ class _DashboardScreenState extends State<DashboardScreen> {
const Text( const Text(
"Continue Payment", "Continue Payment",
style: TextStyle( style: TextStyle(
color: Color(0xFFFFFFFF), color: Colors.white,
fontSize: 16, fontSize: 16,
), ),
), ),
SvgPicture.asset( SvgPicture.asset(
"assets/svg/continue_ic.svg", "assets/svg/continue_ic.svg",
color: Color(0xFFFFFFFF), color: Colors.white,
height: 25, height: 25,
width: 25, width: 25,
), ),
...@@ -971,12 +1232,15 @@ class _DashboardScreenState extends State<DashboardScreen> { ...@@ -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> { ...@@ -17,6 +17,28 @@ class _HelpTicketScreenState extends State<HelpTicketScreen> {
List<String> _selectedImages = []; List<String> _selectedImages = [];
String _selectedReason = 'Payment Issue'; 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 @override
void initState() { void initState() {
super.initState(); super.initState();
...@@ -27,7 +49,6 @@ class _HelpTicketScreenState extends State<HelpTicketScreen> { ...@@ -27,7 +49,6 @@ class _HelpTicketScreenState extends State<HelpTicketScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isEditable = widget.reason == null;
final showOtherReasonField = _selectedReason == 'Other Issues'; final showOtherReasonField = _selectedReason == 'Other Issues';
return SafeArea( return SafeArea(
...@@ -66,54 +87,49 @@ class _HelpTicketScreenState extends State<HelpTicketScreen> { ...@@ -66,54 +87,49 @@ class _HelpTicketScreenState extends State<HelpTicketScreen> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
/// Section Title /// Section Title
const SectionHeading(title: 'Create New Ticket'), Row(
const SizedBox(height: 12), mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
/// Reason Label SectionHeading(title: 'Create New Ticket'),
_fieldLabel("Reason"),
const SizedBox(height: 6),
/// Reason Dropdown
Container( Container(
width: double.infinity,
padding: padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 10), const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Color(0xffE0E0E0), color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(12), 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( child: Text(
value, "order #1235",
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontFamily: "Plus Jakarta Sans", fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Colors.black87, 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, _selectedReason,
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
...@@ -122,6 +138,13 @@ class _HelpTicketScreenState extends State<HelpTicketScreen> { ...@@ -122,6 +138,13 @@ class _HelpTicketScreenState extends State<HelpTicketScreen> {
color: Colors.black87, color: Colors.black87,
), ),
), ),
SvgPicture.asset(
"assets/svg/edit_ic.svg",
height: 25,
),
],
),
),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
...@@ -267,7 +290,7 @@ class _HelpTicketScreenState extends State<HelpTicketScreen> { ...@@ -267,7 +290,7 @@ class _HelpTicketScreenState extends State<HelpTicketScreen> {
return Text( return Text(
text, text,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 14,
fontFamily: "Plus Jakarta Sans", fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Colors.grey[700], color: Colors.grey[700],
...@@ -341,4 +364,120 @@ class _HelpTicketScreenState extends State<HelpTicketScreen> { ...@@ -341,4 +364,120 @@ class _HelpTicketScreenState extends State<HelpTicketScreen> {
_otherReasonController.dispose(); _otherReasonController.dispose();
super.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/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
import '../../Notifier/HelpAndEnquiryProvider.dart';
import '../../Utility/AppColors.dart'; import '../../Utility/AppColors.dart';
import '../../Utility/Reusablewidgets.dart'; import '../../Utility/CustomSnackbar.dart';
class EnquiryScreen extends StatefulWidget { 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 @override
State<EnquiryScreen> createState() => _EnquiryScreenState(); State<EnquiryScreen> createState() => _EnquiryScreenState();
} }
class _EnquiryScreenState extends State<EnquiryScreen> { class _EnquiryScreenState extends State<EnquiryScreen> {
final _formKey = GlobalKey<FormState>();
final TextEditingController nameController = TextEditingController(); final TextEditingController nameController = TextEditingController();
final TextEditingController emailController = TextEditingController(); final TextEditingController emailController = TextEditingController();
final TextEditingController phoneController = TextEditingController(); final TextEditingController phoneController = TextEditingController();
...@@ -19,13 +30,15 @@ class _EnquiryScreenState extends State<EnquiryScreen> { ...@@ -19,13 +30,15 @@ class _EnquiryScreenState extends State<EnquiryScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final enquiryProvider = Provider.of<HelpAndEnquiryProvider>(context);
return SafeArea( return SafeArea(
top: false, top: false,
child: Scaffold( child: Scaffold(
backgroundColor: AppColors.backgroundRegular, backgroundColor: Color(0xFFffffff),
appBar: AppBar( appBar: AppBar(
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
backgroundColor: Colors.white, backgroundColor: Color(0xFFFCFCFC),
elevation: 0, elevation: 0,
title: Row( title: Row(
children: [ children: [
...@@ -53,30 +66,69 @@ class _EnquiryScreenState extends State<EnquiryScreen> { ...@@ -53,30 +66,69 @@ class _EnquiryScreenState extends State<EnquiryScreen> {
// Main Body // Main Body
body: SingleChildScrollView( body: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
child: Form(
key: _formKey,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_labelText("Name"), _labelText("Name"),
_textField(nameController, "Enter Name"), _textField(
controller: nameController,
hint: "Enter Name",
fieldName: "Name",
),
const SizedBox(height: 16), const SizedBox(height: 16),
_labelText("Email Id"), _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), const SizedBox(height: 16),
_labelText("Phone No."), _labelText("Phone No."),
_textField(phoneController, "Enter Phone Number", _textField(
keyboardType: TextInputType.phone), 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), const SizedBox(height: 16),
_labelText("Requirement"), _labelText("Requirement"),
_textField(requirementController, "Enter Requirement"), _textField(
controller: requirementController,
hint: "Enter Requirement",
fieldName: "Requirement",
),
const SizedBox(height: 16), const SizedBox(height: 16),
_labelText("Note"), _labelText("Note"),
_textField( _textField(
noteController, controller: noteController,
"Write a short note", hint: "Write a short note",
fieldName: "Note",
maxLines: 5, maxLines: 5,
), ),
const SizedBox(height: 32), const SizedBox(height: 32),
...@@ -85,9 +137,45 @@ class _EnquiryScreenState extends State<EnquiryScreen> { ...@@ -85,9 +137,45 @@ class _EnquiryScreenState extends State<EnquiryScreen> {
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: enquiryProvider.isLoading
// Submit action ? null
: () async {
FocusScope.of(context).unfocus(); 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( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.buttonColor, backgroundColor: AppColors.buttonColor,
...@@ -98,7 +186,16 @@ class _EnquiryScreenState extends State<EnquiryScreen> { ...@@ -98,7 +186,16 @@ class _EnquiryScreenState extends State<EnquiryScreen> {
), ),
elevation: 0, 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", "Submit",
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
...@@ -108,11 +205,11 @@ class _EnquiryScreenState extends State<EnquiryScreen> { ...@@ -108,11 +205,11 @@ class _EnquiryScreenState extends State<EnquiryScreen> {
), ),
), ),
), ),
const SizedBox(height: 16),
], ],
), ),
), ),
), ),
),
); );
} }
...@@ -129,22 +226,41 @@ class _EnquiryScreenState extends State<EnquiryScreen> { ...@@ -129,22 +226,41 @@ class _EnquiryScreenState extends State<EnquiryScreen> {
); );
} }
/// Rounded Input Field /// Text Field with error message shown below box
Widget _textField( Widget _textField({
TextEditingController controller, required TextEditingController controller,
String hint, { required String hint,
required String fieldName,
TextInputType keyboardType = TextInputType.text, TextInputType keyboardType = TextInputType.text,
int maxLines = 1, 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( decoration: BoxDecoration(
color: Colors.white, color: Color(0xffF6F6F8),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
// border: Border.all(
// color: field.hasError ? Colors.red : Colors.transparent,
// width: 1,
// ),
), ),
child: TextFormField( child: TextFormField(
controller: controller, controller: controller,
keyboardType: keyboardType, keyboardType: keyboardType,
maxLines: maxLines, maxLines: maxLines,
onChanged: (_) => field.didChange(controller.text),
decoration: InputDecoration( decoration: InputDecoration(
hintText: hint, hintText: hint,
hintStyle: TextStyle( hintStyle: TextStyle(
...@@ -157,6 +273,22 @@ class _EnquiryScreenState extends State<EnquiryScreen> { ...@@ -157,6 +273,22 @@ class _EnquiryScreenState extends State<EnquiryScreen> {
border: InputBorder.none, 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/material.dart';
import 'package:flutter_svg/flutter_svg.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/Screens/HelpScreens/ProcessTicketScreen.dart';
import 'package:gen_rentals/Utility/Reusablewidgets.dart'; import 'package:gen_rentals/Utility/Reusablewidgets.dart';
import '../../Notifier/HelpAndEnquiryProvider.dart';
import '../../Utility/AppColors.dart'; import '../../Utility/AppColors.dart';
import 'package:provider/provider.dart';
class HelpScreen extends StatefulWidget { class HelpScreen extends StatefulWidget {
const HelpScreen({super.key}); final String sessionId;
final String accId;
HelpScreen({
super.key,
required this.sessionId,
required this.accId,
});
@override @override
State<HelpScreen> createState() => _HelpScreenState(); State<HelpScreen> createState() => _HelpScreenState();
} }
class _HelpScreenState extends State<HelpScreen> { 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 = [ final List<Map<String, dynamic>> createNewTickets = [
{ {
'title': 'Payment Issues', 'title': 'Payment Issues',
...@@ -36,16 +59,6 @@ class _HelpScreenState extends State<HelpScreen> { ...@@ -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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SafeArea( return SafeArea(
...@@ -65,7 +78,7 @@ class _HelpScreenState extends State<HelpScreen> { ...@@ -65,7 +78,7 @@ class _HelpScreenState extends State<HelpScreen> {
), ),
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
Text( const Text(
"Help?", "Help?",
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
...@@ -77,14 +90,32 @@ class _HelpScreenState extends State<HelpScreen> { ...@@ -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), padding: const EdgeInsets.all(16),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Create New Ticket Section // Create New Ticket Section
SectionHeading( const SectionHeading(
title: 'Create New Ticket', title: 'Create New Ticket',
padding: EdgeInsets.symmetric(horizontal: 2, vertical: 4), padding: EdgeInsets.symmetric(horizontal: 2, vertical: 4),
), ),
...@@ -93,129 +124,126 @@ class _HelpScreenState extends State<HelpScreen> { ...@@ -93,129 +124,126 @@ class _HelpScreenState extends State<HelpScreen> {
const SizedBox(height: 12), const SizedBox(height: 12),
// Processing Tickets Section // Processing Tickets Section
SectionHeading( const SectionHeading(
title: 'Processing Tickets', title: 'Processing Tickets',
padding: EdgeInsets.symmetric(horizontal: 2, vertical: 4), padding: EdgeInsets.symmetric(horizontal: 2, vertical: 4),
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
_buildProcessingTicketsSection(), _buildProcessingTicketsSection(processingTickets),
const SizedBox(height: 10), const SizedBox(height: 10),
// Closed Tickets Section // Closed Tickets Section
SectionHeading( const SectionHeading(
title: 'Closed Tickets', title: 'Closed Tickets',
padding: EdgeInsets.symmetric(horizontal: 2, vertical: 4), padding: EdgeInsets.symmetric(horizontal: 2, vertical: 4),
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
_buildClosedTicketsSection(), _buildClosedTicketsSection(closedTickets),
], ],
), ),
);
},
), ),
), ),
); );
} }
Widget _buildCreateNewTicketSection() { Widget _buildCreateNewTicketSection() {
return GridView.builder( return InkResponse(
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(
onTap: () { onTap: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute(builder: (context) => HelpTicketScreen(reason: title,)) MaterialPageRoute(builder: (context) => OrderHelpScreen(sessionId: widget.sessionId, accId: widget.accId))
); );
}, },
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 2, vertical: 1), padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 14),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Icon container
Container(
width: 88,
height: 88,
decoration: BoxDecoration( decoration: BoxDecoration(
color: color.withOpacity(0.7), color: Colors.white,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(14),
), ),
child: Center( child: Row(
child: SizedBox( children: [
height: 40, SizedBox(
width: 40, height: 42,
width: 42,
child: SvgPicture.asset( child: SvgPicture.asset(
icon, "assets/svg/help_ic.svg",
fit: BoxFit.fitWidth, 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), SizedBox(height: 4),
// Title Text(
SizedBox( "Select an order",
child: Text(
title,
textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: AppColors.nearDarkText, fontFamily: "Poppins",
fontSize: 14, fontSize: 12,
fontWeight: FontWeight.w400, color: Colors.grey,
fontFamily: "Plus Jakarta Sans",
), ),
), ),
],
),
),
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( return Container(
padding: const EdgeInsets.all(4), padding: const EdgeInsets.all(4),
child: Column( child: Column(
children: processingTickets.map((ticket) { children: tickets.map((ticket) {
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 4), padding: const EdgeInsets.only(bottom: 4),
child: CommonListItem( child: CommonListItem(
title: ticket['title']!, orderId: ticket.ticketNumber ?? '',
date: ticket['date']!, title: ticket.type ?? 'Untitled',
status: ticket['status']!, date: ticket.date ?? '',
status: 'In Process',
onTap: () { onTap: () {
Navigator.push( Navigator.push(
context, 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> { ...@@ -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( return Container(
padding: const EdgeInsets.all(4), padding: const EdgeInsets.all(4),
child: Column( child: Column(
children: closedTickets.map((ticket) { children: tickets.map((ticket) {
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 4), padding: const EdgeInsets.only(bottom: 4),
child: CommonListItem( child: CommonListItem(
title: ticket['title']!, orderId: ticket.ticketNumber ?? '',
date: ticket['date']!, title: ticket.type ?? 'Untitled',
status: "", // Empty status for closed tickets date: ticket.date ?? '',
status: "",
onTap: () { 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> { ...@@ -247,7 +291,9 @@ class _HelpScreenState extends State<HelpScreen> {
} }
} }
class CommonListItem extends StatelessWidget { class CommonListItem extends StatelessWidget {
final String orderId;
final String title; final String title;
final String date; final String date;
final String status; final String status;
...@@ -255,6 +301,7 @@ class CommonListItem extends StatelessWidget { ...@@ -255,6 +301,7 @@ class CommonListItem extends StatelessWidget {
const CommonListItem({ const CommonListItem({
Key? key, Key? key,
required this.orderId,
required this.title, required this.title,
required this.date, required this.date,
required this.status, required this.status,
...@@ -265,7 +312,7 @@ class CommonListItem extends StatelessWidget { ...@@ -265,7 +312,7 @@ class CommonListItem extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
children: [ children: [
const SizedBox(height: 4), const SizedBox(height: 6),
Material( Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
...@@ -276,10 +323,20 @@ class CommonListItem extends StatelessWidget { ...@@ -276,10 +323,20 @@ class CommonListItem extends StatelessWidget {
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 16), padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 17),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text(
"#${orderId}",
style: const TextStyle(
fontSize: 12,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400,
color: AppColors.subtitleText,
),
),
SizedBox(width: 10,),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
...@@ -294,6 +351,7 @@ class CommonListItem extends StatelessWidget { ...@@ -294,6 +351,7 @@ class CommonListItem extends StatelessWidget {
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
if(status.isNotEmpty)
Text( Text(
date, date,
style: TextStyle( style: TextStyle(
...@@ -323,6 +381,16 @@ class CommonListItem extends StatelessWidget { ...@@ -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),
],
),
),
);
}
}
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
import '../../Notifier/HelpAndEnquiryProvider.dart';
import '../../Utility/AppColors.dart'; import '../../Utility/AppColors.dart';
import '../../Utility/Reusablewidgets.dart'; import '../../Utility/Reusablewidgets.dart';
import 'package:image_picker/image_picker.dart';
class TicketChatScreen extends StatefulWidget {
final String sessionId;
final String accId;
final String ticketId;
final String status;
class ProcessTicketChatScreen extends StatefulWidget { const TicketChatScreen({
final String? reason; super.key,
const ProcessTicketChatScreen({super.key, this.reason}); required this.sessionId,
required this.accId,
required this.ticketId,
required this.status,
});
@override @override
State<ProcessTicketChatScreen> createState() => State<TicketChatScreen> createState() =>
_ProcessTicketChatScreenState(); _TicketChatScreenState();
} }
class _ProcessTicketChatScreenState extends State<ProcessTicketChatScreen> { class _TicketChatScreenState extends State<TicketChatScreen> {
final TextEditingController _messageController = TextEditingController(); final TextEditingController _messageController = TextEditingController();
List<String> _selectedImages = []; List<String> _selectedImages = [];
List<File> _selectedImagesFiles = [];
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
final provider =
Provider.of<HelpAndEnquiryProvider>(context, listen: false);
provider.fetchTicketChatDisplay(
sessionId: widget.sessionId,
accId: widget.accId,
ticketId: widget.ticketId,
);
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<HelpAndEnquiryProvider>(
builder: (context, provider, _) {
final chatData = provider.chatResponse;
final isLoading = provider.isLoading;
final status = widget.status.toLowerCase() == "in process";
return SafeArea( return SafeArea(
top: false, top: false,
child: Scaffold( child: Scaffold(
...@@ -50,16 +86,16 @@ class _ProcessTicketChatScreenState extends State<ProcessTicketChatScreen> { ...@@ -50,16 +86,16 @@ class _ProcessTicketChatScreenState extends State<ProcessTicketChatScreen> {
padding: padding:
const EdgeInsets.symmetric(horizontal: 10, vertical: 6), const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFD7F7D9), color: status ? Colors.green.shade50 : Colors.red.shade50,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
), ),
child: const Text( child: Text(
"In Process", widget.status,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
fontFamily: "Plus Jakarta Sans", fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Colors.green, color: status ? Colors.green : Colors.red,
), ),
), ),
), ),
...@@ -67,51 +103,49 @@ class _ProcessTicketChatScreenState extends State<ProcessTicketChatScreen> { ...@@ -67,51 +103,49 @@ class _ProcessTicketChatScreenState extends State<ProcessTicketChatScreen> {
), ),
), ),
// Chat Messages + Button body: isLoading
body: Column( ? const Center(child: CircularProgressIndicator())
: (chatData?.feedBacks == null ||
chatData!.feedBacks!.isEmpty)
? const Center(child: Text("No messages yet"))
: Column(
children: [ children: [
Expanded( Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
padding: padding: const EdgeInsets.symmetric(
const EdgeInsets.symmetric(horizontal: 16, vertical: 16), horizontal: 16, vertical: 16),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: chatData.feedBacks!.map((msg) {
SizedBox(height: 4,), return Padding(
/// USER MESSAGE padding:
_chatBubble( const EdgeInsets.only(bottom: 16),
name: "Rajveer Singh", child: _chatBubble(
date: "25th Jan 2025", name: msg.userName ?? "Unknown",
message: imageName: msg.imgNames ?? [],
"I am writing to express my dissatisfaction regarding an issue with my order set for January 25, 2025. It’s frustrating to encounter problems like this, and I would appreciate prompt assistance from your support team to resolve this matter.", images: msg.images ?? [],
isUser: true, date: msg.createdDatetime ?? "",
), message: msg.text ?? "",
const SizedBox(height: 16), userIcon: msg.userImg ?? "",
isUser:
/// SUPPORT MESSAGE msg.user?.toLowerCase() == "user",
_chatBubble(
name: "Genrental Support",
date: "25th Jan 2025",
message:
"We regret to inform you that there is a problem with your order scheduled for January 25, 2025. Please contact our support team for assistance.",
isUser: false,
), ),
], );
}).toList(),
), ),
), ),
), ),
/// Bottom Button
Container( Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
color: Colors.white, color: Colors.white,
child: ElevatedButton( child: ElevatedButton(
onPressed: _openMessageSheet, onPressed: () => _openMessageSheet(ticketId: chatData.ticketId.toString()),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.buttonColor, backgroundColor: AppColors.buttonColor,
foregroundColor: Colors.white, foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16), padding:
const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(28), borderRadius: BorderRadius.circular(28),
), ),
...@@ -131,30 +165,56 @@ class _ProcessTicketChatScreenState extends State<ProcessTicketChatScreen> { ...@@ -131,30 +165,56 @@ class _ProcessTicketChatScreenState extends State<ProcessTicketChatScreen> {
), ),
), ),
); );
},
);
} }
/// Chat bubble widget /// Chat bubble widget
Widget _chatBubble({ Widget _chatBubble({
required String name, required String name,
required List<String> imageName,
required List<String> images,
required String date, required String date,
required String message, required String message,
required String userIcon,
required bool isUser, required bool isUser,
}) { }) {
debugPrint(" Images ${images.length}");
return Container( return Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Profile Icon // Profile Icon
CircleAvatar( Container(
radius: 16, height: 42,
backgroundColor: isUser ? Colors.blue.shade200 : Colors.blue, width: 42,
child: Text( decoration: const BoxDecoration(
name[0], color: Color(0xFFE6F6FF),
style: const TextStyle( shape: BoxShape.circle,
color: Colors.white, ),
fontSize: 14, clipBehavior: Clip.antiAlias,
fontWeight: FontWeight.w600, child: (userIcon.isNotEmpty)
? Image.network(
userIcon,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return const Icon(Icons.person, color: Color(0xFF2d2d2d));
},
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return const Center(
child: CircularProgressIndicator(strokeWidth: 2));
},
)
: CircleAvatar(
radius: 26,
backgroundColor: Colors.blue.shade100,
child: SvgPicture.asset(
"assets/svg/person_ic.svg",
height: 23,
width: 23,
), ),
), ),
), ),
...@@ -195,7 +255,58 @@ class _ProcessTicketChatScreenState extends State<ProcessTicketChatScreen> { ...@@ -195,7 +255,58 @@ class _ProcessTicketChatScreenState extends State<ProcessTicketChatScreen> {
height: 1.5, height: 1.5,
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 8),
// 🔹 Image container section
if (images.isNotEmpty)
Container(
margin: const EdgeInsets.only(top: 4, bottom: 8),
child: Wrap(
spacing: 8,
runSpacing: 8,
children: List.generate(images.length, (index) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.network(
images[index],
width: 100,
height: 80,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Container(
width: 100,
height: 80,
color: Colors.grey.shade200,
child: const Icon(Icons.broken_image,
color: Colors.grey),
);
},
),
),
if (imageName.length > index)
Padding(
padding: const EdgeInsets.only(top: 4.0),
child: Text(
imageName[index],
style: const TextStyle(
fontSize: 12,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w500,
color: Colors.black54,
),
),
),
],
);
}),
),
),
// Divider line
Container( Container(
height: 1, height: 1,
width: double.infinity, width: double.infinity,
...@@ -209,8 +320,11 @@ class _ProcessTicketChatScreenState extends State<ProcessTicketChatScreen> { ...@@ -209,8 +320,11 @@ class _ProcessTicketChatScreenState extends State<ProcessTicketChatScreen> {
); );
} }
/// Bottom Sheet for sending message /// Bottom Sheet for sending message
void _openMessageSheet() { void _openMessageSheet({required String ticketId}) {
final ImagePicker _picker = ImagePicker();
showModalBottomSheet( showModalBottomSheet(
isScrollControlled: true, isScrollControlled: true,
backgroundColor: Colors.white, backgroundColor: Colors.white,
...@@ -305,19 +419,70 @@ class _ProcessTicketChatScreenState extends State<ProcessTicketChatScreen> { ...@@ -305,19 +419,70 @@ class _ProcessTicketChatScreenState extends State<ProcessTicketChatScreen> {
spacing: 8, spacing: 8,
runSpacing: 8, runSpacing: 8,
children: [ children: [
GestureDetector( // Add Image Button with multiple options
onTap: () { PopupMenuButton<String>(
onSelected: (source) async {
if (source == 'camera') {
final XFile? image = await _picker.pickImage(
source: ImageSource.camera,
imageQuality: 80,
maxWidth: 1024,
maxHeight: 1024,
);
if (image != null) {
setSheetState(() {
_selectedImages.add(image.path);
_selectedImagesFiles.add(File(image.path));
});
}
} else if (source == 'gallery') {
final List<XFile>? images = await _picker.pickMultiImage(
imageQuality: 80,
maxWidth: 1024,
maxHeight: 1024,
);
if (images != null && images.isNotEmpty) {
setSheetState(() { setSheetState(() {
_selectedImages for (var image in images) {
.add('https://via.placeholder.com/100'); _selectedImages.add(image.path);
_selectedImagesFiles.add(File(image.path));
}
}); });
}
}
}, },
itemBuilder: (BuildContext context) => [
const PopupMenuItem<String>(
value: 'camera',
child: Row(
children: [
Icon(Icons.camera_alt, size: 20),
SizedBox(width: 8),
Text('Take Photo'),
],
),
),
const PopupMenuItem<String>(
value: 'gallery',
child: Row(
children: [
Icon(Icons.photo_library, size: 20),
SizedBox(width: 8),
Text('Choose from Gallery'),
],
),
),
],
child: Container( child: Container(
width: 60, width: 60,
height: 60, height: 60,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey.shade100, color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Colors.grey.shade300,
width: 1,
),
), ),
child: Icon( child: Icon(
Icons.add, Icons.add,
...@@ -325,16 +490,32 @@ class _ProcessTicketChatScreenState extends State<ProcessTicketChatScreen> { ...@@ -325,16 +490,32 @@ class _ProcessTicketChatScreenState extends State<ProcessTicketChatScreen> {
), ),
), ),
), ),
..._selectedImages.map(
(img) => Stack( // Selected Images
..._selectedImages.asMap().entries.map((entry) {
final index = entry.key;
final imagePath = entry.value;
return Stack(
children: [ children: [
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
child: Image.network( child: Image.file(
img, File(imagePath),
width: 60, width: 60,
height: 60, height: 60,
fit: BoxFit.cover, fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Container(
width: 60,
height: 60,
color: Colors.grey.shade200,
child: Icon(
Icons.broken_image,
color: Colors.grey[400],
),
);
},
), ),
), ),
Positioned( Positioned(
...@@ -343,10 +524,12 @@ class _ProcessTicketChatScreenState extends State<ProcessTicketChatScreen> { ...@@ -343,10 +524,12 @@ class _ProcessTicketChatScreenState extends State<ProcessTicketChatScreen> {
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
setSheetState(() { setSheetState(() {
_selectedImages.remove(img); _selectedImages.removeAt(index);
_selectedImagesFiles.removeAt(index);
}); });
}, },
child: Container( child: Container(
padding: const EdgeInsets.all(2),
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: Colors.black54, color: Colors.black54,
shape: BoxShape.circle, shape: BoxShape.circle,
...@@ -360,18 +543,59 @@ class _ProcessTicketChatScreenState extends State<ProcessTicketChatScreen> { ...@@ -360,18 +543,59 @@ class _ProcessTicketChatScreenState extends State<ProcessTicketChatScreen> {
), ),
), ),
], ],
);
}),
],
),
// Show file names if needed
if (_selectedImages.isNotEmpty) ...[
const SizedBox(height: 8),
Text(
'${_selectedImages.length} file(s) selected',
style: TextStyle(
fontSize: 12,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400,
color: Colors.grey[600],
), ),
), ),
], ],
),
const SizedBox(height: 24), const SizedBox(height: 24),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: () {
Navigator.pop(context); if (_messageController.text.trim().isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Please enter a message'),
duration: Duration(seconds: 2),
),
);
return;
}
final provider = Provider.of<HelpAndEnquiryProvider>(context, listen: false);
provider.sendMessage(
context,
sessionId: widget.sessionId,
accId: widget.accId,
ticketId: ticketId,
msgText: _messageController.text.trim(),
images: _selectedImagesFiles,
);
// Clear and close
setState(() {
_selectedImages.clear();
_selectedImagesFiles.clear();
});
_messageController.clear(); _messageController.clear();
Navigator.pop(context);
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.buttonColor, backgroundColor: AppColors.buttonColor,
......
...@@ -100,7 +100,40 @@ class _ProductsDetailScreenState extends State<ProductsDetailScreen> { ...@@ -100,7 +100,40 @@ class _ProductsDetailScreenState extends State<ProductsDetailScreen> {
], ],
), ),
), ),
backgroundColor: AppColors.backgroundRegular,
body: _buildBody(provider, screenHeight, bottomPadding), 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> { ...@@ -153,7 +186,7 @@ class _ProductsDetailScreenState extends State<ProductsDetailScreen> {
final order = provider.orderDetails!; final order = provider.orderDetails!;
return Container( return Container(
color: const Color(0xFFF3F3F3), color: AppColors.backgroundRegular,
height: screenHeight, height: screenHeight,
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
...@@ -192,19 +225,20 @@ class _ProductsDetailScreenState extends State<ProductsDetailScreen> { ...@@ -192,19 +225,20 @@ class _ProductsDetailScreenState extends State<ProductsDetailScreen> {
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
order.rentedDate ?? 'Date not available', order.rentedDate ?? 'Date not available',
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontFamily: "Poppins", fontFamily: "Poppins",
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: Colors.grey, color: AppColors.subtitleText,
), ),
), ),
const SizedBox(height: 14), const SizedBox(height: 14),
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 6),
decoration: BoxDecoration( decoration: BoxDecoration(
color: _getExpiringColor(order.expiringInColor), gradient: _getGradientByColor(order.expiringInColor),
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(8),
), ),
child: Text( child: Text(
order.expiringText ?? 'Expiring info not available', order.expiringText ?? 'Expiring info not available',
...@@ -212,7 +246,7 @@ class _ProductsDetailScreenState extends State<ProductsDetailScreen> { ...@@ -212,7 +246,7 @@ class _ProductsDetailScreenState extends State<ProductsDetailScreen> {
fontSize: 12, fontSize: 12,
fontFamily: "Poppins", fontFamily: "Poppins",
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Colors.white, color: Colors.black87,
), ),
), ),
), ),
...@@ -264,51 +298,29 @@ class _ProductsDetailScreenState extends State<ProductsDetailScreen> { ...@@ -264,51 +298,29 @@ class _ProductsDetailScreenState extends State<ProductsDetailScreen> {
onTap: () => _showReasonBottomSheet(), onTap: () => _showReasonBottomSheet(),
child: Row( child: Row(
children: [ children: [
SvgPicture.asset(
"assets/svg/have_compaints.svg",
height: 30,
width: 30,
),
SizedBox(width: 8,),
Text( Text(
"Need help with this order?", "Need help with this order?",
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontFamily: "Poppins", fontFamily: "Poppins",
fontWeight: FontWeight.w500, fontWeight: FontWeight.w400,
color: Colors.grey, color: AppColors.amountText,
), ),
), ),
], ],
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// View Bill button // 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> { ...@@ -347,12 +359,12 @@ class _ProductsDetailScreenState extends State<ProductsDetailScreen> {
children: [ children: [
// Product ID and Name // Product ID and Name
Text( Text(
product.idName ?? product.id ?? 'N/A', "#${product.idName}",
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontFamily: "Poppins", fontFamily: "Poppins",
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Colors.grey, color: AppColors.amountText,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
...@@ -381,11 +393,11 @@ class _ProductsDetailScreenState extends State<ProductsDetailScreen> { ...@@ -381,11 +393,11 @@ class _ProductsDetailScreenState extends State<ProductsDetailScreen> {
product.dispatchDate != null ? product.dispatchDate != null ?
"Dispatched On ${product.dispatchDate!}" : "Dispatched On ${product.dispatchDate!}" :
"Dispatch date not available", "Dispatch date not available",
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
fontFamily: "Poppins", fontFamily: "Poppins",
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: Colors.grey, color: AppColors.subtitleText,
), ),
), ),
), ),
...@@ -445,16 +457,21 @@ class _ProductsDetailScreenState extends State<ProductsDetailScreen> { ...@@ -445,16 +457,21 @@ class _ProductsDetailScreenState extends State<ProductsDetailScreen> {
); );
} }
Color _getExpiringColor(String? colorString) { LinearGradient _getGradientByColor(String? color) {
if (colorString == null || colorString.isEmpty) { switch (color) {
return const Color(0xFFFFEBEB); // Default color case "Red":
} return const LinearGradient(
colors: [Color(0xFFFFE0E0), Color(0xFFFFC0C0)],
try { begin: Alignment.topLeft,
// Assuming colorString is in format like "FFFF5757" end: Alignment.bottomRight,
return Color(int.parse('FF$colorString', radix: 16)); );
} catch (e) { case "Green":
return const Color(0xFFFFEFEF); // Default color on error 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