mirror of
https://github.com/flutter/samples.git
synced 2025-11-08 13:58:47 +00:00
Flutter 3.29 beta (#2571)
This commit is contained in:
@@ -8,8 +8,10 @@ import 'package:provider/provider.dart';
|
||||
import 'place_tracker_app.dart';
|
||||
|
||||
void main() {
|
||||
runApp(ChangeNotifierProvider(
|
||||
create: (context) => AppState(),
|
||||
child: const PlaceTrackerApp(),
|
||||
));
|
||||
runApp(
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => AppState(),
|
||||
child: const PlaceTrackerApp(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -65,8 +65,4 @@ class Place {
|
||||
starRating.hashCode;
|
||||
}
|
||||
|
||||
enum PlaceCategory {
|
||||
favorite,
|
||||
visited,
|
||||
wantToGo,
|
||||
}
|
||||
enum PlaceCategory { favorite, visited, wantToGo }
|
||||
|
||||
@@ -13,10 +13,7 @@ import 'stub_data.dart';
|
||||
class PlaceDetails extends StatefulWidget {
|
||||
final Place place;
|
||||
|
||||
const PlaceDetails({
|
||||
required this.place,
|
||||
super.key,
|
||||
});
|
||||
const PlaceDetails({required this.place, super.key});
|
||||
|
||||
@override
|
||||
State<PlaceDetails> createState() => _PlaceDetailsState();
|
||||
@@ -107,10 +104,12 @@ class _PlaceDetailsState extends State<PlaceDetails> {
|
||||
void _onMapCreated(GoogleMapController controller) {
|
||||
_mapController = controller;
|
||||
setState(() {
|
||||
_markers.add(Marker(
|
||||
markerId: MarkerId(_place.latLng.toString()),
|
||||
position: _place.latLng,
|
||||
));
|
||||
_markers.add(
|
||||
Marker(
|
||||
markerId: MarkerId(_place.latLng.toString()),
|
||||
position: _place.latLng,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -179,10 +178,7 @@ class _Map extends StatelessWidget {
|
||||
height: 240,
|
||||
child: GoogleMap(
|
||||
onMapCreated: onMapCreated,
|
||||
initialCameraPosition: CameraPosition(
|
||||
target: center,
|
||||
zoom: 16,
|
||||
),
|
||||
initialCameraPosition: CameraPosition(target: center, zoom: 16),
|
||||
markers: markers,
|
||||
zoomGesturesEnabled: false,
|
||||
rotateGesturesEnabled: false,
|
||||
@@ -199,10 +195,7 @@ class _NameTextField extends StatelessWidget {
|
||||
|
||||
final ValueChanged<String> onChanged;
|
||||
|
||||
const _NameTextField({
|
||||
required this.controller,
|
||||
required this.onChanged,
|
||||
});
|
||||
const _NameTextField({required this.controller, required this.onChanged});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -247,9 +240,10 @@ class _Reviews extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: StubData.reviewStrings
|
||||
.map((reviewText) => _buildSingleReview(reviewText))
|
||||
.toList(),
|
||||
children:
|
||||
StubData.reviewStrings
|
||||
.map((reviewText) => _buildSingleReview(reviewText))
|
||||
.toList(),
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -267,10 +261,7 @@ class _Reviews extends StatelessWidget {
|
||||
height: 80,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
border: Border.all(
|
||||
width: 3,
|
||||
color: Colors.grey,
|
||||
),
|
||||
border: Border.all(width: 3, color: Colors.grey),
|
||||
),
|
||||
child: const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
@@ -283,11 +274,7 @@ class _Reviews extends StatelessWidget {
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.star,
|
||||
color: Colors.amber,
|
||||
size: 36,
|
||||
),
|
||||
Icon(Icons.star, color: Colors.amber, size: 36),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -302,10 +289,7 @@ class _Reviews extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
height: 8,
|
||||
color: Colors.grey[700],
|
||||
),
|
||||
Divider(height: 8, color: Colors.grey[700]),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -317,25 +301,24 @@ class _StarBar extends StatelessWidget {
|
||||
final int rating;
|
||||
final ValueChanged<int> onChanged;
|
||||
|
||||
const _StarBar({
|
||||
required this.rating,
|
||||
required this.onChanged,
|
||||
}) : assert(rating >= 0 && rating <= maxStars);
|
||||
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(),
|
||||
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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,10 +33,11 @@ class _PlaceListState extends State<PlaceList> {
|
||||
padding: const EdgeInsets.fromLTRB(16.0, 0.0, 16.0, 8.0),
|
||||
controller: _scrollController,
|
||||
shrinkWrap: true,
|
||||
children: state.places
|
||||
.where((place) => place.category == state.selectedCategory)
|
||||
.map((place) => _PlaceListTile(place: place))
|
||||
.toList(),
|
||||
children:
|
||||
state.places
|
||||
.where((place) => place.category == state.selectedCategory)
|
||||
.map((place) => _PlaceListTile(place: place))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -45,8 +46,10 @@ class _PlaceListState extends State<PlaceList> {
|
||||
|
||||
void _onCategoryChanged(PlaceCategory newCategory) {
|
||||
_scrollController.jumpTo(0.0);
|
||||
Provider.of<AppState>(context, listen: false)
|
||||
.setSelectedCategory(newCategory);
|
||||
Provider.of<AppState>(
|
||||
context,
|
||||
listen: false,
|
||||
).setSelectedCategory(newCategory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +70,7 @@ class _CategoryButton extends StatelessWidget {
|
||||
final buttonText = switch (category) {
|
||||
PlaceCategory.favorite => 'Favorites',
|
||||
PlaceCategory.visited => 'Visited',
|
||||
PlaceCategory.wantToGo => 'Want To Go'
|
||||
PlaceCategory.wantToGo => 'Want To Go',
|
||||
};
|
||||
|
||||
return Container(
|
||||
@@ -134,9 +137,7 @@ class _ListCategoryButtonBar extends StatelessWidget {
|
||||
class _PlaceListTile extends StatelessWidget {
|
||||
final Place place;
|
||||
|
||||
const _PlaceListTile({
|
||||
required this.place,
|
||||
});
|
||||
const _PlaceListTile({required this.place});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -157,15 +158,17 @@ class _PlaceListTile extends StatelessWidget {
|
||||
maxLines: 3,
|
||||
),
|
||||
Row(
|
||||
children: List.generate(5, (index) {
|
||||
return Icon(
|
||||
Icons.star,
|
||||
size: 28.0,
|
||||
color: place.starRating > index
|
||||
? Colors.amber
|
||||
: Colors.grey[400],
|
||||
);
|
||||
}).toList(),
|
||||
children:
|
||||
List.generate(5, (index) {
|
||||
return Icon(
|
||||
Icons.star,
|
||||
size: 28.0,
|
||||
color:
|
||||
place.starRating > index
|
||||
? Colors.amber
|
||||
: Colors.grey[400],
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
Text(
|
||||
place.description ?? '',
|
||||
@@ -174,10 +177,7 @@ class _PlaceListTile extends StatelessWidget {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
Divider(
|
||||
height: 2.0,
|
||||
color: Colors.grey[700],
|
||||
),
|
||||
Divider(height: 2.0, color: Colors.grey[700]),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -54,10 +54,7 @@ class MapConfiguration {
|
||||
class PlaceMap extends StatefulWidget {
|
||||
final LatLng? center;
|
||||
|
||||
const PlaceMap({
|
||||
super.key,
|
||||
this.center,
|
||||
});
|
||||
const PlaceMap({super.key, this.center});
|
||||
|
||||
@override
|
||||
State<PlaceMap> createState() => _PlaceMapState();
|
||||
@@ -94,43 +91,45 @@ class _PlaceMapState extends State<PlaceMap> {
|
||||
Widget build(BuildContext context) {
|
||||
_watchMapConfigurationChanges();
|
||||
var state = Provider.of<AppState>(context, listen: true);
|
||||
return Builder(builder: (context) {
|
||||
// We need this additional builder here so that we can pass its context to
|
||||
// _AddPlaceButtonBar's onSavePressed callback. This callback shows a
|
||||
// SnackBar and to do this, we need a build context that has Scaffold as
|
||||
// an ancestor.
|
||||
return Center(
|
||||
child: Stack(
|
||||
children: [
|
||||
GoogleMap(
|
||||
onMapCreated: onMapCreated,
|
||||
initialCameraPosition: CameraPosition(
|
||||
target: widget.center!,
|
||||
zoom: 11.0,
|
||||
return Builder(
|
||||
builder: (context) {
|
||||
// We need this additional builder here so that we can pass its context to
|
||||
// _AddPlaceButtonBar's onSavePressed callback. This callback shows a
|
||||
// SnackBar and to do this, we need a build context that has Scaffold as
|
||||
// an ancestor.
|
||||
return Center(
|
||||
child: Stack(
|
||||
children: [
|
||||
GoogleMap(
|
||||
onMapCreated: onMapCreated,
|
||||
initialCameraPosition: CameraPosition(
|
||||
target: widget.center!,
|
||||
zoom: 11.0,
|
||||
),
|
||||
mapType: _currentMapType,
|
||||
markers: _markers,
|
||||
onCameraMove: (position) => _lastMapPosition = position.target,
|
||||
),
|
||||
mapType: _currentMapType,
|
||||
markers: _markers,
|
||||
onCameraMove: (position) => _lastMapPosition = position.target,
|
||||
),
|
||||
_CategoryButtonBar(
|
||||
selectedPlaceCategory: state.selectedCategory,
|
||||
visible: _pendingMarker == null,
|
||||
onChanged: _switchSelectedCategory,
|
||||
),
|
||||
_AddPlaceButtonBar(
|
||||
visible: _pendingMarker != null,
|
||||
onSavePressed: () => _confirmAddPlace(context),
|
||||
onCancelPressed: _cancelAddPlace,
|
||||
),
|
||||
_MapFabs(
|
||||
visible: _pendingMarker == null,
|
||||
onAddPlacePressed: _onAddPlacePressed,
|
||||
onToggleMapTypePressed: _onToggleMapTypePressed,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
_CategoryButtonBar(
|
||||
selectedPlaceCategory: state.selectedCategory,
|
||||
visible: _pendingMarker == null,
|
||||
onChanged: _switchSelectedCategory,
|
||||
),
|
||||
_AddPlaceButtonBar(
|
||||
visible: _pendingMarker != null,
|
||||
onSavePressed: () => _confirmAddPlace(context),
|
||||
onCancelPressed: _cancelAddPlace,
|
||||
),
|
||||
_MapFabs(
|
||||
visible: _pendingMarker == null,
|
||||
onAddPlacePressed: _onAddPlacePressed,
|
||||
onToggleMapTypePressed: _onToggleMapTypePressed,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> onMapCreated(GoogleMapController controller) async {
|
||||
@@ -221,8 +220,10 @@ class _PlaceMapState extends State<PlaceMap> {
|
||||
scaffoldMessenger.showSnackBar(
|
||||
SnackBar(
|
||||
duration: const Duration(seconds: 3),
|
||||
content:
|
||||
const Text('New place added.', style: TextStyle(fontSize: 16.0)),
|
||||
content: const Text(
|
||||
'New place added.',
|
||||
style: TextStyle(fontSize: 16.0),
|
||||
),
|
||||
action: SnackBarAction(
|
||||
label: 'Edit',
|
||||
onPressed: () async {
|
||||
@@ -278,8 +279,9 @@ class _PlaceMapState extends State<PlaceMap> {
|
||||
// At this point, we know the places have been updated from the list
|
||||
// view. We need to reconfigure the map to respect the updates.
|
||||
for (final place in newConfiguration.places) {
|
||||
final oldPlace =
|
||||
_configuration!.places.firstWhereOrNull((p) => p.id == place.id);
|
||||
final oldPlace = _configuration!.places.firstWhereOrNull(
|
||||
(p) => p.id == place.id,
|
||||
);
|
||||
if (oldPlace == null || oldPlace != place) {
|
||||
// New place or updated place.
|
||||
_updateExistingPlaceMarker(place: place);
|
||||
@@ -336,10 +338,9 @@ class _PlaceMapState extends State<PlaceMap> {
|
||||
}
|
||||
});
|
||||
|
||||
await _zoomToFitPlaces(_getPlacesForCategory(
|
||||
category,
|
||||
_markedPlaces.values.toList(),
|
||||
));
|
||||
await _zoomToFitPlaces(
|
||||
_getPlacesForCategory(category, _markedPlaces.values.toList()),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _switchSelectedCategory(PlaceCategory category) async {
|
||||
@@ -348,8 +349,9 @@ class _PlaceMapState extends State<PlaceMap> {
|
||||
}
|
||||
|
||||
void _updateExistingPlaceMarker({required Place place}) {
|
||||
var marker = _markedPlaces.keys
|
||||
.singleWhere((value) => _markedPlaces[value]!.id == place.id);
|
||||
var marker = _markedPlaces.keys.singleWhere(
|
||||
(value) => _markedPlaces[value]!.id == place.id,
|
||||
);
|
||||
|
||||
setState(() {
|
||||
final updatedMarker = marker.copyWith(
|
||||
@@ -407,16 +409,20 @@ class _PlaceMapState extends State<PlaceMap> {
|
||||
Future<BitmapDescriptor> _getPlaceMarkerIcon(PlaceCategory category) =>
|
||||
switch (category) {
|
||||
PlaceCategory.favorite => BitmapDescriptor.asset(
|
||||
createLocalImageConfiguration(context, size: const Size.square(32)),
|
||||
'assets/heart.png'),
|
||||
createLocalImageConfiguration(context, size: const Size.square(32)),
|
||||
'assets/heart.png',
|
||||
),
|
||||
PlaceCategory.visited => BitmapDescriptor.asset(
|
||||
createLocalImageConfiguration(context, size: const Size.square(32)),
|
||||
'assets/visited.png'),
|
||||
createLocalImageConfiguration(context, size: const Size.square(32)),
|
||||
'assets/visited.png',
|
||||
),
|
||||
PlaceCategory.wantToGo => Future.value(BitmapDescriptor.defaultMarker),
|
||||
};
|
||||
|
||||
static List<Place> _getPlacesForCategory(
|
||||
PlaceCategory category, List<Place> places) {
|
||||
PlaceCategory category,
|
||||
List<Place> places,
|
||||
) {
|
||||
return places.where((place) => place.category == category).toList();
|
||||
}
|
||||
}
|
||||
@@ -496,10 +502,11 @@ class _CategoryButtonBar extends StatelessWidget {
|
||||
children: <Widget>[
|
||||
FilledButton(
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor:
|
||||
selectedPlaceCategory == PlaceCategory.favorite
|
||||
? Colors.green[700]
|
||||
: Colors.lightGreen),
|
||||
backgroundColor:
|
||||
selectedPlaceCategory == PlaceCategory.favorite
|
||||
? Colors.green[700]
|
||||
: Colors.lightGreen,
|
||||
),
|
||||
onPressed: () => onChanged(PlaceCategory.favorite),
|
||||
child: const Text(
|
||||
'Favorites',
|
||||
@@ -508,10 +515,11 @@ class _CategoryButtonBar extends StatelessWidget {
|
||||
),
|
||||
FilledButton(
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor:
|
||||
selectedPlaceCategory == PlaceCategory.visited
|
||||
? Colors.green[700]
|
||||
: Colors.lightGreen),
|
||||
backgroundColor:
|
||||
selectedPlaceCategory == PlaceCategory.visited
|
||||
? Colors.green[700]
|
||||
: Colors.lightGreen,
|
||||
),
|
||||
onPressed: () => onChanged(PlaceCategory.visited),
|
||||
child: const Text(
|
||||
'Visited',
|
||||
@@ -520,10 +528,11 @@ class _CategoryButtonBar extends StatelessWidget {
|
||||
),
|
||||
FilledButton(
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor:
|
||||
selectedPlaceCategory == PlaceCategory.wantToGo
|
||||
? Colors.green[700]
|
||||
: Colors.lightGreen),
|
||||
backgroundColor:
|
||||
selectedPlaceCategory == PlaceCategory.wantToGo
|
||||
? Colors.green[700]
|
||||
: Colors.lightGreen,
|
||||
),
|
||||
onPressed: () => onChanged(PlaceCategory.wantToGo),
|
||||
child: const Text(
|
||||
'Want To Go',
|
||||
|
||||
@@ -13,10 +13,7 @@ import 'place_list.dart';
|
||||
import 'place_map.dart';
|
||||
import 'stub_data.dart';
|
||||
|
||||
enum PlaceTrackerViewType {
|
||||
map,
|
||||
list,
|
||||
}
|
||||
enum PlaceTrackerViewType { map, list }
|
||||
|
||||
class PlaceTrackerApp extends StatelessWidget {
|
||||
const PlaceTrackerApp({super.key});
|
||||
@@ -35,25 +32,26 @@ class PlaceTrackerApp extends StatelessWidget {
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
routerConfig: GoRouter(routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (context, state) => const _PlaceTrackerHomePage(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'place/:id',
|
||||
builder: (context, state) {
|
||||
final id = state.pathParameters['id']!;
|
||||
final place = context
|
||||
.read<AppState>()
|
||||
.places
|
||||
.singleWhere((place) => place.id == id);
|
||||
return PlaceDetails(place: place);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
]),
|
||||
routerConfig: GoRouter(
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (context, state) => const _PlaceTrackerHomePage(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'place/:id',
|
||||
builder: (context, state) {
|
||||
final id = state.pathParameters['id']!;
|
||||
final place = context.read<AppState>().places.singleWhere(
|
||||
(place) => place.id == id,
|
||||
);
|
||||
return PlaceDetails(place: place);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -101,7 +99,7 @@ class _PlaceTrackerHomePage extends StatelessWidget {
|
||||
index: state.viewType == PlaceTrackerViewType.map ? 0 : 1,
|
||||
children: const [
|
||||
PlaceMap(center: LatLng(45.521563, -122.677433)),
|
||||
PlaceList()
|
||||
PlaceList(),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -148,6 +148,6 @@ class StubData {
|
||||
static const reviewStrings = [
|
||||
'My favorite place in Portland. The employees are wonderful and so is the food. I go here at least once a month!',
|
||||
'Staff was very friendly. Great atmosphere and good music. Would recommend.',
|
||||
'Best. Place. In. Town. Period.'
|
||||
'Best. Place. In. Town. Period.',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ description: A new Flutter project.
|
||||
version: 1.0.0+1
|
||||
|
||||
environment:
|
||||
sdk: ^3.5.0
|
||||
sdk: ^3.7.0-0
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
|
||||
Reference in New Issue
Block a user