import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:supplier_new/resources/source_loctaions_model.dart'; import '../common/first_char_upper.dart'; import '../common/settings.dart'; import '../google_maps_place_picker_mb/src/models/pick_result.dart'; import 'dart:io' show Platform; import 'package:flutter/services.dart'; import '../common/keys.dart'; import '../google_maps_place_picker_mb/src/place_picker.dart'; import 'package:supplier_new/google_maps_place_picker_mb/google_maps_place_picker.dart'; import 'dart:io' show File, Platform; import 'package:google_maps_flutter_android/google_maps_flutter_android.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:location/location.dart' as locationmap; class SourceDetailsScreen extends StatefulWidget { var sourceDetails; var status; SourceDetailsScreen({this.sourceDetails, this.status}); @override State createState() => _SourceDetailsScreenState(); } PickResult? selectedPlace; bool _mapsInitialized = false; final String _mapsRenderer = "latest"; var kInitialPosition = const LatLng(15.462477, 78.717401); locationmap.Location location = locationmap.Location(); final GoogleMapsFlutterPlatform mapsImplementation = GoogleMapsFlutterPlatform.instance; void initRenderer() { if (_mapsInitialized) return; if (mapsImplementation is GoogleMapsFlutterAndroid) { switch (_mapsRenderer) { case "legacy": (mapsImplementation as GoogleMapsFlutterAndroid) .initializeWithRenderer(AndroidMapRenderer.legacy); break; case "latest": (mapsImplementation as GoogleMapsFlutterAndroid) .initializeWithRenderer(AndroidMapRenderer.latest); break; } } // setState(() { // _mapsInitialized = true; // }); } class _SourceDetailsScreenState extends State { final List> recentTrips = [ {"type": "Drinking Water", "liters": "10,000 L", "time": "7:02 PM", "date": "28 Jun 2025"}, {"type": "Drinking Water", "liters": "10,000 L", "time": "7:02 PM", "date": "28 Jun 2025"}, {"type": "Drinking Water", "liters": "10,000 L", "time": "7:02 PM", "date": "28 Jun 2025"}, ]; double _avSize = 80; // avatar diameter double _cardHPad = 16; // horizontal padding double _cardTPad = 16; // top inner padding double _avatarOverlap = 28; // how much avatar rises above the card String search = ''; bool isLoading = false; List sourceLocationsList = []; // ---------- Form state (bottom sheet) ---------- final _formKey = GlobalKey(); final _locationNameController = TextEditingController(); final _mobileCtrl = TextEditingController(); bool addBusinessAsSource = false; // toggle if you want to hide map input String? selectedWaterType; final List waterTypes = const ['Drinking water', 'Bore water', 'Both']; double? lat; double? lng; String? address; @override void initState() { // TODO: implement initState super.initState(); lat=widget.sourceDetails.latitude; lng=widget.sourceDetails.longitude; } String? _required(String? v, {String field = "This field"}) { if (v == null || v.trim().isEmpty) return "$field is required"; return null; } String? _validatePhone(String? v, {String label = "Phone"}) { if (v == null || v.trim().isEmpty) return "$label is required"; if (v.trim().length != 10) return "$label must be 10 digits"; return null; } String? fitToOption(String? incoming, List options) { if (incoming == null) return null; final inc = incoming.trim(); if (inc.isEmpty) return null; final match = options.firstWhere( (o) => o.toLowerCase() == inc.toLowerCase(), orElse: () => '', ); return match.isEmpty ? null : match; // return the exact option string } Future _refreshSourceDetails() async { try { setState(() => isLoading = true); final updatedDetails = await AppSettings.getSourceDetailsById( widget.sourceDetails.dbId, ); if (updatedDetails != null) { setState(() { widget.sourceDetails = updatedDetails; }); } else { AppSettings.longFailedToast("Failed to fetch updated driver details"); } } catch (e) { debugPrint("⚠️ Error refreshing driver details: $e"); AppSettings.longFailedToast("Error refreshing driver details"); } finally { setState(() => isLoading = false); } } Future _updateSourceLocation() async { // run all validators, including the dropdowns final ok = _formKey.currentState?.validate() ?? false; if (!ok) { setState(() {}); // ensure error texts render return; } final payload = { "location_name": _locationNameController.text.trim(), "phone": _mobileCtrl.text.trim(), "water_type": selectedWaterType, "status": 'active', // or whatever string your backend expects "address": address, "city": '', "state": '', "zip": '', "latitude": lat, "longitude": lng, }; try { final bool created = await AppSettings.updateSourceLocations(payload, widget.sourceDetails.dbId); if (!mounted) return; if (created) { AppSettings.longSuccessToast("Source Updated successfully"); Navigator.pop(context, true); // close sheet _refreshSourceDetails(); } else { Navigator.pop(context, true); AppSettings.longFailedToast("failed to update Source details"); } } catch (e) { debugPrint("⚠️ Source error: $e"); if (!mounted) return; AppSettings.longFailedToast("Something went wrong"); } } Future _openSourceFormSheet(BuildContext context)async { if (widget.sourceDetails != null) { _locationNameController.text = widget.sourceDetails.source_name ?? ''; _mobileCtrl.text = widget.sourceDetails.phone ?? ''; selectedWaterType = fitToOption(widget.sourceDetails.water_type, waterTypes); address = widget.sourceDetails.address ?? ''; lat = widget.sourceDetails.latitude; lng = widget.sourceDetails.longitude; } else { _locationNameController.clear(); _mobileCtrl.clear(); selectedWaterType = null; address = null; lat = null; lng = null; } await showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (context) { final kbInset = MediaQuery.of(context).viewInsets.bottom; return FractionallySizedBox( heightFactor: 0.75, // fixed height child: Container( decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), child: Padding( padding: EdgeInsets.fromLTRB(20, 16, 20, 20 + kbInset), child: Form( key: _formKey, child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Row( children: [ Expanded( child: Center( child: Container( width: 86, height: 4, margin: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( color: const Color(0xFFE0E0E0), borderRadius: BorderRadius.circular(2), ), ), ), ), ], ), _LabeledField( label: "Location Name *", child: TextFormField( controller: _locationNameController, validator: (v) => _required(v, field: "Location Name"), textCapitalization: TextCapitalization.none, inputFormatters: const [ FirstCharUppercaseFormatter(), // << live first-letter caps ], decoration: InputDecoration( hintText: "Location Name", hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400), border: const OutlineInputBorder(), isDense: true, ), textInputAction: TextInputAction.next, ), ), _LabeledField( label: "Mobile Number *", child: TextFormField( controller: _mobileCtrl, validator: (v) => _validatePhone(v, label: "Mobile Number"), keyboardType: TextInputType.phone, decoration: InputDecoration( hintText: "Mobile Number", hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400), border: const OutlineInputBorder(), isDense: true, ), textInputAction: TextInputAction.next, ), ), Visibility( visible: !addBusinessAsSource, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ SizedBox( width: double.infinity, child: OutlinedButton.icon( onPressed: () { location.serviceEnabled().then((value) { if (value) { initRenderer(); Navigator.push( context, MaterialPageRoute( builder: (context) { return PlacePicker( resizeToAvoidBottomInset: false, hintText: "Find a place ...", searchingText: "Please wait ...", selectText: "Select place", outsideOfPickAreaText: "Place not in area", initialPosition: (lat != null && lng != null) ? LatLng(lat!, lng!) : kInitialPosition, useCurrentLocation: (lat == null || lng == null), selectInitialPosition: true, usePinPointingSearch: true, usePlaceDetailSearch: true, zoomGesturesEnabled: true, zoomControlsEnabled: true, onMapCreated: (GoogleMapController controller) {}, onPlacePicked: (PickResult result) { setState(() { selectedPlace = result; lat = selectedPlace!.geometry!.location.lat; lng = selectedPlace!.geometry!.location.lng; if (selectedPlace!.types!.length == 1) { address = selectedPlace!.formattedAddress!; } else { address = '${selectedPlace!.name!}, ${selectedPlace!.formattedAddress!}'; } Navigator.of(context).pop(); }); }, onMapTypeChanged: (MapType mapType) {}, apiKey: Platform.isAndroid ? APIKeys.androidApiKey : APIKeys.iosApiKey, forceAndroidLocationManager: true, ); }, ), ); } else { // same dialog code ... } }); }, label: Text( "Change Location on map", style: fontTextStyle(14, const Color(0xFF646566), FontWeight.w600), ), ), ), const SizedBox(height: 12), ], ), ), _LabeledField( label: "Water Type *", child: DropdownButtonFormField( value: selectedWaterType, items: waterTypes .map((w) => DropdownMenuItem(value: w, child: Text(w))) .toList(), onChanged: (v) => setState(() => selectedWaterType = v), validator: (v) => v == null || v.isEmpty ? "Water Type is required" : null, isExpanded: true, alignment: Alignment.centerLeft, hint: Text( "Select Water Type", style: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400), ), icon: Image.asset('images/downarrow.png', width: 16, height: 16), decoration: const InputDecoration( border: OutlineInputBorder(), isDense: false, contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 14), ), ), ), const SizedBox(height: 20), // Actions Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ SizedBox( width: double.infinity, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF8270DB), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), ), onPressed: (){ _updateSourceLocation(); }, child: Text( "Update", style: fontTextStyle(14, Colors.white, FontWeight.w600), ), ), ), ], ), ], ), ), ), ), ), ); }, ); } showDeleteSourceDialog(BuildContext context) async { return showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return AlertDialog( backgroundColor: Color(0XFFFFFFFF),// Set your desired background color shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), // Optional: Rounded corners ), title: Center( child: Text('Delete Driver?' ,style: fontTextStyle(16,Color(0XFF3B3B3B),FontWeight.w600),), ), content: SingleChildScrollView( child: ListBody( children: [ Container( child: Text('Do u want to delete "${widget.sourceDetails.source_name}"',style: fontTextStyle(14,Color(0XFF101214),FontWeight.w600),), ), ], ), ), actions: [ Center( child: Row( mainAxisSize: MainAxisSize.min, children: [ Expanded(child: GestureDetector( onTap: (){ Navigator.pop(context); }, child: Container( decoration: BoxDecoration( shape: BoxShape.rectangle, color: Color(0XFFFFFFFF), border: Border.all( width: 1, color: Color(0XFF1D7AFC)), borderRadius: BorderRadius.circular( 12, )), alignment: Alignment.center, child: Visibility( visible: true, child: Padding( padding: EdgeInsets.fromLTRB(16,12,16,12), child: Text('Cancel', style: fontTextStyle(12, Color(0XFF1D7AFC), FontWeight.w600)), ), ), ), ),), SizedBox(width:MediaQuery.of(context).size.width * .016,), Expanded(child: GestureDetector( onTap: ()async{ bool status = await AppSettings.deleteSource(widget.sourceDetails.dbId,); if(status){ AppSettings.longSuccessToast('Source deleted successfully'); Navigator.of(context).pop(true); Navigator.of(context).pop(true); } else{ Navigator.of(context).pop(true); AppSettings.longFailedToast('Failed to delete Source'); } }, child: Container( decoration: BoxDecoration( shape: BoxShape.rectangle, color: Color(0XFFE2483D), border: Border.all( width: 1, color: Color(0XFFE2483D)), borderRadius: BorderRadius.circular( 12, )), alignment: Alignment.center, child: Visibility( visible: true, child: Padding( padding: EdgeInsets.fromLTRB(16,12,16,12), child: Text( 'Delete', style: fontTextStyle( 12, Color(0XFFFFFFFF), FontWeight.w600)), ), ), ) ),) ], ), ), ], ); }); }, ); } @override Widget build(BuildContext context) { return WillPopScope( onWillPop: () async { // ✅ Return true to indicate successful tanker update refresh Navigator.pop(context, true); return false; // prevent default pop since we manually handled it }, child:Scaffold( backgroundColor: Colors.white, appBar:AppSettings.supplierAppBarWithActionsText( widget.sourceDetails.source_name.isNotEmpty ? widget.sourceDetails.source_name[0].toUpperCase() + widget.sourceDetails.source_name.substring(1) : '', context), body: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height:MediaQuery.of(context).size.height * .1,), // 👤 Driver Profile Card Stack( clipBehavior: Clip.none, children: [ // Main card (give extra top padding so text starts below the avatar) Container( padding: EdgeInsets.fromLTRB( _cardHPad, _cardTPad + _avSize - _avatarOverlap, // space for avatar _cardHPad, 16, ), decoration: const BoxDecoration( color: Color(0xFFF3F1FB), borderRadius: BorderRadius.only( topLeft: Radius.circular(24), topRight: Radius.circular(24), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // status chip (just under avatar, aligned left) Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 6), Text( widget.sourceDetails.source_name, style: fontTextStyle( 20, Color(0XFF2D2E30), FontWeight.w500), ), const SizedBox(height: 2), Text( "+91 "+widget.sourceDetails.phone, style: fontTextStyle( 12, Color(0XFF646566), FontWeight.w400), ), const SizedBox(height: 2), Text( widget.sourceDetails.address, style: fontTextStyle( 12, Color(0XFF646566), FontWeight.w400), ), ], ),), PopupMenuButton( // 🔁 Use `child:` so you can place any widget (your 3-dots image) child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0), child: Image.asset( 'images/popup_menu.png', // your 3-dots image width: 22, height: 22, // If you want to tint it like an icon: color: Color(0XFF939495), // remove if you want original colors colorBlendMode: BlendMode.srcIn, ), ), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), offset: const Offset(0, 40), color: Colors.white, elevation: 4, onSelected: (value) { if (value == 'edit') { _openSourceFormSheet(context); } else if (value == 'disable') { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Disable selected')), ); } else if (value == 'delete') { showDeleteSourceDialog(context); } }, itemBuilder: (context) => [ PopupMenuItem( value: 'edit', child: Row( children: [ Image.asset( 'images/edit.png', width: 20, height: 20, color: Color(0XFF646566), // tint (optional) colorBlendMode: BlendMode.srcIn, ), const SizedBox(width: 12), Text( 'Edit', style: fontTextStyle(14, const Color(0XFF646566), FontWeight.w400), ), ], ), ), PopupMenuItem( value: 'disable', child: Row( children: [ Image.asset( 'images/disable.png', width: 20, height: 20, color: Color(0XFF646566), // tint (optional) colorBlendMode: BlendMode.srcIn, ), const SizedBox(width: 12), Text( 'Disable', style: fontTextStyle(14, const Color(0XFF646566), FontWeight.w400), ), ], ), ), PopupMenuItem( value: 'delete', child: Row( children: [ Image.asset( 'images/delete.png', width: 20, height: 20, color: Color(0XFFE2483D), // red like your example colorBlendMode: BlendMode.srcIn, ), const SizedBox(width: 12), Text( 'Delete', style: fontTextStyle(14, const Color(0xFFE2483D), FontWeight.w400), ), ], ), ), ], ) ], ), const SizedBox(height: 8), ], ), ), // Floating avatar (top-left, overlapping the card) Positioned( top: -_avatarOverlap, left: _cardHPad, child: ClipRRect( borderRadius: BorderRadius.circular(_avSize / 2), child: Image.asset( 'images/avatar.png', width: _avSize, height: _avSize, fit: BoxFit.cover, ), ), ), // Call icon aligned with the card’s top-right /*Positioned( top: _cardTPad - _avatarOverlap + 4, // aligns near chip row right: _cardHPad - 4, child: IconButton( onPressed: () { // 📞 Call action }, icon: const Icon(Icons.call, color: Color(0xFF4F46E5)), ), ),*/ ], ), const SizedBox(height: 12), // Filling & Wait time cards Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ Expanded( child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0xFFF7F7F7), borderRadius: BorderRadius.circular(12), ), child: Column( children: [ Text("12", style: fontTextStyle(20, Colors.black, FontWeight.w600)), Text("sec/L", style: fontTextStyle(14, Colors.black, FontWeight.w400)), const SizedBox(height: 4), Text("Filling Time", style: fontTextStyle(12, Colors.grey, FontWeight.w400)), ], ), ), ), const SizedBox(width: 12), Expanded( child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0xFFF7F7F7), borderRadius: BorderRadius.circular(12), ), child: Column( children: [ Text("24", style: fontTextStyle(20, Colors.black, FontWeight.w600)), Text("min", style: fontTextStyle(14, Colors.black, FontWeight.w400)), const SizedBox(height: 4), Text("Wait Time", style: fontTextStyle(12, Colors.grey, FontWeight.w400)), ], ), ), ), ], ), ), const SizedBox(height: 20), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Text("Recent Trips", style: fontTextStyle(16, Colors.black, FontWeight.w600)), ), const SizedBox(height: 8), ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: recentTrips.length, itemBuilder: (context, index) { final trip = recentTrips[index]; return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xFFFFFFFF), borderRadius: BorderRadius.circular(8), border: Border.all(color: Color(0XFFC3C4C4)), ), child: Row( children: [ Image.asset('images/recent_trips.png', width: 28, height: 28), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "${trip['type']} - ${trip['liters']}", style: fontTextStyle( 14, const Color(0xFF2D2E30), FontWeight.w500), ), Text( "${trip['time']}, ${trip['date']}", style: fontTextStyle( 12, const Color(0xFF656565), FontWeight.w400), ), ], ) ), ], ), ), ); }, ), const SizedBox(height: 20), ],)))); } } class _LabeledField extends StatelessWidget { final String label; final Widget child; const _LabeledField({required this.label, required this.child}); String _capFirstWord(String input) { if (input.isEmpty) return input; final i = input.indexOf(RegExp(r'\S')); if (i == -1) return input; return input.replaceRange(i, i + 1, input[i].toUpperCase()); } @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 14.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _capFirstWord(label), style: fontTextStyle(12, const Color(0xFF515253), FontWeight.w600), ), const SizedBox(height: 6), child, ], ), ); } }