// Copyright 2020 The Flutter team. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:provider/provider.dart'; import 'place.dart'; import 'place_tracker_app.dart'; import 'stub_data.dart'; class PlaceDetails extends StatefulWidget { final Place place; const PlaceDetails({ required this.place, super.key, }); @override State createState() => _PlaceDetailsState(); } class _PlaceDetailsState extends State { late Place _place; GoogleMapController? _mapController; final Set _markers = {}; final TextEditingController _nameController = TextEditingController(); final TextEditingController _descriptionController = TextEditingController(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(_place.name), backgroundColor: Colors.green[700], actions: [ Padding( padding: const EdgeInsets.fromLTRB(0.0, 0.0, 8.0, 0.0), child: IconButton( icon: const Icon(Icons.save, size: 30.0), onPressed: () { _onChanged(_place); Navigator.pop(context); }, ), ), ], ), body: GestureDetector( onTap: () { FocusScope.of(context).requestFocus(FocusNode()); }, child: _detailsBody(), ), ); } @override void initState() { _place = widget.place; _nameController.text = _place.name; _descriptionController.text = _place.description ?? ''; return super.initState(); } Widget _detailsBody() { return ListView( padding: const EdgeInsets.fromLTRB(24.0, 12.0, 24.0, 12.0), children: [ _NameTextField( controller: _nameController, onChanged: (value) { setState(() { _place = _place.copyWith(name: value); }); }, ), _DescriptionTextField( controller: _descriptionController, onChanged: (value) { setState(() { _place = _place.copyWith(description: value); }); }, ), _StarBar( rating: _place.starRating, onChanged: (value) { setState(() { _place = _place.copyWith(starRating: value); }); }, ), _Map( center: _place.latLng, mapController: _mapController, onMapCreated: _onMapCreated, markers: _markers, ), const _Reviews(), ], ); } void _onMapCreated(GoogleMapController controller) { _mapController = controller; setState(() { _markers.add(Marker( markerId: MarkerId(_place.latLng.toString()), position: _place.latLng, )); }); } void _onChanged(Place value) { // Replace the place with the modified version. final newPlaces = List.from(context.read().places); final index = newPlaces.indexWhere((place) => place.id == value.id); newPlaces[index] = value; context.read().setPlaces(newPlaces); } } class _DescriptionTextField extends StatelessWidget { final TextEditingController controller; final ValueChanged onChanged; const _DescriptionTextField({ required this.controller, required this.onChanged, }); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 16.0), child: TextField( decoration: const InputDecoration( labelText: 'Description', labelStyle: TextStyle(fontSize: 18.0), ), style: const TextStyle(fontSize: 20.0, color: Colors.black87), maxLines: null, autocorrect: true, controller: controller, onChanged: (value) { onChanged(value); }, ), ); } } class _Map extends StatelessWidget { final LatLng center; final GoogleMapController? mapController; final ArgumentCallback onMapCreated; final Set markers; const _Map({ required this.center, required this.mapController, required this.onMapCreated, required this.markers, }); @override Widget build(BuildContext context) { return Card( margin: const EdgeInsets.symmetric(vertical: 16.0), elevation: 4, child: SizedBox( width: 340, height: 240, child: GoogleMap( onMapCreated: onMapCreated, initialCameraPosition: CameraPosition( target: center, zoom: 16, ), markers: markers, zoomGesturesEnabled: false, rotateGesturesEnabled: false, tiltGesturesEnabled: false, scrollGesturesEnabled: false, ), ), ); } } class _NameTextField extends StatelessWidget { final TextEditingController controller; final ValueChanged onChanged; const _NameTextField({ required this.controller, required this.onChanged, }); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.fromLTRB(0, 0, 0, 16), child: TextField( decoration: const InputDecoration( labelText: 'Name', labelStyle: TextStyle(fontSize: 18), ), style: const TextStyle(fontSize: 20, color: Colors.black87), autocorrect: true, controller: controller, onChanged: (value) { onChanged(value); }, ), ); } } class _Reviews extends StatelessWidget { const _Reviews(); @override Widget build(BuildContext context) { return Column( children: [ const Padding( padding: EdgeInsets.fromLTRB(0, 12, 0, 8), child: Align( alignment: Alignment.topLeft, child: Text( 'Reviews', style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, decoration: TextDecoration.underline, color: Colors.black87, ), ), ), ), Column( children: StubData.reviewStrings .map((reviewText) => _buildSingleReview(reviewText)) .toList(), ), ], ); } Widget _buildSingleReview(String reviewText) { return Column( children: [ Padding( padding: const EdgeInsets.symmetric(vertical: 10), child: Row( children: [ Container( width: 80, height: 80, decoration: BoxDecoration( borderRadius: BorderRadius.circular(40), border: Border.all( width: 3, color: Colors.grey, ), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: const [ Text( '5', style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: Colors.black87, ), ), Icon( Icons.star, color: Colors.amber, size: 36, ), ], ), ), const SizedBox(width: 16), Expanded( child: Text( reviewText, style: const TextStyle(fontSize: 20, color: Colors.black87), maxLines: null, ), ), ], ), ), Divider( height: 8, color: Colors.grey[700], ), ], ); } } class _StarBar extends StatelessWidget { static const int maxStars = 5; final int rating; final ValueChanged onChanged; const _StarBar({ required this.rating, required this.onChanged, }) : assert(rating >= 0 && rating <= maxStars); @override Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(maxStars, (index) { return IconButton( icon: const Icon(Icons.star), iconSize: 40, color: rating > index ? Colors.amber : Colors.grey[400], onPressed: () { onChanged(index + 1); }, ); }).toList(), ); } }