338 lines
10 KiB
Dart
338 lines
10 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_google_places_sdk/flutter_google_places_sdk.dart'
|
|
as places;
|
|
import 'package:wheres_my_sign/common/constants.dart';
|
|
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
|
|
import 'package:wheres_my_sign/common/functions.dart';
|
|
import 'dart:async';
|
|
|
|
import 'package:wheres_my_sign/models/property.dart';
|
|
|
|
class CustomDialogBox extends StatefulWidget {
|
|
final String title, descriptions, text;
|
|
final Image? img;
|
|
|
|
const CustomDialogBox({
|
|
super.key,
|
|
required this.title,
|
|
required this.descriptions,
|
|
required this.text,
|
|
this.img,
|
|
});
|
|
|
|
@override
|
|
CustomDialogBoxState createState() => CustomDialogBoxState();
|
|
}
|
|
|
|
class CustomDialogBoxState extends State<CustomDialogBox> {
|
|
final PageController _pageController = PageController();
|
|
int _currentPage = 0;
|
|
|
|
final _addressController = TextEditingController();
|
|
final _signsController = TextEditingController();
|
|
String? _selectedRadius;
|
|
|
|
final _addressFocusNode = FocusNode();
|
|
Timer? _debouncer;
|
|
List<places.AutocompletePrediction> _predictions = [];
|
|
String apiKey = getGooglePlacesApiKey();
|
|
|
|
final places.FlutterGooglePlacesSdk _places = places.FlutterGooglePlacesSdk(
|
|
getGooglePlacesApiKey(),
|
|
);
|
|
|
|
late Property property; // Declare property to store final details
|
|
|
|
@override
|
|
void dispose() {
|
|
_addressController.dispose();
|
|
_signsController.dispose();
|
|
_debouncer?.cancel();
|
|
super.dispose();
|
|
}
|
|
|
|
void _onAddressChanged(String value) {
|
|
if (_debouncer?.isActive ?? false) _debouncer!.cancel();
|
|
_debouncer = Timer(const Duration(milliseconds: 300), () async {
|
|
if (value.isNotEmpty) {
|
|
final result = await _places.findAutocompletePredictions(value);
|
|
setState(() => _predictions = result.predictions);
|
|
} else {
|
|
setState(() => _predictions = []);
|
|
}
|
|
});
|
|
}
|
|
|
|
void _selectPrediction(places.AutocompletePrediction prediction) async {
|
|
// Fetch place details
|
|
final placeDetails = await _places.fetchPlace(
|
|
prediction.placeId,
|
|
fields: [
|
|
places.PlaceField.Location,
|
|
places.PlaceField.AddressComponents,
|
|
places.PlaceField.Name,
|
|
],
|
|
);
|
|
|
|
final place = placeDetails.place;
|
|
|
|
// Null checks for place and latLng
|
|
if (place != null && place.latLng != null) {
|
|
final latLng =
|
|
place
|
|
.latLng!; // Use the null check operator, since we've already verified it's not null
|
|
|
|
setState(() {
|
|
_addressController.text = prediction.fullText;
|
|
_predictions = [];
|
|
property = Property(
|
|
latitude: latLng.lat,
|
|
longitude: latLng.lng,
|
|
address:
|
|
place.name ?? 'Unknown', // Default to 'Unknown' if name is null
|
|
signLocations: [],
|
|
);
|
|
});
|
|
}
|
|
|
|
FocusScope.of(context).unfocus(); // Close keyboard
|
|
}
|
|
|
|
void _nextPage() {
|
|
if (_currentPage < 3) {
|
|
setState(() => _currentPage++);
|
|
_pageController.animateToPage(
|
|
_currentPage,
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.easeInOut,
|
|
);
|
|
}
|
|
}
|
|
|
|
void _previousPage() {
|
|
if (_currentPage > 0) {
|
|
setState(() => _currentPage--);
|
|
_pageController.animateToPage(
|
|
_currentPage,
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.easeInOut,
|
|
);
|
|
}
|
|
}
|
|
|
|
Widget _buildNavigationButtons() {
|
|
return Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
if (_currentPage > 0)
|
|
TextButton(onPressed: _previousPage, child: const Text("Previous")),
|
|
const Spacer(),
|
|
if (_currentPage < 3)
|
|
TextButton(onPressed: _nextPage, child: const Text("Next"))
|
|
else
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.of(context).pop(property);
|
|
},
|
|
child: const Text("Show"),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildPageContent() {
|
|
return SizedBox(
|
|
height: MediaQuery.of(context).size.height * 0.30,
|
|
child: PageView(
|
|
controller: _pageController,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
children: [
|
|
// Page 1
|
|
Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const Text(
|
|
"Let's show a property. Over the next few pages, we'll ask for the address, how many signs you want to place, and the radius you'd like to reach. The final page will confirm your input and show the best locations for your open house signs. Ready? Let's Begin!",
|
|
textAlign: TextAlign.justify,
|
|
),
|
|
const SizedBox(height: 20),
|
|
const Spacer(),
|
|
_buildNavigationButtons(),
|
|
],
|
|
),
|
|
|
|
// Page 2 - Address with Autocomplete
|
|
Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
TextFormField(
|
|
controller: _addressController,
|
|
focusNode: _addressFocusNode,
|
|
onChanged: _onAddressChanged,
|
|
decoration: const InputDecoration(
|
|
labelText: 'Property Address',
|
|
),
|
|
),
|
|
if (_predictions.isNotEmpty)
|
|
Container(
|
|
height: 150,
|
|
child: ListView.builder(
|
|
itemCount: _predictions.length,
|
|
itemBuilder: (context, index) {
|
|
final p = _predictions[index];
|
|
return ListTile(
|
|
title: Text(p.fullText),
|
|
onTap: () => _selectPrediction(p),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
Spacer(),
|
|
_buildNavigationButtons(),
|
|
],
|
|
),
|
|
|
|
// Page 3
|
|
Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
TextFormField(
|
|
controller: _signsController,
|
|
decoration: const InputDecoration(labelText: 'Number of Signs'),
|
|
keyboardType: TextInputType.number,
|
|
),
|
|
const SizedBox(height: 10),
|
|
DropdownButtonFormField<String>(
|
|
value: _selectedRadius,
|
|
decoration: const InputDecoration(labelText: 'Search Radius'),
|
|
items:
|
|
['1 mile', '2 miles', '5 miles']
|
|
.map((e) => DropdownMenuItem(value: e, child: Text(e)))
|
|
.toList(),
|
|
onChanged: (val) => setState(() => _selectedRadius = val),
|
|
),
|
|
const SizedBox(height: 20),
|
|
const Spacer(),
|
|
_buildNavigationButtons(),
|
|
],
|
|
),
|
|
|
|
// Page 4 - Summary
|
|
Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text(
|
|
"Summary",
|
|
style: TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
const SizedBox(height: 10),
|
|
Text("Address: ${_addressController.text}"),
|
|
Text("Signs: ${_signsController.text}"),
|
|
Text("Radius: ${_selectedRadius ?? 'Not selected'}"),
|
|
const SizedBox(height: 20),
|
|
const Spacer(),
|
|
_buildNavigationButtons(),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Dialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(Constants.padding),
|
|
),
|
|
elevation: 0,
|
|
backgroundColor: Colors.transparent,
|
|
child: contentBox(context),
|
|
);
|
|
}
|
|
|
|
Widget contentBox(BuildContext context) {
|
|
return Stack(
|
|
children: <Widget>[
|
|
Container(
|
|
padding: EdgeInsets.only(
|
|
left: Constants.padding,
|
|
top: Constants.avatarRadius + Constants.padding,
|
|
right: Constants.padding,
|
|
bottom: Constants.padding,
|
|
),
|
|
margin: EdgeInsets.only(top: Constants.avatarRadius),
|
|
decoration: BoxDecoration(
|
|
shape: BoxShape.rectangle,
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(Constants.padding),
|
|
boxShadow: const [
|
|
BoxShadow(
|
|
color: Colors.black,
|
|
offset: Offset(0, 10),
|
|
blurRadius: 10,
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
Text(
|
|
widget.title,
|
|
style: const TextStyle(
|
|
fontSize: 22,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
const SizedBox(height: 15),
|
|
Text(
|
|
widget.descriptions,
|
|
style: const TextStyle(fontSize: 14),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: 15),
|
|
SmoothPageIndicator(
|
|
controller: _pageController,
|
|
count: 4,
|
|
effect: WormEffect(
|
|
dotHeight: 10,
|
|
dotWidth: 10,
|
|
spacing: 8,
|
|
activeDotColor: Theme.of(context).primaryColor,
|
|
dotColor: Colors.grey.shade300,
|
|
),
|
|
),
|
|
const SizedBox(height: 15),
|
|
_buildPageContent(),
|
|
],
|
|
),
|
|
),
|
|
Positioned(
|
|
left: Constants.padding,
|
|
right: Constants.padding,
|
|
child: CircleAvatar(
|
|
backgroundColor: Colors.transparent,
|
|
radius: Constants.avatarRadius,
|
|
child: ClipRRect(
|
|
borderRadius: const BorderRadius.all(
|
|
Radius.circular(Constants.avatarRadius),
|
|
),
|
|
child:
|
|
widget.img ??
|
|
Image.asset(
|
|
"assets/icons/house.png",
|
|
width: Constants.avatarRadius * 2,
|
|
height: Constants.avatarRadius * 2,
|
|
fit: BoxFit.cover,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|