592 lines
20 KiB
Dart
592 lines
20 KiB
Dart
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:fluttertoast/fluttertoast.dart';
|
|
import 'package:geocoding/geocoding.dart';
|
|
import 'package:geolocator/geolocator.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
|
import 'package:google_places_flutter/google_places_flutter.dart';
|
|
import 'package:google_places_flutter/model/prediction.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import '../../constants/color_constants.dart';
|
|
import '../../constants/font_constants.dart';
|
|
import '../../controllers/tenant_controller /tenant_list.dart';
|
|
import '../../widgets/text_widget.dart';
|
|
import '../home_view.dart';
|
|
|
|
class CustomerCreateView extends StatefulWidget {
|
|
final String mobileNumber;
|
|
const CustomerCreateView({super.key,required this.mobileNumber});
|
|
|
|
@override
|
|
State<CustomerCreateView> createState() => _CustomerCreateViewState();
|
|
}
|
|
|
|
class _CustomerCreateViewState extends State<CustomerCreateView> {
|
|
Map<String, dynamic>? selectedLocationData;
|
|
bool isFetching = false;
|
|
|
|
final TenantController tenantController = Get.put(TenantController());
|
|
|
|
final TextEditingController nameController = TextEditingController();
|
|
final TextEditingController landmarkController = TextEditingController();
|
|
|
|
Future<void> createCustomer(Map<String, dynamic> locationData) async {
|
|
try {
|
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
|
|
|
String? fcmToken = prefs.getString('fcmToken') ?? '';
|
|
String deviceId = prefs.getString('currentDeviceId') ?? '';
|
|
String deviceType = Platform.isAndroid ? "android" : "ios";
|
|
|
|
final url = Uri.parse('https://fiesta.nearle.app/live/api/v1/mob/customers/create');
|
|
|
|
final Map<String, dynamic> body = {
|
|
"configid": 2,
|
|
"firstname": nameController.text.trim(),
|
|
"applocationid": 1,
|
|
"profileimage": "",
|
|
"dialcode": "+91",
|
|
"contactno": widget.mobileNumber,
|
|
"devicetype": deviceType,
|
|
"deviceid": deviceId,
|
|
"customertoken": fcmToken,
|
|
"address": locationData["address"] ?? "",
|
|
"suburb": locationData["suburb"] ?? "",
|
|
"city": locationData["city"] ?? "",
|
|
"state": locationData["state"] ?? "",
|
|
"postcode": locationData["postcode"] ?? "",
|
|
"landmark": landmarkController.text.isEmpty ? "near" : landmarkController.text.trim(),
|
|
"doorno": locationData["doorno"] ?? "",
|
|
"latitude": locationData["latitude"] ?? "",
|
|
"longitude": locationData["longitude"] ?? "",
|
|
"tenantid": 630,
|
|
"email": "",
|
|
"primaryaddress": 1,
|
|
"gender": "Male",
|
|
"dob": "2025-06-30"
|
|
};
|
|
|
|
Fluttertoast.showToast(
|
|
msg: "Creating customer...",
|
|
toastLength: Toast.LENGTH_SHORT,
|
|
gravity: ToastGravity.TOP,
|
|
backgroundColor: Colors.black.withOpacity(0.8),
|
|
textColor: Colors.white,
|
|
fontSize: 15,
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final response = await http.post(
|
|
url,
|
|
headers: {"Content-Type": "application/json"},
|
|
body: jsonEncode(body),
|
|
);
|
|
|
|
final data = jsonDecode(response.body);
|
|
final bool status = data['status'] ?? false;
|
|
final String message = data['message'] ?? 'Unknown response';
|
|
|
|
if (status) {
|
|
final details = data['details'];
|
|
|
|
if (details != null) {
|
|
// ✅ Save important details to SharedPreferences
|
|
final customerIdStr = details['customerid']?.toString() ?? '0';
|
|
await prefs.setInt('customerId', int.tryParse(customerIdStr) ?? 0);
|
|
await prefs.setString('customerFirstname', details['firstname'] ?? '');
|
|
await prefs.setString('customertoken', details['customertoken'] ?? '');
|
|
await prefs.setInt('deliverylocationid', details['deliverylocationid'] ?? 0);
|
|
await prefs.setInt('contactno', int.tryParse(details['contactno'] ?? '0') ?? 0);
|
|
await prefs.setString('customerAddress', details['address'] ?? '');
|
|
await prefs.setString('customerSuburb', details['suburb'] ?? '');
|
|
await prefs.setString('customerCity', details['city'] ?? '');
|
|
await prefs.setString('customerState', details['state'] ?? '');
|
|
await prefs.setString('customerLandmark', details['landmark'] ?? '');
|
|
await prefs.setString('customerDoorNo', details['doorno'] ?? '');
|
|
|
|
debugPrint("✅ Customer info saved to SharedPreferences.");
|
|
}
|
|
tenantController.loadTenants();
|
|
|
|
print(data);
|
|
// Get.put(TenantController());
|
|
Get.offAll(() => BottomNavigation());
|
|
// ✅ Use message from API
|
|
|
|
Fluttertoast.showToast(
|
|
msg: "Customer created successfully!",
|
|
toastLength: Toast.LENGTH_SHORT,
|
|
gravity: ToastGravity.TOP,
|
|
backgroundColor: Colors.green.withOpacity(0.8),
|
|
textColor: Colors.white,
|
|
fontSize: 15,
|
|
);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// ❌ Handle failure message from API
|
|
debugPrint("❌ API returned failure: $message");
|
|
|
|
Fluttertoast.showToast(
|
|
msg: "Customer already available",
|
|
toastLength: Toast.LENGTH_SHORT,
|
|
gravity: ToastGravity.TOP,
|
|
backgroundColor: Colors.black.withOpacity(0.8),
|
|
textColor: Colors.white,
|
|
fontSize: 15,
|
|
);
|
|
|
|
|
|
}
|
|
} catch (e, stacktrace) {
|
|
debugPrint(" Something went wrong");
|
|
debugPrint("Stacktrace: $stacktrace");
|
|
Fluttertoast.showToast(
|
|
msg: "Something went wrong",
|
|
toastLength: Toast.LENGTH_SHORT,
|
|
gravity: ToastGravity.TOP,
|
|
backgroundColor: Colors.black.withOpacity(0.8),
|
|
textColor: Colors.white,
|
|
fontSize: 15,
|
|
);
|
|
|
|
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
|
|
|
|
|
|
|
|
final size = MediaQuery.of(context).size;
|
|
final width = size.width;
|
|
final height = size.height;
|
|
|
|
double scaleFont(double size) {
|
|
if (width > 800) return size * 1.5;
|
|
if (width > 600) return size * 1.3;
|
|
return size;
|
|
}
|
|
|
|
return SafeArea(
|
|
top: false,
|
|
child: Scaffold(
|
|
backgroundColor: Colors.grey.shade100,
|
|
appBar: AppBar(
|
|
backgroundColor: Colors.white,
|
|
elevation: 0,
|
|
centerTitle: true,
|
|
leadingWidth: 300,
|
|
leading: Row(
|
|
children: [
|
|
IconButton(
|
|
icon: const Icon(Icons.arrow_back, color: Colors.black87),
|
|
onPressed: () => Navigator.pop(context),
|
|
),
|
|
ReusableTextWidget(
|
|
text: "Create Account",
|
|
color: ColorConstants.blackColor,
|
|
fontWeight: FontWeight.w700,
|
|
fontSize: scaleFont(17),
|
|
fontFamily: FontConstants.fontFamily,
|
|
)
|
|
],
|
|
),
|
|
),
|
|
|
|
body: SingleChildScrollView(
|
|
padding: EdgeInsets.symmetric(horizontal: width * 0.06, vertical: height * 0.02),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Padding(
|
|
padding: EdgeInsets.only(bottom: height * 0.02),
|
|
child: ReusableTextWidget(
|
|
text: "Welcome 👋\nPlease enter your details below",
|
|
color: Colors.black87,
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: scaleFont(13),
|
|
fontFamily: FontConstants.fontFamily,
|
|
),
|
|
|
|
),
|
|
|
|
_buildLabel("Full Name", scaleFont),
|
|
_buildTextField("Enter your name", Icons.person, width, controller: nameController),
|
|
|
|
SizedBox(height: height * 0.03),
|
|
|
|
_buildLabel("Location", scaleFont),
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(10),
|
|
border: Border.all(color: Colors.black54, width: 0.40),
|
|
),
|
|
child: ListTile(
|
|
leading: Icon(Icons.location_on, color: ColorConstants.primaryColor),
|
|
title: ReusableTextWidget(
|
|
text: selectedLocationData == null
|
|
? "Use my current location"
|
|
: selectedLocationData!["address"],
|
|
color: selectedLocationData == null
|
|
? ColorConstants.primaryColor
|
|
: Colors.black,
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 12,
|
|
fontFamily: FontConstants.fontFamily,
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
|
|
subtitle: selectedLocationData == null
|
|
? ReusableTextWidget(
|
|
text: "Fetching current location...",
|
|
color: Colors.grey,
|
|
fontWeight: FontWeight.w400,
|
|
fontSize: 9,
|
|
fontFamily: FontConstants.fontFamily,
|
|
)
|
|
: null,
|
|
|
|
trailing: isFetching
|
|
? const SizedBox(
|
|
width: 22,
|
|
height: 22,
|
|
child: CircularProgressIndicator(strokeWidth: 2),
|
|
)
|
|
: const Icon(Icons.arrow_forward_ios_rounded,
|
|
color: Colors.grey, size: 18),
|
|
onTap: () async {
|
|
setState(() => isFetching = true);
|
|
final result = await Get.to(() => const MapPickerPage1());
|
|
if (result != null) {
|
|
setState(() {
|
|
selectedLocationData = result;
|
|
});
|
|
}
|
|
setState(() => isFetching = false);
|
|
},
|
|
),
|
|
),
|
|
|
|
SizedBox(height: height * 0.03),
|
|
|
|
_buildLabel("Door No / Landmark", scaleFont),
|
|
_buildTextField("Enter door no / landmark", Icons.home_filled, width,
|
|
controller: landmarkController),
|
|
|
|
SizedBox(height: height * 0.05),
|
|
],
|
|
),
|
|
),
|
|
|
|
bottomNavigationBar: Container(
|
|
padding: EdgeInsets.symmetric(horizontal: width * 0.06, vertical: height * 0.02),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.grey.shade300,
|
|
blurRadius: 10,
|
|
offset: const Offset(0, -2),
|
|
)
|
|
],
|
|
),
|
|
child: SizedBox(
|
|
height: height * 0.065,
|
|
width: double.infinity,
|
|
child: ElevatedButton(
|
|
onPressed: selectedLocationData == null
|
|
? null
|
|
: () {
|
|
if (nameController.text.isEmpty) {
|
|
Get.snackbar("Error", "Please enter your name");
|
|
return;
|
|
}
|
|
createCustomer(selectedLocationData!);
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: ColorConstants.primaryColor,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
elevation: 2,
|
|
),
|
|
child: Text(
|
|
"Submit",
|
|
style: TextStyle(
|
|
fontFamily: FontConstants.fontFamily,
|
|
fontSize: scaleFont(17),
|
|
fontWeight: FontWeight.w700,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildLabel(String text, double Function(double) scaleFont) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 6),
|
|
child: ReusableTextWidget(
|
|
text: text,
|
|
color: Colors.black87,
|
|
fontWeight: FontWeight.w700,
|
|
fontSize: scaleFont(15),
|
|
fontFamily: FontConstants.fontFamily,
|
|
),
|
|
|
|
);
|
|
}
|
|
|
|
Widget _buildTextField(String hint, IconData icon, double width,
|
|
{TextEditingController? controller}) {
|
|
return TextFormField(
|
|
controller: controller,
|
|
style: const TextStyle(fontSize: 14),
|
|
decoration: InputDecoration(
|
|
hintText: hint,
|
|
hintStyle: TextStyle(
|
|
fontFamily: FontConstants.fontFamily,
|
|
fontWeight: FontWeight.w400,
|
|
color: Colors.grey,
|
|
),
|
|
prefixIcon: Icon(icon, color: Colors.grey[700]),
|
|
filled: true,
|
|
fillColor: Colors.white,
|
|
contentPadding: const EdgeInsets.symmetric(vertical: 14, horizontal: 12),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(10),
|
|
borderSide: BorderSide.none,
|
|
),
|
|
|
|
enabledBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(10),
|
|
borderSide: BorderSide(color: Colors.black54, width: 0.40),
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(10),
|
|
borderSide: BorderSide(color: ColorConstants.primaryColor, width: 1.3),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class MapPickerPage1 extends StatefulWidget {
|
|
const MapPickerPage1({super.key});
|
|
|
|
@override
|
|
State<MapPickerPage1> createState() => _MapPickerPage1State();
|
|
}
|
|
|
|
class _MapPickerPage1State extends State<MapPickerPage1> {
|
|
GoogleMapController? mapController;
|
|
LatLng? selectedLatLng;
|
|
final TextEditingController searchController = TextEditingController();
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_getCurrentLocation();
|
|
}
|
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
if (state == AppLifecycleState.resumed) {
|
|
// App came back from background, retry location
|
|
_getCurrentLocation();
|
|
}
|
|
}
|
|
Future<void> _getCurrentLocation() async {
|
|
try {
|
|
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
|
if (!serviceEnabled) {
|
|
// Get.snackbar("Location Disabled", "Please enable GPS to continue");
|
|
await Geolocator.openLocationSettings();
|
|
_getCurrentLocation();
|
|
serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
|
if (!serviceEnabled) return _getCurrentLocation();
|
|
}
|
|
|
|
LocationPermission permission = await Geolocator.checkPermission();
|
|
if (permission == LocationPermission.denied) {
|
|
permission = await Geolocator.requestPermission();
|
|
if (permission == LocationPermission.denied) return;
|
|
}
|
|
if (permission == LocationPermission.deniedForever) {
|
|
Get.snackbar("Permission Denied Forever",
|
|
"Please enable location in app settings.");
|
|
await Geolocator.openAppSettings();
|
|
return;
|
|
}
|
|
|
|
Position position = await Geolocator.getCurrentPosition(
|
|
desiredAccuracy: LocationAccuracy.high,
|
|
);
|
|
setState(() {
|
|
selectedLatLng = LatLng(position.latitude, position.longitude);
|
|
});
|
|
mapController?.animateCamera(CameraUpdate.newLatLngZoom(selectedLatLng!, 16));
|
|
} catch (e) {
|
|
Get.snackbar("Error", "Failed to get location: $e");
|
|
}
|
|
}
|
|
|
|
Future<void> _goToSearchedPlace(double lat, double lng) async {
|
|
setState(() {
|
|
selectedLatLng = LatLng(lat, lng);
|
|
});
|
|
mapController?.animateCamera(
|
|
CameraUpdate.newCameraPosition(
|
|
CameraPosition(target: selectedLatLng!, zoom: 16),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return SafeArea(
|
|
top: false,
|
|
child: Scaffold(
|
|
backgroundColor: Colors.white,
|
|
appBar: AppBar(
|
|
backgroundColor: Colors.white,
|
|
title: const Text("Pick Location"),
|
|
|
|
),
|
|
body: Stack(
|
|
children: [
|
|
selectedLatLng == null
|
|
? const Center(child: CircularProgressIndicator())
|
|
: GoogleMap(
|
|
initialCameraPosition:
|
|
CameraPosition(target: selectedLatLng!, zoom: 16),
|
|
onMapCreated: (controller) => mapController = controller,
|
|
onTap: (latLng) {
|
|
setState(() => selectedLatLng = latLng);
|
|
},
|
|
markers: selectedLatLng != null
|
|
? {
|
|
Marker(
|
|
markerId: const MarkerId("selected"),
|
|
position: selectedLatLng!,
|
|
draggable: true,
|
|
onDragEnd: (newPos) =>
|
|
setState(() => selectedLatLng = newPos),
|
|
),
|
|
}
|
|
: {},
|
|
),
|
|
Positioned(
|
|
top: 10,
|
|
left: 15,
|
|
right: 15,
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.2),
|
|
blurRadius: 5,
|
|
),
|
|
],
|
|
),
|
|
child: GooglePlaceAutoCompleteTextField(
|
|
textEditingController: searchController,
|
|
googleAPIKey: "AIzaSyBhkGfnq27sN0wV5y_S-M2KojpFTk_by-Q",
|
|
inputDecoration: const InputDecoration(
|
|
hintText: "Search location...",
|
|
border: InputBorder.none,
|
|
contentPadding: EdgeInsets.all(12),
|
|
),
|
|
debounceTime: 400,
|
|
countries: ["in"],
|
|
isLatLngRequired: true,
|
|
getPlaceDetailWithLatLng: (Prediction prediction) {
|
|
double lat = double.parse(prediction.lat!);
|
|
double lng = double.parse(prediction.lng!);
|
|
_goToSearchedPlace(lat, lng);
|
|
},
|
|
itemClick: (Prediction prediction) {
|
|
searchController.text = prediction.description!;
|
|
FocusScope.of(context).unfocus();
|
|
},
|
|
),
|
|
),
|
|
),
|
|
Positioned(
|
|
bottom: 30,
|
|
left: 20,
|
|
right: 20,
|
|
child: ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: ColorConstants.primaryColor,
|
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12)),
|
|
),
|
|
onPressed: selectedLatLng == null
|
|
? null
|
|
: () async {
|
|
try {
|
|
List<Placemark> placemarks =
|
|
await placemarkFromCoordinates(
|
|
selectedLatLng!.latitude,
|
|
selectedLatLng!.longitude,
|
|
);
|
|
|
|
if (placemarks.isNotEmpty) {
|
|
final place = placemarks.first;
|
|
String address =
|
|
"${place.name}, ${place.locality}, ${place.administrativeArea}, ${place.postalCode}, ${place.country}";
|
|
|
|
Map<String, dynamic> selectedLocation = {
|
|
"address": address,
|
|
"suburb": place.subLocality ?? "",
|
|
"city": place.locality ?? "",
|
|
"state": place.administrativeArea ?? "",
|
|
"postcode": place.postalCode ?? "",
|
|
"doorno": place.name ?? "",
|
|
"landmark": "near",
|
|
"latitude": selectedLatLng!.latitude.toString(),
|
|
"longitude":
|
|
selectedLatLng!.longitude.toString(),
|
|
};
|
|
|
|
Navigator.of(Get.context!).pop(selectedLocation);
|
|
}
|
|
} catch (e) {
|
|
Get.snackbar("Error", "Failed to get location: $e");
|
|
}
|
|
},
|
|
child: const Text(
|
|
"Confirm Location",
|
|
style: TextStyle(color: Colors.white, fontSize: 16),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|