1894 lines
97 KiB
Dart
1894 lines
97 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:taxglide/consts/app_asstes.dart';
|
|
import 'package:taxglide/consts/comman_button.dart';
|
|
import 'package:taxglide/consts/details_webscokect.dart';
|
|
import 'package:taxglide/consts/download_helper.dart';
|
|
import 'package:taxglide/consts/local_store.dart';
|
|
import 'package:taxglide/consts/validation_popup.dart';
|
|
import 'package:taxglide/controller/api_contoller.dart';
|
|
import 'package:taxglide/controller/api_repository.dart';
|
|
import 'package:taxglide/model/detail_model.dart';
|
|
import 'package:taxglide/view/Mahi_chat/live_chat_screen.dart';
|
|
import 'package:taxglide/view/Main_controller/main_controller.dart';
|
|
import 'package:taxglide/view/screens/history/completed_live_chat_screen.dart';
|
|
|
|
class DetailScreen extends ConsumerStatefulWidget {
|
|
final int id;
|
|
final int? sourceTabIndex;
|
|
const DetailScreen({super.key, required this.id, this.sourceTabIndex});
|
|
|
|
@override
|
|
ConsumerState<DetailScreen> createState() => _DetailScreenState();
|
|
}
|
|
|
|
class _DetailScreenState extends ConsumerState<DetailScreen> {
|
|
Map<String, bool> _downloadingFiles = {}; // Track each file separately
|
|
bool _isDownloadingInvoice = false;
|
|
String? _downloadedInvoicePath;
|
|
|
|
bool _isWebSocketInitialized = false; // Add this flag
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_initializeWebSocket(); // Initialize WebSocket
|
|
}
|
|
|
|
// Add this method
|
|
Future<void> _initializeWebSocket() async {
|
|
if (_isWebSocketInitialized) return;
|
|
|
|
try {
|
|
String? userId = await LocalStore().getUserId();
|
|
if (userId != null && userId.isNotEmpty) {
|
|
final wsService = DetailsWebscokect();
|
|
|
|
// Setup message handler to refresh count
|
|
wsService.onMessageReceived = (message) {
|
|
debugPrint("📨 WebSocket message received: $message");
|
|
|
|
// Refresh count when message arrives
|
|
_refreshCount();
|
|
};
|
|
|
|
// Connect WebSocket
|
|
await wsService.connect(userId: userId);
|
|
_isWebSocketInitialized = true;
|
|
|
|
debugPrint("✅ WebSocket initialized for detail screen");
|
|
}
|
|
} catch (e) {
|
|
debugPrint("❌ Failed to initialize WebSocket: $e");
|
|
}
|
|
}
|
|
|
|
// Add this method to refresh count
|
|
void _refreshCount() {
|
|
// Get the chatId from the detail model
|
|
final detailAsync = ref.read(serviceDetailProvider(widget.id));
|
|
|
|
detailAsync.whenData((detailModel) {
|
|
final chatId = detailModel.data?.chatId;
|
|
if (chatId != null && chatId.isNotEmpty) {
|
|
// Invalidate the count provider to trigger refresh
|
|
ref.invalidate(countProvider(chatId));
|
|
debugPrint("🔄 Count refreshed for chatId: $chatId");
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
// Optionally disconnect WebSocket when leaving screen
|
|
// DetailsWebscokect().disconnect();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _handleDownload(String url) async {
|
|
if (_downloadingFiles[url] == true) return;
|
|
|
|
setState(() {
|
|
_downloadingFiles[url] = true;
|
|
});
|
|
|
|
try {
|
|
// Check if file exists, if yes open it, if no download it
|
|
final result = await DownloadHelper.downloadFile(url);
|
|
|
|
// No snackbar for downloads - only notification will show
|
|
// For existing files (view mode), no messages shown at all
|
|
|
|
// If there's an error, still show it
|
|
if (mounted && result['success'] == false) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text('Error: ${result['error']}'),
|
|
backgroundColor: Colors.red,
|
|
),
|
|
);
|
|
}
|
|
} catch (e) {
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('Error: $e'), backgroundColor: Colors.red),
|
|
);
|
|
}
|
|
} finally {
|
|
if (mounted) {
|
|
setState(() {
|
|
_downloadingFiles[url] = false;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _handleInvoiceDownload(String invoiceUrl) async {
|
|
if (_isDownloadingInvoice) return;
|
|
|
|
// Check if already downloaded
|
|
final existingPath = await DownloadHelper.checkFileExists(invoiceUrl);
|
|
if (existingPath != null) {
|
|
setState(() {
|
|
_downloadedInvoicePath = existingPath;
|
|
});
|
|
// Open the existing file
|
|
await DownloadHelper.openDownloadedFile(existingPath);
|
|
return;
|
|
}
|
|
|
|
setState(() {
|
|
_isDownloadingInvoice = true;
|
|
});
|
|
|
|
try {
|
|
final result = await DownloadHelper.downloadFile(invoiceUrl);
|
|
|
|
if (mounted && result['success'] == true) {
|
|
setState(() {
|
|
_downloadedInvoicePath = result['filePath'];
|
|
});
|
|
} else if (mounted && result['success'] == false) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text('Download failed: ${result['error']}'),
|
|
backgroundColor: Colors.red,
|
|
),
|
|
);
|
|
}
|
|
} catch (e) {
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('Error: $e'), backgroundColor: Colors.red),
|
|
);
|
|
}
|
|
} finally {
|
|
if (mounted) {
|
|
setState(() {
|
|
_isDownloadingInvoice = false;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
Widget _buildInvoiceDownloadButton(String? invoiceUrl, double width) {
|
|
if (invoiceUrl == null || invoiceUrl.isEmpty) {
|
|
return SizedBox(
|
|
width: double.infinity,
|
|
height: 57,
|
|
child: ElevatedButton.icon(
|
|
onPressed: null,
|
|
icon: const Icon(Icons.error_outline, color: Colors.white, size: 22),
|
|
label: Text(
|
|
"Invoice Not Available",
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(
|
|
fontFamily: 'Gilroy-SemiBold',
|
|
fontWeight: FontWeight.w400,
|
|
fontSize: width * 0.045,
|
|
height: 1.3,
|
|
letterSpacing: 0.04 * 17.64,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.grey,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(6),
|
|
),
|
|
disabledBackgroundColor: Colors.grey,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
return FutureBuilder<String?>(
|
|
future: DownloadHelper.checkFileExists(invoiceUrl),
|
|
builder: (context, snapshot) {
|
|
final isAlreadyDownloaded =
|
|
snapshot.data != null || _downloadedInvoicePath != null;
|
|
|
|
return SizedBox(
|
|
width: double.infinity,
|
|
height: 57,
|
|
child: ElevatedButton.icon(
|
|
onPressed: _isDownloadingInvoice
|
|
? null
|
|
: () => _handleInvoiceDownload(invoiceUrl),
|
|
icon: _isDownloadingInvoice
|
|
? const SizedBox(
|
|
width: 22,
|
|
height: 22,
|
|
child: CircularProgressIndicator(
|
|
strokeWidth: 2,
|
|
color: Colors.white,
|
|
),
|
|
)
|
|
: Icon(
|
|
isAlreadyDownloaded
|
|
? Icons.visibility
|
|
: Icons.download_rounded,
|
|
color: Colors.white,
|
|
size: 22,
|
|
),
|
|
label: Text(
|
|
_isDownloadingInvoice
|
|
? "Downloading..."
|
|
: isAlreadyDownloaded
|
|
? "View Invoice"
|
|
: "Download Invoice",
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(
|
|
fontFamily: 'Gilroy-SemiBold',
|
|
fontWeight: FontWeight.w400,
|
|
fontSize: width * 0.045,
|
|
height: 1.3,
|
|
letterSpacing: 0.04 * 17.64,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: isAlreadyDownloaded
|
|
? const Color(0xFF0C5A78)
|
|
: const Color(0xFF1E780C),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(6),
|
|
),
|
|
elevation: 7.8,
|
|
shadowColor: const Color(0x66000000),
|
|
disabledBackgroundColor: Colors.grey,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildProformaDownloadButton(String? proforma) {
|
|
final bool isDisabled = proforma == null || proforma.isEmpty;
|
|
|
|
return FutureBuilder<String?>(
|
|
future: isDisabled ? null : DownloadHelper.checkFileExists(proforma),
|
|
builder: (context, snapshot) {
|
|
final bool isDownloaded = snapshot.data != null;
|
|
|
|
return GestureDetector(
|
|
onTap: isDisabled
|
|
? null
|
|
: () async {
|
|
if (isDownloaded) {
|
|
/// 👀 View Proforma (OPEN LOCAL FILE)
|
|
await DownloadHelper.openDownloadedFile(snapshot.data!);
|
|
} else {
|
|
/// ⬇️ Download Proforma
|
|
await _handleInvoiceDownload(proforma);
|
|
}
|
|
},
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
children: [
|
|
/// 🔵 Icon
|
|
Container(
|
|
width: 38.45,
|
|
height: 38.45,
|
|
decoration: const BoxDecoration(
|
|
color: Color(0xFF4000FF),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(
|
|
isDownloaded ? Icons.visibility : Icons.download_rounded,
|
|
color: Colors.white,
|
|
size: 22,
|
|
),
|
|
),
|
|
|
|
const SizedBox(width: 5),
|
|
|
|
/// 🔤 Text
|
|
Text(
|
|
isDisabled
|
|
? "Proforma Not Available"
|
|
: isDownloaded
|
|
? "View Proforma"
|
|
: "Download Proforma",
|
|
style: TextStyle(
|
|
fontFamily: 'Gilroy-SemiBold',
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 12,
|
|
height: 1.3,
|
|
letterSpacing: 0.04 * 14.47,
|
|
color: isDisabled ? Colors.grey : const Color(0xFF4000FF),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final size = MediaQuery.of(context).size;
|
|
final width = size.width;
|
|
final height = size.height;
|
|
final validationPopup = ValidationPopup();
|
|
|
|
final detailAsync = ref.watch(serviceDetailProvider(widget.id));
|
|
|
|
return Scaffold(
|
|
body: Container(
|
|
height: height,
|
|
width: width,
|
|
decoration: const BoxDecoration(
|
|
image: DecorationImage(
|
|
image: AssetImage(AppAssets.backgroundimages),
|
|
fit: BoxFit.cover,
|
|
),
|
|
),
|
|
child: SafeArea(
|
|
child: detailAsync.when(
|
|
loading: () => const Center(
|
|
child: CircularProgressIndicator(color: Colors.deepPurple),
|
|
),
|
|
error: (e, _) => Center(
|
|
child: Text(
|
|
"Error: $e",
|
|
style: const TextStyle(
|
|
color: Colors.red,
|
|
fontSize: 16,
|
|
fontFamily: 'Gilroy-Medium',
|
|
),
|
|
),
|
|
),
|
|
data: (DetailModel detailModel) {
|
|
final data = detailModel.data;
|
|
if (data == null) {
|
|
return const Center(child: Text("No details found"));
|
|
}
|
|
final countAsync = data.chatId != null && data.chatId!.isNotEmpty
|
|
? ref.watch(countProvider(data.chatId!))
|
|
: null;
|
|
return SingleChildScrollView(
|
|
physics: const BouncingScrollPhysics(),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
/// 🔹 Custom App Bar
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 20,
|
|
vertical: 21,
|
|
),
|
|
child: SizedBox(
|
|
height: 50,
|
|
width: double.infinity,
|
|
child: Stack(
|
|
alignment: Alignment.center,
|
|
children: [
|
|
// ✅ Center Title
|
|
Text(
|
|
"Service Details",
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(
|
|
fontFamily: 'Gilroy-SemiBold',
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: width * 0.055,
|
|
color: const Color(0xFF111827),
|
|
),
|
|
),
|
|
|
|
// ✅ Left Back Icon
|
|
Positioned(
|
|
left: 0,
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
final targetTab = widget.sourceTabIndex ?? 0;
|
|
Get.offAll(
|
|
() =>
|
|
MainController(initialIndex: targetTab),
|
|
);
|
|
},
|
|
child: const Icon(
|
|
Icons.arrow_back_ios_rounded,
|
|
color: Color(0xFF111827),
|
|
),
|
|
),
|
|
),
|
|
|
|
if (data.serviceStatus == 'Completed')
|
|
Positioned(
|
|
right: 0,
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
Get.to(
|
|
() => CompletedLiveChatScreen(
|
|
fileid: data.id.toString(),
|
|
chatid:
|
|
int.tryParse(
|
|
data.completedChatId ?? '0',
|
|
) ??
|
|
0,
|
|
),
|
|
)?.then((_) {
|
|
ref
|
|
.read(
|
|
notificationTriggerProvider
|
|
.notifier,
|
|
)
|
|
.state++;
|
|
ref.invalidate(chatMessagesProvider);
|
|
});
|
|
},
|
|
child: Container(
|
|
width: 39.0,
|
|
height: 39.0,
|
|
decoration: BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
color: Colors.white,
|
|
border: Border.all(
|
|
color: const Color(0xFF055976),
|
|
width: 0.8,
|
|
),
|
|
),
|
|
child: const Icon(
|
|
Icons.message_rounded,
|
|
size: 18,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 20,
|
|
vertical: 16,
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
"Detailed Information",
|
|
style: TextStyle(
|
|
fontFamily: 'Gilroy-SemiBold',
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: width * 0.047,
|
|
height: 1.4,
|
|
letterSpacing: 0.03 * 20,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
|
|
if (data.serviceStatus == 'Waiting for Admin' ||
|
|
data.serviceStatus == 'Payment Pending' ||
|
|
data.serviceStatus == 'Cancelled')
|
|
Container(
|
|
width: width * 0.3,
|
|
height: height * 0.04,
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFFFE8E8),
|
|
borderRadius: BorderRadius.circular(5.52),
|
|
border: Border.all(
|
|
color: const Color(0xFFFFD7D7),
|
|
width: 1.84,
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.25),
|
|
blurRadius: 7.17,
|
|
offset: const Offset(0, 3.68),
|
|
),
|
|
],
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
data.serviceStatus.toString(),
|
|
style: TextStyle(
|
|
fontFamily: 'Gilroy-SemiBold',
|
|
fontWeight: FontWeight.w400,
|
|
fontSize: width * 0.029,
|
|
color: const Color(0xFFFF0F0F),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
// 🔹 In Progress Badge
|
|
if (data.serviceStatus == 'In Progress')
|
|
Container(
|
|
width: width * 0.25,
|
|
height: height * 0.042,
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFEAFAE6),
|
|
borderRadius: BorderRadius.circular(6.21),
|
|
border: Border.all(
|
|
color: const Color(0xFFDDFFDD),
|
|
width: 2.07,
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.25),
|
|
blurRadius: 8.08,
|
|
offset: const Offset(0, 4.14),
|
|
),
|
|
],
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
data.serviceStatus.toString(),
|
|
style: TextStyle(
|
|
fontFamily: 'Gilroy-SemiBold',
|
|
fontWeight: FontWeight.w400,
|
|
fontSize: width * 0.032,
|
|
letterSpacing: 0.03,
|
|
color: const Color(0xFF12800C),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
// 🔹 Completed Badge
|
|
if (data.serviceStatus == 'Completed')
|
|
Container(
|
|
width: width * 0.23,
|
|
height: height * 0.038,
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFFAF7E6),
|
|
borderRadius: BorderRadius.circular(5.52),
|
|
border: Border.all(
|
|
color: const Color(0xFFFFE9DD),
|
|
width: 1.84,
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.25),
|
|
blurRadius: 7.17,
|
|
offset: const Offset(0, 3.68),
|
|
),
|
|
],
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
data.serviceStatus.toString(),
|
|
style: TextStyle(
|
|
fontFamily: 'Gilroy-SemiBold',
|
|
fontWeight: FontWeight.w400,
|
|
fontSize: width * 0.029,
|
|
letterSpacing: 0.03,
|
|
color: const Color(0xFFFF630F),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
if (data.serviceStatus == "Completed") ...[
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
vertical: 20,
|
|
horizontal: 20,
|
|
),
|
|
child: Container(
|
|
width: double.infinity,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(10),
|
|
border: Border.all(
|
|
color: const Color(0xFFE1E1E1),
|
|
width: 1,
|
|
),
|
|
boxShadow: const [
|
|
BoxShadow(
|
|
color: Color(0x66757575),
|
|
blurRadius: 15,
|
|
offset: Offset(0, 1),
|
|
),
|
|
],
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Task Final Report",
|
|
style: TextStyle(
|
|
fontFamily: 'Gilroy-SemiBold',
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: width * 0.042,
|
|
height: 1.4,
|
|
letterSpacing: 0.03 * 16,
|
|
color: const Color(0xFF111827),
|
|
),
|
|
textAlign: TextAlign.start,
|
|
),
|
|
const SizedBox(height: 15),
|
|
Row(
|
|
mainAxisAlignment:
|
|
MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Flexible(
|
|
child: RichText(
|
|
overflow: TextOverflow.ellipsis,
|
|
text: TextSpan(
|
|
children: [
|
|
TextSpan(
|
|
text: 'Date: ',
|
|
style: TextStyle(
|
|
fontFamily: 'Gilroy-SemiBold',
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: width * 0.035,
|
|
height: 1.3,
|
|
letterSpacing: 0.04 * 13.97,
|
|
color: const Color(0xFF111827),
|
|
),
|
|
),
|
|
TextSpan(
|
|
text: data.createdDate ?? "-",
|
|
style: TextStyle(
|
|
fontFamily: 'Gilroy-Medium',
|
|
fontWeight: FontWeight.w400,
|
|
fontSize: width * 0.035,
|
|
height: 1.3,
|
|
letterSpacing: 0.04 * 13.97,
|
|
color: const Color(0xFF111827),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 10),
|
|
Flexible(
|
|
child: RichText(
|
|
overflow: TextOverflow.ellipsis,
|
|
textAlign: TextAlign.end,
|
|
text: TextSpan(
|
|
children: [
|
|
TextSpan(
|
|
text: 'Payment : ',
|
|
style: TextStyle(
|
|
fontFamily: 'Gilroy-SemiBold',
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: width * 0.035,
|
|
height: 1.3,
|
|
letterSpacing: 0.04 * 13.97,
|
|
color: const Color(0xFF111827),
|
|
),
|
|
),
|
|
TextSpan(
|
|
text: data.paymentStatus ?? "-",
|
|
style: TextStyle(
|
|
fontFamily: 'Gilroy-Medium',
|
|
fontWeight: FontWeight.w400,
|
|
fontSize: width * 0.035,
|
|
height: 1.3,
|
|
letterSpacing: 0.04 * 13.97,
|
|
color: const Color(0xFF111827),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 15),
|
|
if (data.userHandoverDocuments.isNotEmpty)
|
|
SizedBox(
|
|
height: 100,
|
|
child: ListView.builder(
|
|
scrollDirection: Axis.horizontal,
|
|
itemCount:
|
|
data.userHandoverDocuments.length,
|
|
itemBuilder: (context, index) {
|
|
final doc =
|
|
data.userHandoverDocuments[index];
|
|
final filePath = doc.filePath ?? '';
|
|
final isImage =
|
|
filePath.toLowerCase().endsWith(
|
|
'.jpg',
|
|
) ||
|
|
filePath.toLowerCase().endsWith(
|
|
'.jpeg',
|
|
) ||
|
|
filePath.toLowerCase().endsWith(
|
|
'.png',
|
|
) ||
|
|
filePath.toLowerCase().endsWith(
|
|
'.gif',
|
|
) ||
|
|
filePath.toLowerCase().endsWith(
|
|
'.webp',
|
|
);
|
|
final isPdf = filePath
|
|
.toLowerCase()
|
|
.endsWith('.pdf');
|
|
|
|
final isDownloadingThis =
|
|
_downloadingFiles[filePath] == true;
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.only(
|
|
right: 10,
|
|
),
|
|
child: GestureDetector(
|
|
onTap: () =>
|
|
_handleDownload(filePath),
|
|
child: Container(
|
|
height: 90,
|
|
width: 90,
|
|
decoration: BoxDecoration(
|
|
borderRadius:
|
|
BorderRadius.circular(8),
|
|
color: const Color(0xFFF5F5F5),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black
|
|
.withOpacity(0.05),
|
|
blurRadius: 3,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Stack(
|
|
children: [
|
|
ClipRRect(
|
|
borderRadius:
|
|
BorderRadius.circular(
|
|
8,
|
|
),
|
|
child: isImage
|
|
? Image.network(
|
|
filePath,
|
|
width: 90,
|
|
height: 90,
|
|
fit: BoxFit.cover,
|
|
errorBuilder:
|
|
(
|
|
context,
|
|
error,
|
|
stackTrace,
|
|
) {
|
|
return const Center(
|
|
child: Icon(
|
|
Icons
|
|
.broken_image,
|
|
color: Colors
|
|
.grey,
|
|
),
|
|
);
|
|
},
|
|
)
|
|
: isPdf
|
|
? const Center(
|
|
child: Icon(
|
|
Icons
|
|
.picture_as_pdf,
|
|
color: Colors.red,
|
|
size: 50,
|
|
),
|
|
)
|
|
: const Center(
|
|
child: Icon(
|
|
Icons
|
|
.insert_drive_file,
|
|
color:
|
|
Colors.grey,
|
|
size: 50,
|
|
),
|
|
),
|
|
),
|
|
Center(
|
|
child: Container(
|
|
width: 56.12,
|
|
height: 56.12,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white
|
|
.withOpacity(0.37),
|
|
borderRadius:
|
|
BorderRadius.circular(
|
|
8,
|
|
),
|
|
),
|
|
child: Center(
|
|
child: Container(
|
|
width: 36.66,
|
|
height: 36.66,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius:
|
|
BorderRadius.circular(
|
|
6,
|
|
),
|
|
boxShadow: const [
|
|
BoxShadow(
|
|
color: Colors
|
|
.black12,
|
|
blurRadius: 4,
|
|
offset: Offset(
|
|
0,
|
|
2,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
child:
|
|
isDownloadingThis
|
|
? const SizedBox(
|
|
width: 20,
|
|
height: 20,
|
|
child: CircularProgressIndicator(
|
|
strokeWidth:
|
|
2,
|
|
color: Colors
|
|
.deepPurple,
|
|
),
|
|
)
|
|
: FutureBuilder<
|
|
String?
|
|
>(
|
|
future:
|
|
DownloadHelper.checkFileExists(
|
|
filePath,
|
|
),
|
|
builder:
|
|
(
|
|
context,
|
|
snapshot,
|
|
) {
|
|
final fileExists =
|
|
snapshot.data !=
|
|
null;
|
|
return Icon(
|
|
fileExists
|
|
? Icons.visibility
|
|
: Icons.download,
|
|
color:
|
|
Colors.black87,
|
|
size:
|
|
20,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
)
|
|
else
|
|
const SizedBox.shrink(),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
Column(
|
|
children: [
|
|
if (data.paymentStatus == "Un Paid") ...[
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
vertical: 31,
|
|
horizontal: 20,
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Container(
|
|
width: double.infinity,
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFFFF6ED),
|
|
borderRadius: BorderRadius.circular(6),
|
|
border: Border.all(
|
|
color: const Color(0xFFD8CEC5),
|
|
width: 1,
|
|
),
|
|
boxShadow: const [
|
|
BoxShadow(
|
|
color: Color(0x66716E6E),
|
|
blurRadius: 15.2,
|
|
offset: Offset(0, 1),
|
|
),
|
|
],
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Payment Advice",
|
|
style: TextStyle(
|
|
fontFamily: 'Gilroy-SemiBold',
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: width * 0.042,
|
|
height: 1.4,
|
|
letterSpacing: 0.03 * 16,
|
|
color: const Color(0xFF111827),
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
Row(
|
|
mainAxisAlignment:
|
|
MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Flexible(
|
|
child: RichText(
|
|
overflow: TextOverflow.ellipsis,
|
|
text: TextSpan(
|
|
children: [
|
|
TextSpan(
|
|
text: 'Date: ',
|
|
style: TextStyle(
|
|
fontFamily:
|
|
'Gilroy-SemiBold',
|
|
fontWeight:
|
|
FontWeight.w600,
|
|
fontSize: width * 0.035,
|
|
height: 1.3,
|
|
letterSpacing:
|
|
0.04 * 13.97,
|
|
color: const Color(
|
|
0xFF111827,
|
|
),
|
|
),
|
|
),
|
|
TextSpan(
|
|
text:
|
|
data.createdDate ??
|
|
"-",
|
|
style: TextStyle(
|
|
fontFamily:
|
|
'Gilroy-Medium',
|
|
fontWeight:
|
|
FontWeight.w400,
|
|
fontSize: width * 0.035,
|
|
height: 1.3,
|
|
letterSpacing:
|
|
0.04 * 13.97,
|
|
color: const Color(
|
|
0xFF111827,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 10),
|
|
Flexible(
|
|
child: RichText(
|
|
overflow: TextOverflow.ellipsis,
|
|
textAlign: TextAlign.end,
|
|
text: TextSpan(
|
|
children: [
|
|
TextSpan(
|
|
text: 'Payment : ',
|
|
style: TextStyle(
|
|
fontFamily:
|
|
'Gilroy-SemiBold',
|
|
fontWeight:
|
|
FontWeight.w600,
|
|
fontSize: width * 0.035,
|
|
height: 1.3,
|
|
letterSpacing:
|
|
0.04 * 13.97,
|
|
color: const Color(
|
|
0xFF111827,
|
|
),
|
|
),
|
|
),
|
|
TextSpan(
|
|
text:
|
|
data.paymentStatus ??
|
|
"-",
|
|
style: TextStyle(
|
|
fontFamily:
|
|
'Gilroy-Medium',
|
|
fontWeight:
|
|
FontWeight.w400,
|
|
fontSize: width * 0.035,
|
|
height: 1.3,
|
|
letterSpacing:
|
|
0.04 * 13.97,
|
|
color: const Color(
|
|
0xFF111827,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 20),
|
|
const Divider(),
|
|
const SizedBox(height: 20),
|
|
Row(
|
|
mainAxisAlignment:
|
|
MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
"Total Amount",
|
|
style: TextStyle(
|
|
fontFamily: 'Gilroy-SemiBold',
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: width * 0.04,
|
|
color: const Color(0xFF111827),
|
|
),
|
|
),
|
|
Text(
|
|
"₹ ${data.paymentAmount ?? '0'}",
|
|
style: TextStyle(
|
|
fontFamily: 'Roboto',
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: width * 0.045,
|
|
color: const Color(0xFF111827),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
CommanButton(text: 'Pay Now', onPressed: () {}),
|
|
],
|
|
),
|
|
),
|
|
] else if (data.paymentStatus == "Paid") ...[
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
vertical: 31,
|
|
horizontal: 20,
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Container(
|
|
width: double.infinity,
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFFFF6ED),
|
|
borderRadius: BorderRadius.circular(6),
|
|
border: Border.all(
|
|
color: const Color(0xFFD8CEC5),
|
|
width: 1,
|
|
),
|
|
boxShadow: const [
|
|
BoxShadow(
|
|
color: Color(0x66716E6E),
|
|
blurRadius: 15.2,
|
|
offset: Offset(0, 1),
|
|
),
|
|
],
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Payment Advice",
|
|
style: TextStyle(
|
|
fontFamily: 'Gilroy-SemiBold',
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: width * 0.042,
|
|
height: 1.4,
|
|
letterSpacing: 0.03 * 16,
|
|
color: const Color(0xFF111827),
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
Row(
|
|
mainAxisAlignment:
|
|
MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Flexible(
|
|
child: RichText(
|
|
overflow: TextOverflow.ellipsis,
|
|
text: TextSpan(
|
|
children: [
|
|
TextSpan(
|
|
text: 'Date: ',
|
|
style: TextStyle(
|
|
fontFamily:
|
|
'Gilroy-SemiBold',
|
|
fontWeight:
|
|
FontWeight.w600,
|
|
fontSize: width * 0.035,
|
|
height: 1.3,
|
|
letterSpacing:
|
|
0.04 * 13.97,
|
|
color: const Color(
|
|
0xFF111827,
|
|
),
|
|
),
|
|
),
|
|
TextSpan(
|
|
text:
|
|
data.createdDate ??
|
|
"-",
|
|
style: TextStyle(
|
|
fontFamily:
|
|
'Gilroy-Medium',
|
|
fontWeight:
|
|
FontWeight.w400,
|
|
fontSize: width * 0.035,
|
|
height: 1.3,
|
|
letterSpacing:
|
|
0.04 * 13.97,
|
|
color: const Color(
|
|
0xFF111827,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 10),
|
|
Flexible(
|
|
child: RichText(
|
|
overflow: TextOverflow.ellipsis,
|
|
textAlign: TextAlign.end,
|
|
text: TextSpan(
|
|
children: [
|
|
TextSpan(
|
|
text: 'Payment: ',
|
|
style: TextStyle(
|
|
fontFamily:
|
|
'Gilroy-SemiBold',
|
|
fontWeight:
|
|
FontWeight.w600,
|
|
fontSize: width * 0.035,
|
|
height: 1.3,
|
|
letterSpacing:
|
|
0.04 * 13.97,
|
|
color: const Color(
|
|
0xFF111827,
|
|
),
|
|
),
|
|
),
|
|
TextSpan(
|
|
text:
|
|
data.paymentStatus ??
|
|
"-",
|
|
style: TextStyle(
|
|
fontFamily:
|
|
'Gilroy-Medium',
|
|
fontWeight:
|
|
FontWeight.w400,
|
|
fontSize: width * 0.035,
|
|
height: 1.3,
|
|
letterSpacing:
|
|
0.04 * 13.97,
|
|
color: const Color(
|
|
0xFF111827,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 20),
|
|
const Divider(),
|
|
const SizedBox(height: 20),
|
|
Row(
|
|
mainAxisAlignment:
|
|
MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
"Total Amount",
|
|
style: TextStyle(
|
|
fontFamily: 'Gilroy-SemiBold',
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: width * 0.04,
|
|
color: const Color(0xFF111827),
|
|
),
|
|
),
|
|
Text(
|
|
"₹ ${data.paymentAmount ?? '0'}",
|
|
style: TextStyle(
|
|
fontFamily: 'Roboto',
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: width * 0.045,
|
|
color: const Color(0xFF111827),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
_buildInvoiceDownloadButton(
|
|
data.invoice,
|
|
width,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
] else if (data.paymentStatus == "Waiting") ...[
|
|
const SizedBox.shrink(),
|
|
] else ...[
|
|
Text("Unknown Status: ${data.paymentStatus}"),
|
|
],
|
|
|
|
if (data.proforma != null &&
|
|
data.proforma!.isNotEmpty) ...[
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
vertical: 31,
|
|
horizontal: 20,
|
|
),
|
|
child: Container(
|
|
width: double.infinity,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white, // #FFFFFF
|
|
borderRadius: BorderRadius.circular(10),
|
|
border: Border.all(
|
|
color: const Color(0xFFE1E1E1),
|
|
width: 1,
|
|
),
|
|
boxShadow: const [
|
|
BoxShadow(
|
|
color: Color(0x40757575), // #75757540
|
|
offset: Offset(0, 1),
|
|
blurRadius: 15,
|
|
spreadRadius: 0,
|
|
),
|
|
],
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Payment Advice",
|
|
style: TextStyle(
|
|
fontFamily: 'Gilroy-SemiBold',
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: width * 0.042,
|
|
height: 1.4,
|
|
letterSpacing: 0.03 * 16,
|
|
color: const Color(0xFF111827),
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
Row(
|
|
mainAxisAlignment:
|
|
MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Flexible(
|
|
child: RichText(
|
|
overflow: TextOverflow.ellipsis,
|
|
textAlign: TextAlign.end,
|
|
text: TextSpan(
|
|
children: [
|
|
TextSpan(
|
|
text: 'proforma Number : ',
|
|
style: TextStyle(
|
|
fontFamily:
|
|
'Gilroy-SemiBold',
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: width * 0.035,
|
|
height: 1.3,
|
|
letterSpacing: 0.04 * 13.97,
|
|
color: const Color(
|
|
0xFF111827,
|
|
),
|
|
),
|
|
),
|
|
TextSpan(
|
|
text:
|
|
data.proformaNumber ??
|
|
"-",
|
|
style: TextStyle(
|
|
fontFamily: 'Gilroy-Medium',
|
|
fontWeight: FontWeight.w400,
|
|
fontSize: width * 0.035,
|
|
height: 1.3,
|
|
letterSpacing: 0.04 * 13.97,
|
|
color: const Color(
|
|
0xFF111827,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 10),
|
|
RichText(
|
|
overflow: TextOverflow.ellipsis,
|
|
textAlign: TextAlign.end,
|
|
text: TextSpan(
|
|
children: [
|
|
TextSpan(
|
|
text: 'proforma Status : ',
|
|
style: TextStyle(
|
|
fontFamily: 'Gilroy-SemiBold',
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: width * 0.035,
|
|
height: 1.3,
|
|
letterSpacing: 0.04 * 13.97,
|
|
color: const Color(0xFF111827),
|
|
),
|
|
),
|
|
TextSpan(
|
|
text: data.proformastatus ?? "-",
|
|
style: TextStyle(
|
|
fontFamily: 'Gilroy-Medium',
|
|
fontWeight: FontWeight.w400,
|
|
fontSize: width * 0.035,
|
|
height: 1.3,
|
|
letterSpacing: 0.04 * 13.97,
|
|
color: const Color(0xFF111827),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 20),
|
|
if (data.proformastatus != "Accepted") ...[
|
|
Text(
|
|
"Note : Once you accept this proforma only you can pay the amount for the service",
|
|
textAlign: TextAlign.start,
|
|
style: const TextStyle(
|
|
fontFamily: 'Gilroy-SemiBold',
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 13,
|
|
height: 1.78, // 178% line-height
|
|
letterSpacing: 0.04 * 13, // 4%
|
|
color: Colors.red,
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
],
|
|
|
|
Row(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment.center,
|
|
children: [
|
|
/// 📄 Proforma (More width)
|
|
Expanded(
|
|
flex: 3,
|
|
child: _buildProformaDownloadButton(
|
|
data.proforma,
|
|
),
|
|
),
|
|
|
|
const SizedBox(width: 8),
|
|
|
|
if (data.proformastatus != "Accepted")
|
|
/// ✅ Accept (Less width)
|
|
Expanded(
|
|
flex: 2,
|
|
child: SizedBox(
|
|
height:
|
|
42, // match proforma height
|
|
child: CommanButton(
|
|
text: 'Accept',
|
|
onPressed: () async {
|
|
try {
|
|
await ApiRepository()
|
|
.proformaaccept(
|
|
proformaId: int.parse(
|
|
data.proformaId
|
|
.toString(),
|
|
),
|
|
);
|
|
|
|
validationPopup
|
|
.showSuccessMessage(
|
|
context,
|
|
"Proforma accepted successfully",
|
|
);
|
|
|
|
ref.invalidate(
|
|
serviceDetailProvider,
|
|
);
|
|
ref.invalidate(
|
|
serviceHistoryNotifierProvider,
|
|
);
|
|
} catch (error) {
|
|
validationPopup.showErrorMessage(
|
|
context,
|
|
error is Map &&
|
|
error['error'] !=
|
|
null
|
|
? error['error']
|
|
: "Failed to accept proforma",
|
|
);
|
|
}
|
|
},
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: 20),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
|
|
/// 🔹 Main Detail Box
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 20,
|
|
vertical: 12,
|
|
),
|
|
child: Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(6),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.08),
|
|
blurRadius: 6,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildInfoRow("File ID", "${data.id}", width),
|
|
const SizedBox(height: 16),
|
|
_buildInfoRow(
|
|
"Request Type",
|
|
"${data.service}",
|
|
width,
|
|
),
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildInfoRow(
|
|
"Date",
|
|
"${data.createdDate}",
|
|
width,
|
|
),
|
|
),
|
|
const SizedBox(width: 10),
|
|
Expanded(
|
|
child: _buildInfoRow(
|
|
"Time",
|
|
"${data.createdTime}",
|
|
width,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
_buildInfoRow(
|
|
"Created by",
|
|
"${data.createdBy}",
|
|
width,
|
|
),
|
|
const SizedBox(height: 16),
|
|
_buildInfoRow("Message", "${data.message}", width),
|
|
const SizedBox(height: 16),
|
|
|
|
/// 🔹 File List Section
|
|
Text(
|
|
"File Attachments:",
|
|
style: TextStyle(
|
|
fontFamily: 'Gilroy-SemiBold',
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: width * 0.04,
|
|
color: const Color(0xFF111827),
|
|
),
|
|
),
|
|
const SizedBox(height: 10),
|
|
|
|
if (data.userUploadedDocuments.isNotEmpty)
|
|
SizedBox(
|
|
height: 100,
|
|
child: ListView.builder(
|
|
scrollDirection: Axis.horizontal,
|
|
itemCount: data.userUploadedDocuments.length,
|
|
itemBuilder: (context, index) {
|
|
final doc =
|
|
data.userUploadedDocuments[index];
|
|
final filePath = doc.filePath ?? '';
|
|
final isImage =
|
|
filePath.toLowerCase().endsWith(
|
|
'.jpg',
|
|
) ||
|
|
filePath.toLowerCase().endsWith(
|
|
'.jpeg',
|
|
) ||
|
|
filePath.toLowerCase().endsWith(
|
|
'.png',
|
|
) ||
|
|
filePath.toLowerCase().endsWith(
|
|
'.gif',
|
|
) ||
|
|
filePath.toLowerCase().endsWith(
|
|
'.webp',
|
|
);
|
|
final isPdf = filePath
|
|
.toLowerCase()
|
|
.endsWith('.pdf');
|
|
|
|
final isDownloadingThis =
|
|
_downloadingFiles[filePath] == true;
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.only(right: 10),
|
|
child: GestureDetector(
|
|
onTap: () => _handleDownload(filePath),
|
|
child: Container(
|
|
height: 90,
|
|
width: 90,
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(
|
|
8,
|
|
),
|
|
color: const Color(0xFFF5F5F5),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(
|
|
0.05,
|
|
),
|
|
blurRadius: 3,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Stack(
|
|
children: [
|
|
ClipRRect(
|
|
borderRadius:
|
|
BorderRadius.circular(8),
|
|
child: isImage
|
|
? Image.network(
|
|
filePath,
|
|
width: 90,
|
|
height: 90,
|
|
fit: BoxFit.cover,
|
|
errorBuilder:
|
|
(
|
|
context,
|
|
error,
|
|
stackTrace,
|
|
) {
|
|
return const Center(
|
|
child: Icon(
|
|
Icons
|
|
.broken_image,
|
|
color: Colors
|
|
.grey,
|
|
),
|
|
);
|
|
},
|
|
)
|
|
: isPdf
|
|
? const Center(
|
|
child: Icon(
|
|
Icons.picture_as_pdf,
|
|
color: Colors.red,
|
|
size: 50,
|
|
),
|
|
)
|
|
: const Center(
|
|
child: Icon(
|
|
Icons
|
|
.insert_drive_file,
|
|
color: Colors.grey,
|
|
size: 50,
|
|
),
|
|
),
|
|
),
|
|
Center(
|
|
child: Container(
|
|
width: 56.12,
|
|
height: 56.12,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white
|
|
.withOpacity(0.37),
|
|
borderRadius:
|
|
BorderRadius.circular(
|
|
8,
|
|
),
|
|
),
|
|
child: Center(
|
|
child: Container(
|
|
width: 36.66,
|
|
height: 36.66,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius:
|
|
BorderRadius.circular(
|
|
6,
|
|
),
|
|
boxShadow: const [
|
|
BoxShadow(
|
|
color:
|
|
Colors.black12,
|
|
blurRadius: 4,
|
|
offset: Offset(
|
|
0,
|
|
2,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
child: isDownloadingThis
|
|
? const SizedBox(
|
|
width: 20,
|
|
height: 20,
|
|
child: CircularProgressIndicator(
|
|
strokeWidth: 2,
|
|
color: Colors
|
|
.deepPurple,
|
|
),
|
|
)
|
|
: FutureBuilder<
|
|
String?
|
|
>(
|
|
future:
|
|
DownloadHelper.checkFileExists(
|
|
filePath,
|
|
),
|
|
builder: (context, snapshot) {
|
|
final fileExists =
|
|
snapshot
|
|
.data !=
|
|
null;
|
|
return Icon(
|
|
fileExists
|
|
? Icons
|
|
.visibility
|
|
: Icons
|
|
.download,
|
|
color: Colors
|
|
.black87,
|
|
size: 20,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
)
|
|
else
|
|
const Text(
|
|
"No documents uploaded",
|
|
style: TextStyle(
|
|
fontFamily: 'Gilroy-Medium',
|
|
color: Color(0xFF6B7280),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
if (data.chatId != null && data.chatId!.isNotEmpty)
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 20,
|
|
vertical: 20,
|
|
),
|
|
child:
|
|
countAsync?.when(
|
|
data: (count) => CommanButton(
|
|
text: "Chat",
|
|
backgroundImage: AppAssets.backgroundimages,
|
|
prefixIcon: messageIconWithBadge(count.count),
|
|
onPressed: () =>
|
|
Get.to(
|
|
() => LiveChatScreen(
|
|
fileid: data.id.toString(),
|
|
chatid: int.parse(
|
|
data.chatId.toString(),
|
|
),
|
|
),
|
|
)?.then((_) {
|
|
ref
|
|
.read(
|
|
notificationTriggerProvider
|
|
.notifier,
|
|
)
|
|
.state++;
|
|
ref.invalidate(chatMessagesProvider);
|
|
}),
|
|
),
|
|
loading: () => CommanButton(
|
|
text: "Chat",
|
|
backgroundImage: AppAssets.backgroundimages,
|
|
prefixIcon: messageIconWithBadge(0),
|
|
onPressed: () =>
|
|
Get.to(
|
|
() => LiveChatScreen(
|
|
fileid: data.id.toString(),
|
|
chatid: int.parse(
|
|
data.chatId.toString(),
|
|
),
|
|
),
|
|
)?.then((_) {
|
|
ref
|
|
.read(
|
|
notificationTriggerProvider
|
|
.notifier,
|
|
)
|
|
.state++;
|
|
ref.invalidate(chatMessagesProvider);
|
|
}),
|
|
),
|
|
error: (_, __) => CommanButton(
|
|
text: "Chat",
|
|
backgroundImage: AppAssets.backgroundimages,
|
|
prefixIcon: messageIconWithBadge(0),
|
|
onPressed: () =>
|
|
Get.to(
|
|
() => LiveChatScreen(
|
|
fileid: data.id.toString(),
|
|
chatid: int.parse(
|
|
data.chatId.toString(),
|
|
),
|
|
),
|
|
)?.then((_) {
|
|
ref
|
|
.read(
|
|
notificationTriggerProvider
|
|
.notifier,
|
|
)
|
|
.state++;
|
|
ref.invalidate(chatMessagesProvider);
|
|
}),
|
|
),
|
|
) ??
|
|
CommanButton(
|
|
text: "Chat",
|
|
backgroundImage: AppAssets.backgroundimages,
|
|
prefixIcon: messageIconWithBadge(0),
|
|
onPressed: () =>
|
|
Get.to(
|
|
() => LiveChatScreen(
|
|
fileid: data.id.toString(),
|
|
chatid: int.parse(data.chatId.toString()),
|
|
),
|
|
)?.then((_) {
|
|
ref
|
|
.read(
|
|
notificationTriggerProvider.notifier,
|
|
)
|
|
.state++;
|
|
ref.invalidate(chatMessagesProvider);
|
|
}),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 150),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// 🔹 Helper Widget for Each Info Row
|
|
Widget _buildInfoRow(String label, String value, double width) {
|
|
return RichText(
|
|
text: TextSpan(
|
|
children: [
|
|
TextSpan(
|
|
text: '$label: ',
|
|
style: TextStyle(
|
|
fontFamily: 'Gilroy-SemiBold',
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: width * 0.04,
|
|
color: const Color(0xFF111827),
|
|
),
|
|
),
|
|
TextSpan(
|
|
text: value.isEmpty ? '—' : value,
|
|
style: TextStyle(
|
|
fontFamily: 'Gilroy-Medium',
|
|
fontWeight: FontWeight.w500,
|
|
fontSize: width * 0.04,
|
|
height: 1.5,
|
|
color: const Color(0xFF374151),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget messageIconWithBadge(int count) {
|
|
return Stack(
|
|
clipBehavior: Clip.none,
|
|
children: [
|
|
const Icon(Icons.message_outlined, color: Colors.white, size: 26),
|
|
|
|
if (count > 0)
|
|
Positioned(
|
|
right: -2,
|
|
top: -2,
|
|
child: Container(
|
|
padding: const EdgeInsets.all(3),
|
|
decoration: const BoxDecoration(
|
|
color: Colors.red,
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Text(
|
|
count.toString(),
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|