Add Dialog with Pageview to select new house, and then drop a pin where the house is, then zoom map to that area.
This commit is contained in:
104
lib/widgets/custom_dialog_box copy.dart
Normal file
104
lib/widgets/custom_dialog_box copy.dart
Normal file
@@ -0,0 +1,104 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:wheres_my_sign/common/constants.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> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(Constants.padding),
|
||||
),
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
child: contentBox(context),
|
||||
);
|
||||
}
|
||||
|
||||
contentBox(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: [
|
||||
BoxShadow(
|
||||
color: Colors.black,
|
||||
offset: Offset(0, 10),
|
||||
blurRadius: 10,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
widget.title,
|
||||
style: TextStyle(fontSize: 22, fontWeight: FontWeight.w600),
|
||||
),
|
||||
SizedBox(height: 15),
|
||||
Text(
|
||||
widget.descriptions,
|
||||
style: TextStyle(fontSize: 14),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 22),
|
||||
Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(widget.text, style: TextStyle(fontSize: 18)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: Constants.padding,
|
||||
right: Constants.padding,
|
||||
child: CircleAvatar(
|
||||
backgroundColor: Colors.transparent,
|
||||
radius: Constants.avatarRadius,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(Constants.avatarRadius),
|
||||
),
|
||||
child: Image.asset(
|
||||
"assets/icons/house.png",
|
||||
width: Constants.avatarRadius * 2,
|
||||
height: Constants.avatarRadius * 2,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
336
lib/widgets/custom_dialog_box.dart
Normal file
336
lib/widgets/custom_dialog_box.dart
Normal file
@@ -0,0 +1,336 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart' as gmaps;
|
||||
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 '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 = [];
|
||||
|
||||
final places.FlutterGooglePlacesSdk _places = places.FlutterGooglePlacesSdk(
|
||||
'AIzaSyBLSUk32a5qGm3M_n9Yii66I7wi0rmA8oM',
|
||||
);
|
||||
|
||||
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.25,
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user