Files
wheres-my-sign/lib/widgets/custom_dialog_box.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,
),
),
),
),
],
);
}
}