diff --git a/lib/profile/availability.dart b/lib/profile/availability.dart index cb4e45c..dadc633 100644 --- a/lib/profile/availability.dart +++ b/lib/profile/availability.dart @@ -9,8 +9,10 @@ void main() => runApp(const MaterialApp(home: AvailabilityScreen())); class AvailabilityScreen extends StatefulWidget { const AvailabilityScreen({super.key}); + @override _AvailabilityScreenState createState() => _AvailabilityScreenState(); + } class _AvailabilityScreenState extends State { @@ -23,6 +25,7 @@ class _AvailabilityScreenState extends State { TimeOfDay? _weekdayEndTime; TimeOfDay? _weekendStartTime; TimeOfDay? _weekendEndTime; + int currentStep = 3; final List _days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; @@ -93,17 +96,17 @@ class _AvailabilityScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ // Step indicator - Text('Step 5/5', - style: fontTextStyle(14, Colors.grey, FontWeight.normal)), - const SizedBox(height: 4), + Text("Step $currentStep/5",), + Row( - children: List.generate(4, (index) { + children: List.generate(5, (index) { + final isFilled = index < currentStep; return Expanded( child: Container( margin: const EdgeInsets.symmetric(horizontal: 2), height: 5, decoration: BoxDecoration( - color: index < 4 ? const Color(0xFFC3C4C4) : Colors.grey, + color: isFilled ? const Color(0xFF0D3771) : const Color(0xFFE6E6E6), borderRadius: BorderRadius.circular(2), ), ), diff --git a/lib/profile/employees.dart b/lib/profile/employees.dart index c6247b0..602845a 100644 --- a/lib/profile/employees.dart +++ b/lib/profile/employees.dart @@ -1,7 +1,12 @@ +import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:supplier_new/common/settings.dart'; - +import 'package:supplier_new/profile/source_location.dart'; +import '../resources/driver_details.dart'; +import '../resources/drivers_model.dart'; +import '../resources/resources_drivers.dart'; +import 'availability.dart'; class FleetEmployees extends StatefulWidget { const FleetEmployees({super.key}); @@ -11,14 +16,16 @@ class FleetEmployees extends StatefulWidget { } class _FleetEmployeesState extends State { - final _formKey = GlobalKey(); + bool isLoading = false; + int currentStep = 1; + List driversList = []; - // Controllers + // Controllers for adding new driver + final _formKey = GlobalKey(); final _nameCtrl = TextEditingController(); final _mobileCtrl = TextEditingController(); final _altMobileCtrl = TextEditingController(); - // Dropdowns final List licenseNumbers = [ "UP3220050012345", "UP3220050012355", @@ -30,10 +37,30 @@ class _FleetEmployeesState extends State { final List yearOptions = ["1", "2", "3", "4", "5"]; String? selectedExperience; - // Data bucket - final List> _drivers = []; + @override + void initState() { + super.initState(); + _fetchDrivers(); + } + + Future _fetchDrivers() async { + setState(() => isLoading = true); + try { + final response = await AppSettings.getDrivers(); + final data = (jsonDecode(response)['data'] as List) + .map((e) => DriversModel.fromJson(e)) + .toList(); + if (!mounted) return; + setState(() { + driversList = data; + isLoading = false; + }); + } catch (e) { + debugPrint("⚠️ Error fetching drivers: $e"); + setState(() => isLoading = false); + } + } - // Validators String? _required(String? v, {String field = "This field"}) { if (v == null || v.trim().isEmpty) return "$field is required"; return null; @@ -49,22 +76,6 @@ class _FleetEmployeesState extends State { return null; } - @override - void dispose() { - _nameCtrl.dispose(); - _mobileCtrl.dispose(); - _altMobileCtrl.dispose(); - super.dispose(); - } - - Map _buildPayload() => { - "driver_name": _nameCtrl.text.trim(), - "license_number": selectedLicense, - "experience_years": selectedExperience, - "phone": _mobileCtrl.text.trim(), - "alt_phone": _altMobileCtrl.text.trim(), - }; - void _clearForm() { _nameCtrl.clear(); _mobileCtrl.clear(); @@ -74,51 +85,42 @@ class _FleetEmployeesState extends State { setState(() {}); } - void _addDriver() async{ - final ok = _formKey.currentState?.validate() ?? false; - setState(() {}); // ensure error texts render - if (!ok || - selectedLicense == null || - selectedExperience == null || - selectedExperience!.isEmpty) { - if (selectedExperience == null) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text("Please select License & Experience")), - ); - } + Future _addDriver() async { + if (!(_formKey.currentState?.validate() ?? false)) return; + if (selectedLicense == null || selectedExperience == null) { + AppSettings.longFailedToast("Select License & Experience"); return; } - var payload = new Map(); - payload["Name"] = _nameCtrl.text.toString(); - payload["license_number"] =selectedLicense.toString(); - payload["address"] = AppSettings.userAddress; - payload["supplier_name"] = AppSettings.userName; - payload["phone"] = _mobileCtrl.text.toString(); - payload["alternativeContactNumber"] =''; - payload["years_of_experience"] =selectedExperience.toString(); - payload["status"] = 'string'; + var payload = { + "Name": _nameCtrl.text.trim(), + "license_number": selectedLicense, + "address": AppSettings.userAddress, + "supplier_name": AppSettings.userName, + "phone": _mobileCtrl.text.trim(), + "alternativeContactNumber": _altMobileCtrl.text.trim(), + "years_of_experience": selectedExperience, + "status": "active", + }; + + bool status = await AppSettings.addDrivers(payload); + if (status) { + AppSettings.longSuccessToast("Driver Added Successfully"); + _clearForm(); + Navigator.pop(context, true); + _fetchDrivers(); + } else { + AppSettings.longFailedToast("Failed to add driver"); + } + } - - bool tankStatus = await AppSettings.addDrivers(payload); - - try { - if (tankStatus) { - AppSettings.longSuccessToast("Tanker Created Successfully"); - _nameCtrl.text = ''; - Navigator.pop(context,true); - - } - else { - AppSettings.longFailedToast("Tanker Creation failed"); - } - } catch (exception) { - print(exception); + void _onContinue() { + if (currentStep < 5) { + setState(() => currentStep += 1); + } else { + // You can navigate to next screen here + // Navigator.push(context, MaterialPageRoute(builder: (_) => const NextScreen())); } - _clearForm(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Driver added (${_drivers.length})")), - ); } @override @@ -132,305 +134,353 @@ class _FleetEmployeesState extends State { scrolledUnderElevation: 0, title: const Text("Complete Profile"), actions: [ - Padding( - padding: const EdgeInsets.fromLTRB(10, 10, 0, 10), - child: IconButton( - splashRadius: 20, - padding: EdgeInsets.zero, - icon: const Image(image: AssetImage('images/calendar_appbar.png'), width: 22, height: 22), - onPressed: () {}, + IconButton( + splashRadius: 20, + icon: const Image( + image: AssetImage('images/calendar_appbar.png'), + width: 22, + height: 22, ), + onPressed: () {}, ), - Padding( - padding: const EdgeInsets.fromLTRB(0, 10, 10, 10), - child: IconButton( - splashRadius: 20, - padding: EdgeInsets.zero, - icon: Image.asset('images/notification_appbar.png', width: 22, height: 22), - onPressed: () {}, - ), + IconButton( + splashRadius: 20, + icon: Image.asset('images/notification_appbar.png', + width: 22, height: 22), + onPressed: () { + + }, ), ], ), body: SafeArea( - child: Form( - key: _formKey, - child: ListView( - padding: const EdgeInsets.fromLTRB(20, 10, 20, 24), - children: [ - // Step indicator - Text( - "Step 1/5", - style: fontTextStyle(16, const Color(0xFFC3C4C4), FontWeight.w500), - ), - const SizedBox(height: 16), - - // Header block - Column( + child: isLoading + ? const Center(child: CircularProgressIndicator()) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header + Padding( + padding: const EdgeInsets.fromLTRB(20, 10, 20, 0), + child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("EMPLOYEES", style: fontTextStyle(20, const Color(0xFF515253), FontWeight.w600)), + Text("Step $currentStep/5",), + + Row( + children: List.generate(5, (index) { + final isFilled = index < currentStep; + return Expanded( + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 2), + height: 5, + decoration: BoxDecoration( + color: isFilled ? const Color(0xFF0D3771) : const Color(0xFFE6E6E6), + borderRadius: BorderRadius.circular(2), + ), + ), + ); + }), + ), + const SizedBox(height: 16), + Text("EMPLOYEES", + style: fontTextStyle(20, Color(0xFF515253), FontWeight.w600)), const SizedBox(height: 8), - Container( - width: 24, - height: 24, - decoration: const BoxDecoration( - image: DecorationImage(image: AssetImage('images/manage-users.png'), fit: BoxFit.contain), - ), + Image.asset('images/manage-users.png', width: 24, height: 24), + const SizedBox(height: 8), + Text( + "Details about your driver fleet", + style: fontTextStyle(14, Color(0xFF939495), FontWeight.w500), ), ], ), - const SizedBox(height: 6), - Text( - "Details about your water tanker fleet", - style: fontTextStyle(14, const Color(0xFF939495), FontWeight.w500), - ), - const SizedBox(height: 16), - - // Section header (just the bar) - _SectionHeaderBar( - title: "DRIVER #1", - icon: Image.asset('images/arrow-up.png', width: 16, height: 16), - radius: 20, - ), - const SizedBox(height: 12), - - // === Fields - _LabeledField( - label: "Driver Name *", - child: TextFormField( - controller: _nameCtrl, - validator: (v) => _required(v, field: "Driver Name"), - decoration: const InputDecoration( - hintText: "Full Name", - border: OutlineInputBorder(), - isDense: true, - ), - textInputAction: TextInputAction.next, + ), + const SizedBox(height: 16), + + // Driver list + Expanded( + child: driversList.isEmpty + ? Center( + child: Text( + "No drivers added yet.", + style: fontTextStyle( + 14, const Color(0xFF939495), FontWeight.w400), ), + ) + + : ListView.separated( + padding: const EdgeInsets.fromLTRB(16, 4, 16, 100), + itemCount: driversList.length, + separatorBuilder: (_, __) => const SizedBox(height: 8), + itemBuilder: (context, idx) { + final d = driversList[idx]; + bool expanded = false; + + return StatefulBuilder( + builder: (context, setInnerState) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 0), + decoration: BoxDecoration( + color: Color(0xFFF1F1F1), // background color + border: Border.all(color: Color(0xFFE5E5E5)), + borderRadius: BorderRadius.circular(29), + + ), + child: Column( + children: [ + + ListTile( + dense: true, // makes tile shorter + contentPadding: EdgeInsets.zero, // removes default horizontal padding + minVerticalPadding: 0, + visualDensity: const VisualDensity(vertical: -4, horizontal: 0), + title: Text( + d.driver_name ?? 'Unnamed Driver', + style: fontTextStyle(14, const Color(0xFF2D2E30), FontWeight.w600), + ), + + trailing: IconButton( + padding: EdgeInsets.zero, + constraints: const BoxConstraints(minWidth: 32, minHeight: 32), + icon: Image.asset( + expanded ? 'images/arrow-up.png' : 'images/downarrow.png', + width: 18, + height: 18, + ), + onPressed: () => setInnerState(() { + expanded = !expanded; + }), + ), + ), + if (expanded) + Align( + alignment: Alignment.centerLeft, + child: Container( + margin: const EdgeInsets.only(left: 10, right: 10, bottom: 6), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), + decoration: BoxDecoration( + color: Color(0xFFFFFFFF), // 👈 white background + border: Border.all(color:Color(0xFFFFFFFF)), // light border + borderRadius: BorderRadius.circular(8), // smooth rounded edges + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "${d.address ?? 'N/A'} : " " ${d.phone_number ?? 'N/A'}", + style: fontTextStyle(12, const Color(0xFF2D2E30), FontWeight.w500), + ), + const SizedBox(height: 4), + + ], + ), + ), + + ), ], + ), + + ); + }, + ); + }, ), + ) - _LabeledField( - label: "Driver License Number *", - child: DropdownButtonFormField( - value: selectedLicense, - items: licenseNumbers - .map((t) => DropdownMenuItem(value: t, child: Text(t))) - .toList(), - onChanged: (v) => setState(() => selectedLicense = v), - validator: (v) => v == null || v.isEmpty ? "Driver License required" : null, - isExpanded: true, - alignment: Alignment.centerLeft, - hint: Text( - "Select License Number", - 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), - ), - ), - ), + ], + ), + ), - _LabeledField( - label: "Years of Experience *", - child: DropdownButtonFormField( - value: selectedExperience, - items: yearOptions - .map((t) => DropdownMenuItem(value: t, child: Text(t))) - .toList(), - onChanged: (v) => setState(() => selectedExperience = v), - validator: (v) => v == null || v.isEmpty ? "Experience is required" : null, - isExpanded: true, - alignment: Alignment.centerLeft, - hint: Text( - "Years", - 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), - ), + // Bottom buttons + bottomSheet: Container( + color: Colors.white, + padding: + const EdgeInsets.symmetric(horizontal: 20).copyWith(bottom: 24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: double.infinity, + child: OutlinedButton.icon( + onPressed: () { + _openAddDriverSheet(context); + }, + icon: Image.asset('images/Add_icon.png', + width: 16, height: 16), + label: Text( + "Add Driver", + style: fontTextStyle( + 14, const Color(0xFF646566), FontWeight.w600), ), ), - - _LabeledField( - label: "Phone Number *", - child: TextFormField( - controller: _mobileCtrl, - validator: (v) => _validatePhone(v, label: "Phone 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, + ), + 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)), ), - ), - - _LabeledField( - label: "Alternate Phone Number", - child: TextFormField( - controller: _altMobileCtrl, - validator: (v) { - if (v == null || v.trim().isEmpty) return null; // optional - return _validatePhone(v, label: "Alternate Phone 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, + onPressed: () { + Navigator.push(context, MaterialPageRoute(builder: (_) => const SourceLocation())); + }, + child: Text( + "Continue", + style: fontTextStyle(14, Colors.white, FontWeight.w400), ), ), + ), + ], + ), + ), + ); + } - const SizedBox(height: 20), - - // Actions - Column( - crossAxisAlignment: CrossAxisAlignment.stretch, + // Bottom sheet for Add Driver form + void _openAddDriverSheet(BuildContext context) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.white, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(24)), + ), + builder: (_) { + return Padding( + padding: + MediaQuery.of(context).viewInsets.add(const EdgeInsets.all(20)), + child: SingleChildScrollView( + child: Form( + key: _formKey, + child: Column( + mainAxisSize: MainAxisSize.min, children: [ - SizedBox( - width: double.infinity, - child: OutlinedButton.icon( - onPressed: _addDriver, - icon: Image.asset('images/Add_icon.png', width: 16, height: 16), - label: Text( - "Add Driver", - style: fontTextStyle(14, const Color(0xFF646566), FontWeight.w600), + Text("Add New Driver", + style: fontTextStyle( + 18, const Color(0xFF2D2E30), FontWeight.w600)), + const SizedBox(height: 16), + _LabeledField( + label: "Driver Name *", + child: TextFormField( + controller: _nameCtrl, + validator: (v) => _required(v, field: "Driver Name"), + textCapitalization: TextCapitalization.none, + inputFormatters: const [ + FirstCharUppercaseFormatter(), // << live first-letter caps + ], + decoration: const InputDecoration( + hintText: "Full Name", + border: OutlineInputBorder(), + isDense: true, ), ), ), - 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)), + _LabeledField( + label: "License Number *", + child: DropdownButtonFormField( + value: selectedLicense, + items: licenseNumbers + .map((t) => + DropdownMenuItem(value: t, child: Text(t))) + .toList(), + onChanged: (v) => setState(() => selectedLicense = v), + validator: (v) => v == null ? "Select License" : null, + decoration: const InputDecoration( + border: OutlineInputBorder(), + isDense: true, ), - onPressed: () { - // // TODO: Navigate to the next step/screen - // Navigator.push(context, MaterialPageRoute(builder: (_) => const SourceLocation())); - // // ScaffoldMessenger.of(context).showSnackBar( - // // SnackBar(content: Text("Saved ${_drivers.length} driver(s). Proceeding…")), - // // ); - }, - child: Text( - "Continue", - style: fontTextStyle(14, Colors.white, FontWeight.w400), + ), + ), + _LabeledField( + label: "Years of Experience *", + child: DropdownButtonFormField( + value: selectedExperience, + items: yearOptions + .map((t) => + DropdownMenuItem(value: t, child: Text(t))) + .toList(), + onChanged: (v) => setState(() => selectedExperience = v), + validator: (v) => + v == null ? "Select Experience" : null, + decoration: const InputDecoration( + border: OutlineInputBorder(), + isDense: true, + ), + ), + ), + _LabeledField( + label: "Phone Number *", + child: TextFormField( + controller: _mobileCtrl, + validator: (v) => _validatePhone(v), + keyboardType: TextInputType.phone, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(10), + ], + decoration: const InputDecoration( + hintText: "Mobile Number", + border: OutlineInputBorder(), + isDense: true, ), ), ), + _LabeledField( + label: "Alternate Phone Number", + child: TextFormField( + controller: _altMobileCtrl, + keyboardType: TextInputType.phone, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(10), + ], + decoration: const InputDecoration( + hintText: "Mobile Number", + border: OutlineInputBorder(), + isDense: true, + ), + ), + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: _addDriver, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF0D3771), + foregroundColor: Colors.white), + child: const Text("Add Driver"), + ), ], ), - ], - ), - ), - ), - ); - } -} - -// ======= UI helpers ======= - -class _SectionHeaderBar extends StatelessWidget { - final String title; - final Widget? icon; - final Color backgroundColor; - final Color borderColor; - final double radius; - - const _SectionHeaderBar({ - required this.title, - this.icon, - this.backgroundColor = const Color(0xFFEEEEEE), - this.borderColor = const Color(0xFFE5E7EB), - this.radius = 12, - Key? key, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - color: backgroundColor, - border: Border.all(color: borderColor, width: 1), - borderRadius: BorderRadius.circular(radius), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.04), - blurRadius: 6, - offset: const Offset(0, 2), - ), - ], - ), - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), - child: Row( - children: [ - Expanded( - child: Text( - title, - style: fontTextStyle(12, const Color(0xFF2D2E30), FontWeight.w600), ), ), - if (icon != null) icon!, - ], - ), + ); + }, ); } } +// ============ UI Helper ============== class _LabeledField extends StatelessWidget { final String label; final Widget child; - final String? Function()? validator; // (kept from your earlier helper; not used here) - const _LabeledField({ - required this.label, - required this.child, - this.validator, - }); + const _LabeledField({required this.label, required this.child}); @override Widget build(BuildContext context) { - final errorText = validator != null ? validator!() : null; - return Padding( - padding: const EdgeInsets.only(bottom: 14.0), + padding: const EdgeInsets.only(bottom: 14), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(label, style: fontTextStyle(12, const Color(0xFF515253), FontWeight.w600)), + Text(label, + style: + fontTextStyle(12, const Color(0xFF515253), FontWeight.w600)), const SizedBox(height: 6), child, - if (errorText != null) - Padding( - padding: const EdgeInsets.only(top: 6), - child: Text( - errorText, - style: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400), - ), - ), ], ), ); diff --git a/lib/profile/fleet.dart b/lib/profile/fleet.dart index 1b5501b..e8f0a99 100644 --- a/lib/profile/fleet.dart +++ b/lib/profile/fleet.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:supplier_new/common/settings.dart'; +import '../resources/resources_drivers.dart'; import 'employees.dart'; class FleetStep1Page extends StatefulWidget { @@ -16,6 +17,8 @@ class _FleetStep1PageState extends State { bool isLoading = false; List tankersList = []; String search = ''; + int currentStep = 1; // 1..5 + @override void initState() { @@ -43,6 +46,7 @@ class _FleetStep1PageState extends State { showModalBottomSheet( context: context, isScrollControlled: true, + backgroundColor: Color(0xFFFFFFFF), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), @@ -98,9 +102,24 @@ class _FleetStep1PageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Step 2/5", + Text("Step 1/5", style: fontTextStyle(16, Color(0xFFC3C4C4), FontWeight.w500)), const SizedBox(height: 16), + Row( + children: List.generate(5, (index) { + return Expanded( + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 2), + height: 5, + decoration: BoxDecoration( + color: index < 5 ? const Color(0xFFC3C4C4) : Colors.grey, + borderRadius: BorderRadius.circular(2), + ), + ), + ); + }), + ), + const SizedBox(height: 16), Text("FLEET", style: fontTextStyle(20, Color(0xFF515253), FontWeight.w600)), const SizedBox(height: 8), @@ -313,7 +332,10 @@ class _AddTankerFormState extends State { AppSettings.longFailedToast("Tanker Creation Failed"); } } - + String? _required(String? v, {String field = "This field"}) { + if (v == null || v.trim().isEmpty) return "$field is required"; + return null; + } @override Widget build(BuildContext context) { return Padding( @@ -333,24 +355,40 @@ class _AddTankerFormState extends State { label: "Tanker Name *", child: TextFormField( controller: _nameCtrl, - validator: (v) => v == null || v.isEmpty ? "Required" : null, - decoration: - const InputDecoration(border: OutlineInputBorder(), isDense: true), + validator: (v) => _required(v, field: "Tanker Name"), + textCapitalization: TextCapitalization.none, + inputFormatters: const [ + FirstCharUppercaseFormatter(), // << live first-letter caps + ], + decoration: InputDecoration( + hintText: "Enter Tanker Name", + hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400), + border: const OutlineInputBorder(), + isDense: true, + ), + textInputAction: TextInputAction.next, ), ), + _LabeledField( - label: "Capacity (in L) *", + label: "Tanker Capacity (in L) *", child: TextFormField( controller: _capacityCtrl, - validator: (v) => v == null || v.isEmpty ? "Required" : null, + validator: (v) => _required(v, field: "Tanker Capacity"), + decoration: InputDecoration( + hintText: "10,000", + hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400), + border: const OutlineInputBorder(), + isDense: true, + ), keyboardType: TextInputType.number, inputFormatters: [ - FilteringTextInputFormatter.allow(RegExp(r'[0-9,]')) + FilteringTextInputFormatter.allow(RegExp(r'[0-9,]')), ], - decoration: - const InputDecoration(border: OutlineInputBorder(), isDense: true), + textInputAction: TextInputAction.next, ), ), + _LabeledField( label: "Tanker Type *", child: DropdownButtonFormField( @@ -359,55 +397,94 @@ class _AddTankerFormState extends State { .map((t) => DropdownMenuItem(value: t, child: Text(t))) .toList(), onChanged: (v) => setState(() => selectedType = v), - validator: (v) => v == null ? "Required" : null, - decoration: - const InputDecoration(border: OutlineInputBorder(), isDense: true), + validator: (v) => v == null || v.isEmpty ? "Tanker Type is required" : null, + isExpanded: true, + alignment: Alignment.centerLeft, + hint: Text( + "Select 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), + ), ), ), + _LabeledField( - label: "Type of Water *", + label: "Type of water *", child: DropdownButtonFormField( value: selectedTypeOfWater, items: typeOfWater .map((t) => DropdownMenuItem(value: t, child: Text(t))) .toList(), onChanged: (v) => setState(() => selectedTypeOfWater = v), - validator: (v) => v == null ? "Required" : null, - decoration: - const InputDecoration(border: OutlineInputBorder(), isDense: true), + validator: (v) => v == null || v.isEmpty ? "Type of water is required" : null, + isExpanded: true, + alignment: Alignment.centerLeft, + hint: Text( + "Select type of water", + 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), + ), ), ), + _LabeledField( label: "License Plate *", child: TextFormField( controller: _plateCtrl, - validator: (v) => v == null || v.isEmpty ? "Required" : null, + validator: (v) => _required(v, field: "License Plate"), + decoration: InputDecoration( + hintText: "AB 05 H 4948", + hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400), + border: const OutlineInputBorder(), + isDense: true, + ), textCapitalization: TextCapitalization.characters, - decoration: - const InputDecoration(border: OutlineInputBorder(), isDense: true), + textInputAction: TextInputAction.next, ), ), + _LabeledField( - label: "Manufacturing Year", + label: "Manufacturing Year (opt)", child: TextFormField( controller: _mfgYearCtrl, + decoration: InputDecoration( + hintText: "YYYY", + hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400), + border: const OutlineInputBorder(), + isDense: true, + ), keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, - LengthLimitingTextInputFormatter(4) + LengthLimitingTextInputFormatter(4), ], - decoration: - const InputDecoration(border: OutlineInputBorder(), isDense: true), + textInputAction: TextInputAction.next, ), ), + _LabeledField( - label: "Insurance Expiry Date", + label: "Insurance Expiry Date (opt)", child: TextFormField( controller: _insExpiryCtrl, readOnly: true, + decoration: InputDecoration( + hintText: "DD-MM-YYYY", + hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400), + border: const OutlineInputBorder(), + isDense: true, + suffixIcon: const Icon(Icons.calendar_today_outlined, size: 18), + ), onTap: _pickInsuranceDate, - decoration: - const InputDecoration(border: OutlineInputBorder(), isDense: true), ), ), const SizedBox(height: 16), diff --git a/lib/profile/source_location.dart b/lib/profile/source_location.dart index 9592fae..4a8909a 100644 --- a/lib/profile/source_location.dart +++ b/lib/profile/source_location.dart @@ -1,5 +1,5 @@ +import 'dart:convert'; import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; @@ -8,12 +8,12 @@ import '../common/keys.dart'; import '../google_maps_place_picker_mb/src/models/pick_result.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; +import '../resources/resources_drivers.dart'; +import '../resources/source_loctaions_model.dart'; -void main() => runApp(const MaterialApp(home: SourceLocation())); class SourceLocation extends StatefulWidget { const SourceLocation({super.key}); @@ -22,56 +22,44 @@ class SourceLocation extends StatefulWidget { State createState() => _SourceLocationState(); } -class _SourceLocationState extends State { - final _formKey = GlobalKey(); - - // Controllers - final _locationNameController = TextEditingController(); - final _mobileCtrl = TextEditingController(); - String address=''; - - String address1 = ''; - String address2 = ''; - String city = ''; - String state = ''; - String zip = ''; - String country = ''; - double lat=0; - double lng=0; +PickResult? selectedPlace; - PickResult? selectedPlace; +bool _mapsInitialized = false; +final String _mapsRenderer = "latest"; - bool _mapsInitialized = false; - final String _mapsRenderer = "latest"; - - var kInitialPosition = const LatLng(15.462477, 78.717401); +var kInitialPosition = const LatLng(15.462477, 78.717401); - locationmap.Location location = locationmap.Location(); +locationmap.Location location = locationmap.Location(); - final GoogleMapsFlutterPlatform mapsImplementation = - GoogleMapsFlutterPlatform.instance; +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; - } +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; - }); } + // setState(() { + // _mapsInitialized = true; + // }); +} +class _SourceLocationState extends State { + bool isLoading = false; + int currentStep = 2; + List sourceLocationsList = []; + final _locationNameController = TextEditingController(); + bool addBusinessAsSource = false; - // Dropdowns final List waterTypes = [ "Drinking Water", "Bore Water", @@ -79,514 +67,390 @@ class _SourceLocationState extends State { "Construction", "Non-potable", ]; - String? selectedWaterType; - - // Data bucket - final List> _drivers = []; - // Validators - String? _required(String? v, {String field = "This field"}) { - if (v == null || v.trim().isEmpty) return "$field is required"; - return null; - } + // For map + bool _mapsInitialized = false; + final GoogleMapsFlutterPlatform mapsImplementation = + GoogleMapsFlutterPlatform.instance; + final locationmap.Location location = locationmap.Location(); + var kInitialPosition = const LatLng(15.462477, 78.717401); - String? _validatePhone(String? v, {String label = "Phone Number"}) { - if (v == null || v.trim().isEmpty) return "$label is required"; - final digits = v.replaceAll(RegExp(r'\D'), ''); - if (digits.length != 10) return "Enter a 10-digit $label"; - if (!RegExp(r'^[6-9]\d{9}$').hasMatch(digits)) { - return "$label must start with 6/7/8/9"; + void initRenderer() { + if (_mapsInitialized) return; + if (mapsImplementation is GoogleMapsFlutterAndroid) { + (mapsImplementation as GoogleMapsFlutterAndroid) + .initializeWithRenderer(AndroidMapRenderer.latest); } - return null; + setState(() => _mapsInitialized = true); } - @override - void dispose() { - _locationNameController.dispose(); - _mobileCtrl.dispose(); - super.dispose(); - } - bool addBusinessAsSource = false; - void _clearForm() { - _locationNameController.clear(); - _mobileCtrl.clear(); - selectedWaterType = null; - setState(() {}); + @override + void initState() { + super.initState(); + _fetchSources(); } - void _addSourceLocation() async{ - final ok = _formKey.currentState?.validate() ?? false; - - if(addBusinessAsSource){ + 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(() { - address=AppSettings.userAddress; - lat=AppSettings.supplierLatitude; - lng=AppSettings.supplierLongitude; + sourceLocationsList = data; + isLoading = false; }); + } catch (e) { + debugPrint("⚠️ Error fetching source locations: $e"); + setState(() => isLoading = false); } - - setState(() {}); // ensure error texts render - if (!ok || - selectedWaterType == null || - selectedWaterType!.isEmpty) { - if (selectedWaterType == null) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text("Please select Water type")), - ); - } - return; - } - var payload = new Map(); - payload["location_name"] = _locationNameController.text.toString(); - payload["phone"] = _mobileCtrl.text.toString(); - payload["water_type"] =selectedWaterType.toString(); - payload["status"] ='string'; - payload["address"] = address; - payload["city"] = ''; - payload["state"] = ''; - payload["zip"] =''; - payload["latitude"] = lat; - payload["longitude"] = lng; - - - - bool tankStatus = await AppSettings.addSourceLocations(payload); - - try { - if (tankStatus) { - AppSettings.longSuccessToast("Source location added Successfully"); - _locationNameController.text = ''; - Navigator.pop(context,true); - - } - else { - AppSettings.longFailedToast("Tanker Creation failed"); - } - } catch (exception) { - print(exception); - } - _clearForm(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Driver added (${_drivers.length})")), - ); } - - @override - Widget build(BuildContext context) { - return Scaffold( + 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; + } + void _showAddLocationSheet() { + final _formKey = GlobalKey(); + final TextEditingController _nameCtrl = TextEditingController(); + final TextEditingController _mobileCtrl = TextEditingController(); + String? selectedWaterType; + String address = ''; + double lat = 0; + double lng = 0; + + showModalBottomSheet( + isScrollControlled: true, + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20))), backgroundColor: Colors.white, - appBar: AppBar( - backgroundColor: Colors.white, - surfaceTintColor: Colors.transparent, - elevation: 0, - scrolledUnderElevation: 0, - title: const Text("Complete Profile"), - actions: [ - Padding( - padding: const EdgeInsets.fromLTRB(10, 10, 0, 10), - child: IconButton( - splashRadius: 20, - padding: EdgeInsets.zero, - icon: const Image(image: AssetImage('images/calendar_appbar.png'), width: 22, height: 22), - onPressed: () {}, - ), + builder: (context) { + return Padding( + padding: EdgeInsets.only( + left: 20, + right: 20, + top: 20, + bottom: MediaQuery.of(context).viewInsets.bottom + 20, ), - Padding( - padding: const EdgeInsets.fromLTRB(0, 10, 10, 10), - child: IconButton( - splashRadius: 20, - padding: EdgeInsets.zero, - icon: Image.asset('images/notification_appbar.png', width: 22, height: 22), - onPressed: () {}, - ), - ), - ], - ), - body: SafeArea( - child: Form( - key: _formKey, - child: ListView( - padding: const EdgeInsets.fromLTRB(20, 10, 20, 24), - children: [ - // Step indicator - Text( - "Step 3/5", - style: fontTextStyle(16, const Color(0xFFC3C4C4), FontWeight.w500), - ), - const SizedBox(height: 16), - Row( - children: List.generate(4, (index) { - return Expanded( + child: SingleChildScrollView( + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Center( child: Container( - margin: const EdgeInsets.symmetric(horizontal: 2), - height: 5, + width: 40, + height: 4, + margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( - color: index < 4 ? const Color(0xFFC3C4C4) : Colors.grey, + color: Colors.grey[300], borderRadius: BorderRadius.circular(2), ), ), - ); - }), - ), - const SizedBox(height: 12), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text("SORURCE LOCATION", style: fontTextStyle(20, const Color(0xFF515253), FontWeight.w600)), - const SizedBox(height: 8), - Container( - width: 24, - height: 24, - decoration: const BoxDecoration( - image: DecorationImage(image: AssetImage('images/flag.png'), fit: BoxFit.contain), - ), ), - ], - ), - const SizedBox(height: 6), - Text( - "Add your source Location", - style: fontTextStyle(14, const Color(0xFF939495), FontWeight.w500), - ), - const SizedBox(height: 6), - Align( - alignment: Alignment.centerLeft, // keep the whole thing on the left - child: Row( - mainAxisSize: MainAxisSize.min, // don't stretch full width - children: [ - Checkbox( - value: addBusinessAsSource, - onChanged: (v) => setState(() => addBusinessAsSource = v ?? false), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - visualDensity: const VisualDensity(horizontal: -4, vertical: -4), + Center( + child: Text( + "Add Source Location", + style: fontTextStyle( + 16, const Color(0xFF2D2E30), FontWeight.w600), ), - const SizedBox(width: 6), // control the exact gap - Text( - "Add Business Location as a Source Location", - style: fontTextStyle(14, const Color(0xFF939495), FontWeight.w500), - maxLines: 1, - overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 20), + _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, ), - ], - ), - ), - - const SizedBox(height: 16), - - // Section header (just the bar) - _SectionHeaderBar( - title: "SOURCE LOCATION #1", - icon: Image.asset('images/arrow-up.png', width: 16, height: 16), - radius: 20, - ), - const SizedBox(height: 12), - - // === Fields - _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: 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: (){ - 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), + _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, ), ), - 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), + Visibility( + visible: !addBusinessAsSource, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox( + width: double.infinity, + child: OutlinedButton.icon( + onPressed: () { + }, + 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), + ], + ), ), - ), - ), - - 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), + _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: 12), + + const SizedBox(height: 20), + const SizedBox(height: 20), SizedBox( width: double.infinity, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF8270DB), - foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24)), padding: const EdgeInsets.symmetric(vertical: 14), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), ), - onPressed: () { - // // TODO: Navigate to the next step/screen - // Navigator.push(context, MaterialPageRoute(builder: (_) => const SourceLocation1())); - // // ScaffoldMessenger.of(context).showSnackBar( - // // SnackBar(content: Text("Saved ${_drivers.length} driver(s). Proceeding…")), - // // ); + onPressed: () async { + if (_formKey.currentState?.validate() ?? false) { + var payload = { + "location_name": _nameCtrl.text, + "phone": _mobileCtrl.text, + "water_type": selectedWaterType, + "address": address, + "latitude": lat, + "longitude": lng, + "status": "active", + }; + + bool ok = + await AppSettings.addSourceLocations(payload); + if (ok) { + AppSettings.longSuccessToast( + "Source added successfully"); + Navigator.pop(context); + _fetchSources(); + } else { + AppSettings.longFailedToast( + "Failed to add location"); + } + } }, - - child: Text( - "Continue", - style: fontTextStyle(14, Colors.white, FontWeight.w400), - ), + child: const Text("Save Location", + style: TextStyle( + fontSize: 14, color: Colors.white)), ), ), ], ), - ], + ), ), - ), - ), + ); + }, ); } -} - -// ======= UI helpers ======= - -class _SectionHeaderBar extends StatelessWidget { - final String title; - final Widget? icon; - final Color backgroundColor; - final Color borderColor; - final double radius; - - const _SectionHeaderBar({ - required this.title, - this.icon, - this.backgroundColor = const Color(0xFFEEEEEE), - this.borderColor = const Color(0xFFE5E7EB), - this.radius = 12, - Key? key, - }) : super(key: key); @override Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - color: backgroundColor, - border: Border.all(color: borderColor, width: 1), - borderRadius: BorderRadius.circular(radius), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.04), - blurRadius: 6, - offset: const Offset(0, 2), - ), - ], + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBar( + backgroundColor: Colors.white, + surfaceTintColor: Colors.transparent, + elevation: 0, + title: const Text("Complete Profile"), ), - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), - child: Row( - children: [ - Expanded( - child: Text( - title, - style: fontTextStyle(12, const Color(0xFF2D2E30), FontWeight.w600), + body: SafeArea( + child: isLoading + ? const Center(child: CircularProgressIndicator()) + : ListView( + padding: const EdgeInsets.fromLTRB(20, 10, 20, 24), + children: [ + Text("Step $currentStep/5", + style: fontTextStyle( + 16, const Color(0xFFC3C4C4), FontWeight.w500)), + const SizedBox(height: 16), + Text("SOURCE LOCATION", + style: fontTextStyle( + 20, const Color(0xFF515253), FontWeight.w600)), + const SizedBox(height: 6), + Text("Add your source location", + style: fontTextStyle( + 14, const Color(0xFF939495), FontWeight.w500)), + const SizedBox(height: 16), + + // List of saved locations + if (sourceLocationsList.isEmpty) + const Padding( + padding: EdgeInsets.symmetric(vertical: 40), + child: Center( + child: Text("No source locations added yet."), + ), + ) + else + ...List.generate(sourceLocationsList.length, (idx) { + final d = sourceLocationsList[idx]; + bool expanded = false; + return StatefulBuilder(builder: (context, setInner) { + return Container( + margin: const EdgeInsets.only(bottom: 8), + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: const Color(0xFFF1F1F1), + border: Border.all(color: const Color(0xFFE5E5E5)), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + children: [ + ListTile( + dense: true, + contentPadding: EdgeInsets.zero, + title: Text( + d.source_name ?? 'Unnamed location', + style: fontTextStyle( + 14, + const Color(0xFF2D2E30), + FontWeight.w600), + ), + trailing: IconButton( + icon: Image.asset( + expanded + ? 'images/arrow-up.png' + : 'images/downarrow.png', + width: 18, + height: 18, + ), + onPressed: () => + setInner(() => expanded = !expanded), + ), + ), + if (expanded) + Container( + margin: const EdgeInsets.only( + left: 10, right: 10, bottom: 6), + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: const Color(0xFFE5E5E5)), + ), + child: Text( + "Address: ${d.address ?? 'N/A'}", + style: fontTextStyle( + 12, + const Color(0xFF646566), + FontWeight.w400), + ), + ), + ], + ), + ); + }); + }), + + const SizedBox(height: 24), + OutlinedButton.icon( + onPressed: _showAddLocationSheet, + icon: const Icon(Icons.add, size: 18, color: Color(0xFF646566)), + label: Text( + "Add New Location", + style: fontTextStyle( + 14, const Color(0xFF646566), FontWeight.w600), + ), ), - ), - if (icon != null) icon!, - ], + const SizedBox(height: 12), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF8270DB), + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24)), + ), + onPressed: () {}, + child: Text("Continue", + style: fontTextStyle( + 14, Colors.white, FontWeight.w400)), + ), + ], + ), ), ); } } +// ======= Helper widget ======= class _LabeledField extends StatelessWidget { final String label; final Widget child; - final String? Function()? validator; // (kept from your earlier helper; not used here) - - const _LabeledField({ - required this.label, - required this.child, - this.validator, - }); + const _LabeledField({required this.label, required this.child}); @override Widget build(BuildContext context) { - final errorText = validator != null ? validator!() : null; - return Padding( - padding: const EdgeInsets.only(bottom: 14.0), + padding: const EdgeInsets.only(bottom: 14), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(label, style: fontTextStyle(12, const Color(0xFF515253), FontWeight.w600)), + Text(label, + style: fontTextStyle( + 12, const Color(0xFF515253), FontWeight.w600)), const SizedBox(height: 6), child, - if (errorText != null) - Padding( - padding: const EdgeInsets.only(top: 6), - child: Text( - errorText, - style: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400), - ), - ), ], ), ); diff --git a/lib/resources/resources_sources.dart b/lib/resources/resources_sources.dart index 4d17fa2..fee1866 100644 --- a/lib/resources/resources_sources.dart +++ b/lib/resources/resources_sources.dart @@ -2,9 +2,11 @@ import 'dart:convert'; import 'dart:io' show Platform; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:supplier_new/common/settings.dart'; import 'package:supplier_new/resources/source_loctaions_model.dart'; import 'package:supplier_new/resources/sourcelocation_details.dart'; +import '../common/keys.dart'; import '../google_maps_place_picker_mb/src/models/pick_result.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'; @@ -42,7 +44,6 @@ class FirstCharUppercaseFormatter extends TextInputFormatter { } } -void main() => runApp(const MaterialApp(home: ResourcesSourceScreen())); class ResourcesSourceScreen extends StatefulWidget { const ResourcesSourceScreen({super.key}); @@ -295,7 +296,6 @@ class _ResourcesSourceScreenState extends State { child: OutlinedButton.icon( onPressed: () { // Your PlacePicker flow goes here (kept commented for reference) - /* location.serviceEnabled().then((value) { if (value) { initRenderer(); @@ -376,7 +376,6 @@ class _ResourcesSourceScreenState extends State { ); } }); - */ }, icon: Image.asset('images/Add_icon.png', width: 16, height: 16), label: Text(