import 'dart:convert'; import 'dart:io' show Platform; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:supplier_new/common/settings.dart'; import 'package:supplier_new/resources/source_loctaions_model.dart'; // If you use the PlacePicker flow in the button below, make sure you have // the needed imports in your project (google_maps_flutter, place_picker, etc). // import 'package:google_maps_flutter/google_maps_flutter.dart'; // import 'package:place_picker/place_picker.dart'; // import 'package:location/location.dart'; void main() => runApp(const MaterialApp(home: ResourcesSourceScreen())); class ResourcesSourceScreen extends StatefulWidget { const ResourcesSourceScreen({super.key}); @override State createState() => _ResourcesSourceScreenState(); } class _ResourcesSourceScreenState extends State { int selectedTab = 2; 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']; // Optional: values used by your map flow // final location = Location(); // from 'location' package // PickResult? selectedPlace; double? lat; double? lng; String? address; 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; } @override void initState() { super.initState(); _fetchSources(); } @override void dispose() { _locationNameController.dispose(); _mobileCtrl.dispose(); super.dispose(); } void _resetForm() { _formKey.currentState?.reset(); _locationNameController.clear(); _mobileCtrl.clear(); selectedWaterType = null; // keep address/lat/lng as last-picked unless you want to clear: // address = null; lat = null; lng = null; } Future _fetchSources() async { setState(() => isLoading = true); try { final response = await AppSettings.getSourceLoctaions(); final data = (jsonDecode(response)['data'] as List) .map((e) => SourceLocationsModel.fromJson(e)) .toList(); if (!mounted) return; setState(() { sourceLocationsList = data; isLoading = false; }); } catch (e) { debugPrint("⚠️ Error fetching source locations: $e"); setState(() => isLoading = false); } } Future _addSourceLocation() async { final ok = _formKey.currentState?.validate() ?? false; if (addBusinessAsSource) { // Use supplier's address as the source location address = AppSettings.userAddress; lat = AppSettings.supplierLatitude; lng = AppSettings.supplierLongitude; setState(() {}); } if (!ok || selectedWaterType == null || selectedWaterType!.isEmpty) { if (selectedWaterType == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text("Please select Water Type")), ); } setState(() {}); // ensure errors 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 ok = await AppSettings.addSourceLocations(payload); if (!mounted) return; if (ok) { AppSettings.longSuccessToast("Source location added successfully"); Navigator.pop(context, true); // close the sheet _resetForm(); _fetchSources(); // refresh list } else { AppSettings.longFailedToast("Source location creation failed"); } } catch (e) { debugPrint("⚠️ addSourceLocations error: $e"); if (!mounted) return; AppSettings.longFailedToast("Something went wrong"); } } void _openSourceFormSheet(BuildContext context) { _resetForm(); // fresh form each time you open 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: [ _LabeledField( label: "Location Name *", child: TextFormField( controller: _locationNameController, validator: (v) => _required(v, field: "Location Name"), 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, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(10), ], 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: () { // Your PlacePicker flow goes here (kept commented for reference) /* 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: kInitialPosition, useCurrentLocation: true, 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 { showGeneralDialog( context: context, pageBuilder: (context, x, y) { return Scaffold( backgroundColor: Colors.grey.withOpacity(.5), body: Center( child: Container( width: double.infinity, height: 150, padding: const EdgeInsets.symmetric(horizontal: 20), child: Card( child: Padding( padding: const EdgeInsets.all(10.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text( "Please enable the location", style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500), ), const SizedBox(height: 20), ElevatedButton( onPressed: () { Navigator.pop(context); }, child: const Text("Cancel"), ), ], ), ), ), ), ), ); }, ); } }); */ }, icon: Image.asset('images/Add_icon.png', width: 16, height: 16), label: Text( "Add 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: OutlinedButton.icon( onPressed: _addSourceLocation, icon: Image.asset('images/Add_icon.png', width: 16, height: 16), label: Text( "Add Location", style: fontTextStyle(14, const Color(0xFF646566), FontWeight.w600), ), ), ), const SizedBox(height: 12), 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: _addSourceLocation, child: Text( "Save", style: fontTextStyle(14, Colors.white, FontWeight.w600), ), ), ), ], ), ], ), ), ), ), ), ); }, ); } // ---------- UI ---------- @override Widget build(BuildContext context) { final filtered = sourceLocationsList.where((it) { final q = search.trim().toLowerCase(); if (q.isEmpty) return true; return it.source_name.toLowerCase().contains(q) || it.address.toLowerCase().contains(q); }).toList(); return Scaffold( backgroundColor: Colors.white, body: Column( children: [ // Top card section Container( color: Colors.white, padding: const EdgeInsets.fromLTRB(16, 12, 16, 12), child: Column( children: [ const SizedBox(height: 12), // Total Source Locations card Container( width: double.infinity, padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xFF939495)), ), child: Row( children: [ Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 40, height: 40, decoration: const BoxDecoration( color: Color(0xFFF6F0FF), borderRadius: BorderRadius.all(Radius.circular(8)), ), child: Padding( padding: const EdgeInsets.all(8.0), child: Image.asset('images/drivers.png', fit: BoxFit.contain), ), ), const SizedBox(height: 8), Text( 'Total Source Locations', style: fontTextStyle(12, const Color(0xFF2D2E30), FontWeight.w500), ), ], ), const Spacer(), Column( crossAxisAlignment: CrossAxisAlignment.end, mainAxisSize: MainAxisSize.min, children: [ Text( sourceLocationsList.length.toString(), style: fontTextStyle(24, const Color(0xFF0D3771), FontWeight.w500), ), const SizedBox(height: 6), Text('no recent changes', style: fontTextStyle(10, const Color(0xFF646566), FontWeight.w400)), ], ), ], ), ), const SizedBox(height: 12), IntrinsicHeight( child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: const [ Expanded(child: SmallMetricBox(title: 'Drinking water', value: '2')), SizedBox(width: 8), Expanded(child: SmallMetricBox(title: 'Bore water', value: '3')), SizedBox(width: 8), Expanded(child: SmallMetricBox(title: 'Both', value: '1')), ], ), ), ], ), ), // List section Expanded( child: Container( color: const Color(0xFFF5F5F5), child: Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 0), child: Column( children: [ Row( children: [ SizedBox( width: 270, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( border: Border.all(color: const Color(0xFF939495), width: 0.5), borderRadius: BorderRadius.circular(22), ), child: Row( children: [ Image.asset('images/search.png', width: 20, height: 20), const SizedBox(width: 8), Expanded( child: TextField( decoration: InputDecoration( hintText: 'Search', hintStyle: fontTextStyle(12, const Color(0xFF939495), FontWeight.w400), border: InputBorder.none, isDense: true, ), onChanged: (v) => setState(() => search = v), ), ), ], ), ), ), const SizedBox(width: 16), Image.asset('images/icon_tune.png', width: 24, height: 24), const SizedBox(width: 16), Image.asset('images/up_down arrow.png', width: 24, height: 24), ], ), const SizedBox(height: 12), Expanded( child: isLoading ? const Center(child: CircularProgressIndicator()) : ListView.separated( itemCount: filtered.length, separatorBuilder: (_, __) => const SizedBox(height: 10), itemBuilder: (context, idx) { final it = filtered[idx]; return TankCard( title: it.source_name, subtitle: it.address, ); }, ), ), ], ), ), ), ), ], ), floatingActionButton: FloatingActionButton( onPressed: () => _openSourceFormSheet(context), backgroundColor: const Color(0xFF000000), shape: const CircleBorder(), child: const Icon(Icons.add, color: Colors.white), ), ); } } // ====== SmallMetricBox ====== class SmallMetricBox extends StatelessWidget { final String title; final String value; const SmallMetricBox({super.key, required this.title, required this.value}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xFF939495)), color: Colors.white, ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, maxLines: 1, overflow: TextOverflow.ellipsis, style: fontTextStyle(12, const Color(0xFF2D2E30), FontWeight.w500), ), Text( value, style: fontTextStyle(24, const Color(0xFF0D3771), FontWeight.w500), ), ], ), ); } } // ====== TankCard ====== class TankCard extends StatelessWidget { final String title; final String subtitle; const TankCard({ super.key, required this.title, required this.subtitle, }); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey.shade200), ), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: fontTextStyle(14, const Color(0xFF343637), FontWeight.w600)), const SizedBox(height: 6), Text(subtitle, style: fontTextStyle(10, const Color(0xFF515253), FontWeight.w600)), ], ), ), const SizedBox(width: 8), Container( padding: const EdgeInsets.all(8), decoration: const BoxDecoration( color: Color(0xFFF5F6F6), shape: BoxShape.circle, ), child: Image.asset("images/phone_icon.png", width: 20, height: 20), ), ], ), ); } } // ====== Labeled Field Wrapper ====== class _LabeledField extends StatelessWidget { final String label; final Widget child; const _LabeledField({required this.label, required this.child}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 14.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: fontTextStyle(12, const Color(0xFF515253), FontWeight.w600)), const SizedBox(height: 6), child, ], ), ); } }