mirror of
https://github.com/flutter/samples.git
synced 2025-11-08 22:09:06 +00:00
migrate place_tracker to go_router (#1529)
This commit is contained in:
@@ -42,6 +42,27 @@ class Place {
|
|||||||
starRating: starRating ?? this.starRating,
|
starRating: starRating ?? this.starRating,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is Place &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
id == other.id &&
|
||||||
|
latLng == other.latLng &&
|
||||||
|
name == other.name &&
|
||||||
|
category == other.category &&
|
||||||
|
description == other.description &&
|
||||||
|
starRating == other.starRating;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
id.hashCode ^
|
||||||
|
latLng.hashCode ^
|
||||||
|
name.hashCode ^
|
||||||
|
category.hashCode ^
|
||||||
|
description.hashCode ^
|
||||||
|
starRating.hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PlaceCategory {
|
enum PlaceCategory {
|
||||||
|
|||||||
@@ -4,17 +4,17 @@
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'place.dart';
|
import 'place.dart';
|
||||||
|
import 'place_tracker_app.dart';
|
||||||
import 'stub_data.dart';
|
import 'stub_data.dart';
|
||||||
|
|
||||||
class PlaceDetails extends StatefulWidget {
|
class PlaceDetails extends StatefulWidget {
|
||||||
final Place place;
|
final Place place;
|
||||||
final ValueChanged<Place> onChanged;
|
|
||||||
|
|
||||||
const PlaceDetails({
|
const PlaceDetails({
|
||||||
required this.place,
|
required this.place,
|
||||||
required this.onChanged,
|
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ class _PlaceDetailsState extends State<PlaceDetails> {
|
|||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: const Icon(Icons.save, size: 30.0),
|
icon: const Icon(Icons.save, size: 30.0),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
widget.onChanged(_place);
|
_onChanged(_place);
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -61,7 +61,7 @@ class _PlaceDetailsState extends State<PlaceDetails> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
_place = widget.place;
|
_place = widget.place;
|
||||||
_nameController.text = _place.name;
|
_nameController.text = _place.name;
|
||||||
_descriptionController.text = _place.description!;
|
_descriptionController.text = _place.description ?? '';
|
||||||
return super.initState();
|
return super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,12 +113,22 @@ class _PlaceDetailsState extends State<PlaceDetails> {
|
|||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onChanged(Place value) {
|
||||||
|
// Replace the place with the modified version.
|
||||||
|
final newPlaces = List<Place>.from(context.read<AppState>().places);
|
||||||
|
final index = newPlaces.indexWhere((place) => place.id == value.id);
|
||||||
|
newPlaces[index] = value;
|
||||||
|
|
||||||
|
context.read<AppState>().setPlaces(newPlaces);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DescriptionTextField extends StatelessWidget {
|
class _DescriptionTextField extends StatelessWidget {
|
||||||
final TextEditingController controller;
|
final TextEditingController controller;
|
||||||
|
|
||||||
final ValueChanged<String> onChanged;
|
final ValueChanged<String> onChanged;
|
||||||
|
|
||||||
const _DescriptionTextField({
|
const _DescriptionTextField({
|
||||||
required this.controller,
|
required this.controller,
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
@@ -151,6 +161,7 @@ class _Map extends StatelessWidget {
|
|||||||
final GoogleMapController? mapController;
|
final GoogleMapController? mapController;
|
||||||
final ArgumentCallback<GoogleMapController> onMapCreated;
|
final ArgumentCallback<GoogleMapController> onMapCreated;
|
||||||
final Set<Marker> markers;
|
final Set<Marker> markers;
|
||||||
|
|
||||||
const _Map({
|
const _Map({
|
||||||
required this.center,
|
required this.center,
|
||||||
required this.mapController,
|
required this.mapController,
|
||||||
@@ -187,6 +198,7 @@ class _NameTextField extends StatelessWidget {
|
|||||||
final TextEditingController controller;
|
final TextEditingController controller;
|
||||||
|
|
||||||
final ValueChanged<String> onChanged;
|
final ValueChanged<String> onChanged;
|
||||||
|
|
||||||
const _NameTextField({
|
const _NameTextField({
|
||||||
required this.controller,
|
required this.controller,
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
@@ -304,6 +316,7 @@ class _StarBar extends StatelessWidget {
|
|||||||
|
|
||||||
final int rating;
|
final int rating;
|
||||||
final ValueChanged<int> onChanged;
|
final ValueChanged<int> onChanged;
|
||||||
|
|
||||||
const _StarBar({
|
const _StarBar({
|
||||||
required this.rating,
|
required this.rating,
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'place.dart';
|
import 'place.dart';
|
||||||
import 'place_details.dart';
|
|
||||||
import 'place_tracker_app.dart';
|
import 'place_tracker_app.dart';
|
||||||
|
|
||||||
class PlaceList extends StatefulWidget {
|
class PlaceList extends StatefulWidget {
|
||||||
@@ -35,10 +35,7 @@ class _PlaceListState extends State<PlaceList> {
|
|||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
children: state.places
|
children: state.places
|
||||||
.where((place) => place.category == state.selectedCategory)
|
.where((place) => place.category == state.selectedCategory)
|
||||||
.map((place) => _PlaceListTile(
|
.map((place) => _PlaceListTile(place: place))
|
||||||
place: place,
|
|
||||||
onPlaceChanged: (value) => _onPlaceChanged(value),
|
|
||||||
))
|
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -51,16 +48,6 @@ class _PlaceListState extends State<PlaceList> {
|
|||||||
Provider.of<AppState>(context, listen: false)
|
Provider.of<AppState>(context, listen: false)
|
||||||
.setSelectedCategory(newCategory);
|
.setSelectedCategory(newCategory);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onPlaceChanged(Place value) {
|
|
||||||
// Replace the place with the modified version.
|
|
||||||
final newPlaces =
|
|
||||||
List<Place>.from(Provider.of<AppState>(context, listen: false).places);
|
|
||||||
final index = newPlaces.indexWhere((place) => place.id == value.id);
|
|
||||||
newPlaces[index] = value;
|
|
||||||
|
|
||||||
Provider.of<AppState>(context, listen: false).setPlaces(newPlaces);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CategoryButton extends StatelessWidget {
|
class _CategoryButton extends StatelessWidget {
|
||||||
@@ -68,6 +55,7 @@ class _CategoryButton extends StatelessWidget {
|
|||||||
|
|
||||||
final bool selected;
|
final bool selected;
|
||||||
final ValueChanged<PlaceCategory> onCategoryChanged;
|
final ValueChanged<PlaceCategory> onCategoryChanged;
|
||||||
|
|
||||||
const _CategoryButton({
|
const _CategoryButton({
|
||||||
required this.category,
|
required this.category,
|
||||||
required this.selected,
|
required this.selected,
|
||||||
@@ -118,6 +106,7 @@ class _ListCategoryButtonBar extends StatelessWidget {
|
|||||||
final PlaceCategory selectedCategory;
|
final PlaceCategory selectedCategory;
|
||||||
|
|
||||||
final ValueChanged<PlaceCategory> onCategoryChanged;
|
final ValueChanged<PlaceCategory> onCategoryChanged;
|
||||||
|
|
||||||
const _ListCategoryButtonBar({
|
const _ListCategoryButtonBar({
|
||||||
required this.selectedCategory,
|
required this.selectedCategory,
|
||||||
required this.onCategoryChanged,
|
required this.onCategoryChanged,
|
||||||
@@ -151,24 +140,14 @@ class _ListCategoryButtonBar extends StatelessWidget {
|
|||||||
class _PlaceListTile extends StatelessWidget {
|
class _PlaceListTile extends StatelessWidget {
|
||||||
final Place place;
|
final Place place;
|
||||||
|
|
||||||
final ValueChanged<Place> onPlaceChanged;
|
|
||||||
const _PlaceListTile({
|
const _PlaceListTile({
|
||||||
required this.place,
|
required this.place,
|
||||||
required this.onPlaceChanged,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () => Navigator.push<void>(
|
onTap: () => context.go('/place/${place.id}'),
|
||||||
context,
|
|
||||||
MaterialPageRoute(builder: (context) {
|
|
||||||
return PlaceDetails(
|
|
||||||
place: place,
|
|
||||||
onChanged: (value) => onPlaceChanged(value),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.only(top: 16.0),
|
padding: const EdgeInsets.only(top: 16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|||||||
@@ -5,13 +5,14 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
import 'place.dart';
|
import 'place.dart';
|
||||||
import 'place_details.dart';
|
|
||||||
import 'place_tracker_app.dart';
|
import 'place_tracker_app.dart';
|
||||||
|
|
||||||
class MapConfiguration {
|
class MapConfiguration {
|
||||||
@@ -78,10 +79,21 @@ class _PlaceMapState extends State<PlaceMap> {
|
|||||||
MapConfiguration? _configuration;
|
MapConfiguration? _configuration;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
void initState() {
|
||||||
_maybeUpdateMapConfiguration();
|
super.initState();
|
||||||
var state = Provider.of<AppState>(context);
|
context.read<AppState>().addListener(_watchMapConfigurationChanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
context.read<AppState>().removeListener(_watchMapConfigurationChanges);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
_watchMapConfigurationChanges();
|
||||||
|
var state = Provider.of<AppState>(context);
|
||||||
return Builder(builder: (context) {
|
return Builder(builder: (context) {
|
||||||
// We need this additional builder here so that we can pass its context to
|
// We need this additional builder here so that we can pass its context to
|
||||||
// _AddPlaceButtonBar's onSavePressed callback. This callback shows a
|
// _AddPlaceButtonBar's onSavePressed callback. This callback shows a
|
||||||
@@ -189,7 +201,7 @@ class _PlaceMapState extends State<PlaceMap> {
|
|||||||
infoWindowParam: InfoWindow(
|
infoWindowParam: InfoWindow(
|
||||||
title: 'New Place',
|
title: 'New Place',
|
||||||
snippet: null,
|
snippet: null,
|
||||||
onTap: () => _pushPlaceDetailsScreen(newPlace),
|
onTap: () => context.go('/place/${newPlace.id}'),
|
||||||
),
|
),
|
||||||
draggableParam: false,
|
draggableParam: false,
|
||||||
);
|
);
|
||||||
@@ -212,7 +224,7 @@ class _PlaceMapState extends State<PlaceMap> {
|
|||||||
action: SnackBarAction(
|
action: SnackBarAction(
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
_pushPlaceDetailsScreen(newPlace);
|
context.go('/place/${newPlace.id}');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -221,14 +233,6 @@ class _PlaceMapState extends State<PlaceMap> {
|
|||||||
// Add the new place to the places stored in appState.
|
// Add the new place to the places stored in appState.
|
||||||
final newPlaces = List<Place>.from(appState.places)..add(newPlace);
|
final newPlaces = List<Place>.from(appState.places)..add(newPlace);
|
||||||
|
|
||||||
// Manually update our map configuration here since our map is already
|
|
||||||
// updated with the new marker. Otherwise, the map would be reconfigured
|
|
||||||
// in the main build method due to a modified AppState.
|
|
||||||
_configuration = MapConfiguration(
|
|
||||||
places: newPlaces,
|
|
||||||
selectedCategory: appState.selectedCategory,
|
|
||||||
);
|
|
||||||
|
|
||||||
appState.setPlaces(newPlaces);
|
appState.setPlaces(newPlaces);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -240,7 +244,7 @@ class _PlaceMapState extends State<PlaceMap> {
|
|||||||
infoWindow: InfoWindow(
|
infoWindow: InfoWindow(
|
||||||
title: place.name,
|
title: place.name,
|
||||||
snippet: '${place.starRating} Star Rating',
|
snippet: '${place.starRating} Star Rating',
|
||||||
onTap: () => _pushPlaceDetailsScreen(place),
|
onTap: () => context.go('/place/${place.id}'),
|
||||||
),
|
),
|
||||||
icon: await _getPlaceMarkerIcon(context, place.category),
|
icon: await _getPlaceMarkerIcon(context, place.category),
|
||||||
visible: place.category ==
|
visible: place.category ==
|
||||||
@@ -250,11 +254,10 @@ class _PlaceMapState extends State<PlaceMap> {
|
|||||||
return marker;
|
return marker;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _maybeUpdateMapConfiguration() async {
|
Future<void> _watchMapConfigurationChanges() async {
|
||||||
_configuration ??=
|
final appState = context.read<AppState>();
|
||||||
MapConfiguration.of(Provider.of<AppState>(context, listen: false));
|
_configuration ??= MapConfiguration.of(appState);
|
||||||
final newConfiguration =
|
final newConfiguration = MapConfiguration.of(appState);
|
||||||
MapConfiguration.of(Provider.of<AppState>(context, listen: false));
|
|
||||||
|
|
||||||
// Since we manually update [_configuration] when place or selectedCategory
|
// Since we manually update [_configuration] when place or selectedCategory
|
||||||
// changes come from the [place_map], we should only enter this if statement
|
// changes come from the [place_map], we should only enter this if statement
|
||||||
@@ -270,9 +273,14 @@ class _PlaceMapState extends State<PlaceMap> {
|
|||||||
} else {
|
} else {
|
||||||
// At this point, we know the places have been updated from the list
|
// At this point, we know the places have been updated from the list
|
||||||
// view. We need to reconfigure the map to respect the updates.
|
// view. We need to reconfigure the map to respect the updates.
|
||||||
newConfiguration.places
|
for (final place in newConfiguration.places) {
|
||||||
.where((p) => !_configuration!.places.contains(p))
|
final oldPlace =
|
||||||
.map((value) => _updateExistingPlaceMarker(place: value));
|
_configuration!.places.firstWhereOrNull((p) => p.id == place.id);
|
||||||
|
if (oldPlace == null || oldPlace != place) {
|
||||||
|
// New place or updated place.
|
||||||
|
_updateExistingPlaceMarker(place: place);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await _zoomToFitPlaces(
|
await _zoomToFitPlaces(
|
||||||
_getPlacesForCategory(
|
_getPlacesForCategory(
|
||||||
@@ -299,27 +307,6 @@ class _PlaceMapState extends State<PlaceMap> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onPlaceChanged(Place value) {
|
|
||||||
// Replace the place with the modified version.
|
|
||||||
final newPlaces =
|
|
||||||
List<Place>.from(Provider.of<AppState>(context, listen: false).places);
|
|
||||||
final index = newPlaces.indexWhere((place) => place.id == value.id);
|
|
||||||
newPlaces[index] = value;
|
|
||||||
|
|
||||||
_updateExistingPlaceMarker(place: value);
|
|
||||||
|
|
||||||
// Manually update our map configuration here since our map is already
|
|
||||||
// updated with the new marker. Otherwise, the map would be reconfigured
|
|
||||||
// in the main build method due to a modified AppState.
|
|
||||||
_configuration = MapConfiguration(
|
|
||||||
places: newPlaces,
|
|
||||||
selectedCategory:
|
|
||||||
Provider.of<AppState>(context, listen: false).selectedCategory,
|
|
||||||
);
|
|
||||||
|
|
||||||
Provider.of<AppState>(context, listen: false).setPlaces(newPlaces);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onToggleMapTypePressed() {
|
void _onToggleMapTypePressed() {
|
||||||
final nextType =
|
final nextType =
|
||||||
MapType.values[(_currentMapType.index + 1) % MapType.values.length];
|
MapType.values[(_currentMapType.index + 1) % MapType.values.length];
|
||||||
@@ -329,18 +316,6 @@ class _PlaceMapState extends State<PlaceMap> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _pushPlaceDetailsScreen(Place place) {
|
|
||||||
Navigator.push<void>(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(builder: (context) {
|
|
||||||
return PlaceDetails(
|
|
||||||
place: place,
|
|
||||||
onChanged: (value) => _onPlaceChanged(value),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _showPlacesForSelectedCategory(PlaceCategory category) async {
|
Future<void> _showPlacesForSelectedCategory(PlaceCategory category) async {
|
||||||
setState(() {
|
setState(() {
|
||||||
for (var marker in List.of(_markedPlaces.keys)) {
|
for (var marker in List.of(_markedPlaces.keys)) {
|
||||||
@@ -412,7 +387,8 @@ class _PlaceMapState extends State<PlaceMap> {
|
|||||||
maxLong = max(maxLong, place.longitude);
|
maxLong = max(maxLong, place.longitude);
|
||||||
}
|
}
|
||||||
|
|
||||||
await controller.animateCamera(
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
controller.animateCamera(
|
||||||
CameraUpdate.newLatLngBounds(
|
CameraUpdate.newLatLngBounds(
|
||||||
LatLngBounds(
|
LatLngBounds(
|
||||||
southwest: LatLng(minLat, minLong),
|
southwest: LatLng(minLat, minLong),
|
||||||
@@ -421,6 +397,7 @@ class _PlaceMapState extends State<PlaceMap> {
|
|||||||
48.0,
|
48.0,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<BitmapDescriptor> _getPlaceMarkerIcon(
|
static Future<BitmapDescriptor> _getPlaceMarkerIcon(
|
||||||
|
|||||||
@@ -3,10 +3,12 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'place.dart';
|
import 'place.dart';
|
||||||
|
import 'place_details.dart';
|
||||||
import 'place_list.dart';
|
import 'place_list.dart';
|
||||||
import 'place_map.dart';
|
import 'place_map.dart';
|
||||||
import 'stub_data.dart';
|
import 'stub_data.dart';
|
||||||
@@ -21,8 +23,26 @@ class PlaceTrackerApp extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const MaterialApp(
|
return MaterialApp.router(
|
||||||
home: _PlaceTrackerHomePage(),
|
routerConfig: GoRouter(routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: '/',
|
||||||
|
builder: (context, state) => const _PlaceTrackerHomePage(),
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: 'place/:id',
|
||||||
|
builder: (context, state) {
|
||||||
|
final id = state.params['id']!;
|
||||||
|
final place = context
|
||||||
|
.read<AppState>()
|
||||||
|
.places
|
||||||
|
.singleWhere((place) => place.id == id);
|
||||||
|
return PlaceDetails(place: place);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ dependencies:
|
|||||||
google_maps_flutter_web: ">=0.3.0+1 <0.5.0"
|
google_maps_flutter_web: ">=0.3.0+1 <0.5.0"
|
||||||
provider: ^6.0.2
|
provider: ^6.0.2
|
||||||
uuid: ^3.0.4
|
uuid: ^3.0.4
|
||||||
|
go_router: ^5.2.4
|
||||||
|
collection: ^1.16.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user