better help and onboarding
All checks were successful
Build and deploy the backend to staging / Build and push image (pull_request) Successful in 1m40s
Build and release debug APK / Build APK (pull_request) Successful in 7m23s
Build and deploy the backend to staging / Deploy to staging (pull_request) Successful in 14s

This commit is contained in:
Remy Moll 2024-11-18 17:42:52 +01:00
parent d58ef2562d
commit 3f1fe463bf
19 changed files with 326 additions and 242 deletions

View File

@ -1,6 +1,6 @@
import 'package:anyway/utils/get_first_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:anyway/constants.dart'; import 'package:anyway/constants.dart';
import 'package:anyway/layout.dart';
void main() => runApp(const App()); void main() => runApp(const App());
@ -14,7 +14,7 @@ class App extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
title: APP_NAME, title: APP_NAME,
home: BasePage(mainScreen: "map"), home: getFirstPage(),
theme: APP_THEME, theme: APP_THEME,
scaffoldMessengerKey: rootScaffoldMessengerKey scaffoldMessengerKey: rootScaffoldMessengerKey
); );

View File

@ -73,7 +73,7 @@ class _CurrentTripPanelState extends State<CurrentTripPanel> {
const Padding(padding: EdgeInsets.only(top: 10)), const Padding(padding: EdgeInsets.only(top: 10)),
Center(child: saveButton(widget.trip)), Center(child: saveButton(trip: widget.trip)),
], ],
); );
} }

View File

@ -5,10 +5,22 @@ import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
Widget saveButton(Trip trip) => ElevatedButton(
class saveButton extends StatefulWidget {
Trip trip;
saveButton({super.key, required this.trip});
@override
State<saveButton> createState() => _saveButtonState();
}
class _saveButtonState extends State<saveButton> {
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () async { onPressed: () async {
SharedPreferences prefs = await SharedPreferences.getInstance(); SharedPreferences prefs = await SharedPreferences.getInstance();
trip.toPrefs(prefs); setState(() => widget.trip.toPrefs(prefs));
rootScaffoldMessengerKey.currentState!.showSnackBar( rootScaffoldMessengerKey.currentState!.showSnackBar(
SnackBar( SnackBar(
content: Text('Trip saved'), content: Text('Trip saved'),
@ -38,4 +50,6 @@ Widget saveButton(Trip trip) => ElevatedButton(
), ),
) )
); );
}
}

View File

@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
Future<void> helpDialog(BuildContext context, String title, String content) {
return showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(title),
content: Text(content),
actions: <Widget>[
TextButton(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge,
),
child: const Text('Got it!'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}

View File

@ -17,7 +17,6 @@ class LandmarkCard extends StatefulWidget {
class _LandmarkCardState extends State<LandmarkCard> { class _LandmarkCardState extends State<LandmarkCard> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
ThemeData theme = Theme.of(context);
return Container( return Container(
height: 160, height: 160,
child: Card( child: Card(

View File

@ -1,5 +1,5 @@
import 'package:anyway/layout.dart';
import 'package:anyway/main.dart'; import 'package:anyway/main.dart';
import 'package:anyway/pages/current_trip.dart';
import 'package:anyway/structs/preferences.dart'; import 'package:anyway/structs/preferences.dart';
import 'package:anyway/structs/trip.dart'; import 'package:anyway/structs/trip.dart';
import 'package:anyway/utils/fetch_trip.dart'; import 'package:anyway/utils/fetch_trip.dart';
@ -57,7 +57,7 @@ class _NewTripButtonState extends State<NewTripButton> {
fetchTrip(trip, widget.preferences); fetchTrip(trip, widget.preferences);
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => BasePage(mainScreen: "map", trip: trip) builder: (context) => TripPage(trip: trip)
) )
); );
} }

View File

@ -2,13 +2,11 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
class OnboardingCard extends StatelessWidget { class OnboardingCard extends StatelessWidget {
int index; final String title;
String title; final String description;
String description; final String imagePath;
String imagePath;
OnboardingCard({ const OnboardingCard({
required this.index,
required this.title, required this.title,
required this.description, required this.description,
required this.imagePath, required this.imagePath,
@ -16,13 +14,8 @@ class OnboardingCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Color baseColor = Theme.of(context).colorScheme.secondary;
// have a different color for each card, incrementing the hue return Padding(
Color currentColor = baseColor.withAlpha(baseColor.alpha - index * 30);
return Container(
color: currentColor,
alignment: Alignment.center,
child: Padding(
padding: EdgeInsets.all(20), padding: EdgeInsets.all(20),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@ -50,7 +43,6 @@ class OnboardingCard extends StatelessWidget {
] ]
), ),
)
); );
} }
} }

View File

@ -1,6 +1,6 @@
import 'package:anyway/pages/current_trip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:anyway/layout.dart';
import 'package:anyway/structs/trip.dart'; import 'package:anyway/structs/trip.dart';
@ -16,7 +16,6 @@ class TripsOverview extends StatefulWidget {
} }
class _TripsOverviewState extends State<TripsOverview> { class _TripsOverviewState extends State<TripsOverview> {
Widget listBuild (BuildContext context, AsyncSnapshot<List<Trip>> snapshot) { Widget listBuild (BuildContext context, AsyncSnapshot<List<Trip>> snapshot) {
List<Widget> children; List<Widget> children;
if (snapshot.hasData) { if (snapshot.hasData) {
@ -39,7 +38,7 @@ class _TripsOverviewState extends State<TripsOverview> {
onTap: () { onTap: () {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => BasePage(mainScreen: "map", trip: trip) builder: (context) => TripPage(trip: trip)
) )
); );
}, },

View File

@ -1,3 +1,5 @@
import 'package:anyway/modules/help_dialog.dart';
import 'package:anyway/pages/current_trip.dart';
import 'package:anyway/pages/settings.dart'; import 'package:anyway/pages/settings.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -8,22 +10,24 @@ import 'package:anyway/modules/trips_saved_list.dart';
import 'package:anyway/utils/load_trips.dart'; import 'package:anyway/utils/load_trips.dart';
import 'package:anyway/pages/new_trip_location.dart'; import 'package:anyway/pages/new_trip_location.dart';
import 'package:anyway/pages/current_trip.dart';
import 'package:anyway/pages/onboarding.dart'; import 'package:anyway/pages/onboarding.dart';
// BasePage is the scaffold that holds all other pages // BasePage is the scaffold that holds a child page and a side drawer
// A side drawer is used to switch between pages // The side drawer is the main way to switch between pages
class BasePage extends StatefulWidget { class BasePage extends StatefulWidget {
final String mainScreen; final Widget mainScreen;
final Trip? trip; final Widget title;
final List<String> helpTexts;
const BasePage({ const BasePage({
super.key, super.key,
required this.mainScreen, required this.mainScreen,
this.trip, this.title = const Text(APP_NAME),
this.helpTexts = const [],
}); });
@override @override
@ -34,53 +38,24 @@ class _BasePageState extends State<BasePage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget currentView = const Text("loading...");
Future<List<Trip>> trips = loadTrips(); Future<List<Trip>> trips = loadTrips();
if (widget.mainScreen == "map") {
if (widget.trip != null) {
currentView = TripPage(trip: widget.trip!);
} else {
currentView = FutureBuilder(
future: trips,
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Trip> availableTrips = snapshot.data!;
if (availableTrips.isNotEmpty) {
return TripPage(trip: availableTrips[0]);
} else {
return Scaffold( return Scaffold(
body: Center( appBar: AppBar(
child: Text("Wow, so empty!"), title: widget.title,
), actions: [
floatingActionButton: FloatingActionButton.extended( IconButton(
icon: const Icon(Icons.help),
tooltip: 'Help',
onPressed: () { onPressed: () {
Navigator.of(context).push( if (widget.helpTexts.isNotEmpty) {
MaterialPageRoute( helpDialog(context, widget.helpTexts[0], widget.helpTexts[1]);
builder: (context) => const NewTripPage() }
) }
);
},
label: Text("Plan a trip"),
), ),
); ],
} ),
} else { body: Center(child: widget.mainScreen),
return const Text("loading...");
}
},
);
}
} else if (widget.mainScreen == "tutorial") {
currentView = OnboardingPage();
} else if (widget.mainScreen == "settings") {
currentView = SettingsPage();
}
return Scaffold(
appBar: AppBar(title: Text(APP_NAME)),
body: Center(child: currentView),
drawer: Drawer( drawer: Drawer(
child: Column( child: Column(
children: [ children: [
@ -104,7 +79,8 @@ class _BasePageState extends State<BasePage> {
ListTile( ListTile(
title: const Text('Your Trips'), title: const Text('Your Trips'),
leading: const Icon(Icons.map), leading: const Icon(Icons.map),
selected: widget.mainScreen == "map", // TODO: this is not working!
selected: widget.mainScreen is TripPage,
onTap: () {}, onTap: () {},
trailing: ElevatedButton( trailing: ElevatedButton(
onPressed: () { onPressed: () {
@ -134,11 +110,12 @@ class _BasePageState extends State<BasePage> {
ListTile( ListTile(
title: const Text('How to use'), title: const Text('How to use'),
leading: Icon(Icons.help), leading: Icon(Icons.help),
selected: widget.mainScreen == "tutorial", // TODO: this is not working!
selected: widget.mainScreen is OnboardingPage,
onTap: () { onTap: () {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => BasePage(mainScreen: "tutorial") builder: (context) => OnboardingPage()
) )
); );
}, },
@ -148,11 +125,12 @@ class _BasePageState extends State<BasePage> {
ListTile( ListTile(
title: const Text('Settings'), title: const Text('Settings'),
leading: const Icon(Icons.settings), leading: const Icon(Icons.settings),
selected: widget.mainScreen == "settings", // TODO: this is not working!
selected: widget.mainScreen is SettingsPage,
onTap: () { onTap: () {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => BasePage(mainScreen: "settings") builder: (context) => SettingsPage()
) )
); );
}, },

View File

@ -1,4 +1,5 @@
import 'package:anyway/constants.dart'; import 'package:anyway/constants.dart';
import 'package:anyway/pages/base_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:sliding_up_panel/sliding_up_panel.dart'; import 'package:sliding_up_panel/sliding_up_panel.dart';
@ -31,7 +32,8 @@ class _TripPageState extends State<TripPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SlidingUpPanel( return BasePage(
mainScreen: SlidingUpPanel(
// use panelBuilder instead of panel so that we can reuse the scrollcontroller for the listview // use panelBuilder instead of panel so that we can reuse the scrollcontroller for the listview
panelBuilder: (scrollcontroller) => CurrentTripPanel(controller: scrollcontroller, trip: widget.trip), panelBuilder: (scrollcontroller) => CurrentTripPanel(controller: scrollcontroller, trip: widget.trip),
// using collapsed and panelBuilder seems to show both at the same time, so we include the greeter in the panelBuilder // using collapsed and panelBuilder seems to show both at the same time, so we include the greeter in the panelBuilder
@ -52,6 +54,13 @@ class _TripPageState extends State<TripPage> {
color: Colors.black, color: Colors.black,
) )
], ],
),
title: FutureBuilder(
future: widget.trip.cityName,
builder: (context, snapshot) => Text(
'Your trip to ${snapshot.hasData ? snapshot.data! : "..."}',
)
),
); );
} }
} }

View File

@ -1,5 +1,5 @@
import 'package:anyway/modules/new_trip_button.dart';
import 'package:anyway/modules/new_trip_options_button.dart'; import 'package:anyway/modules/new_trip_options_button.dart';
import 'package:anyway/pages/base_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import "package:anyway/structs/trip.dart"; import "package:anyway/structs/trip.dart";
@ -19,13 +19,12 @@ class _NewTripPageState extends State<NewTripPage> {
final TextEditingController lonController = TextEditingController(); final TextEditingController lonController = TextEditingController();
Trip trip = Trip(); Trip trip = Trip();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// floating search bar and map as a background // floating search bar and map as a background
return Scaffold( return BasePage(
appBar: AppBar( mainScreen: Scaffold(
title: const Text('New Trip'),
),
body: Stack( body: Stack(
children: [ children: [
NewTripMap(trip), NewTripMap(trip),
@ -36,6 +35,12 @@ class _NewTripPageState extends State<NewTripPage> {
], ],
), ),
floatingActionButton: NewTripOptionsButton(trip: trip), floatingActionButton: NewTripOptionsButton(trip: trip),
),
title: Text("New Trip"),
helpTexts: [
"Setting the start location",
"To set the starting point, type a city name in the search bar. You can also navigate the map like you're used to and long press anywhere to set a starting point."
],
); );
} }
} }

View File

@ -1,4 +1,5 @@
import 'package:anyway/modules/new_trip_button.dart'; import 'package:anyway/modules/new_trip_button.dart';
import 'package:anyway/pages/base_page.dart';
import 'package:anyway/structs/preferences.dart'; import 'package:anyway/structs/preferences.dart';
import 'package:anyway/structs/trip.dart'; import 'package:anyway/structs/trip.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
@ -19,7 +20,8 @@ class _NewTripPreferencesPageState extends State<NewTripPreferencesPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return BasePage(
mainScreen: Scaffold(
body: ListView( body: ListView(
children: [ children: [
// Center( // Center(
@ -28,16 +30,16 @@ class _NewTripPreferencesPageState extends State<NewTripPreferencesPage> {
// child: Icon(Icons.person, size: 100), // child: Icon(Icons.person, size: 100),
// ) // )
// ), // ),
Padding(padding: EdgeInsets.only(top: 30)), // Padding(padding: EdgeInsets.only(top: 30)),
Center( // Center(
child: FutureBuilder( // child: FutureBuilder(
future: widget.trip.cityName, // future: widget.trip.cityName,
builder: (context, snapshot) => Text( // builder: (context, snapshot) => Text(
'Your trip to ${snapshot.hasData ? snapshot.data! : "..."}', // 'Your trip to ${snapshot.hasData ? snapshot.data! : "..."}',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold) // style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)
) // )
) // )
), // ),
Center( Center(
child: Padding( child: Padding(
@ -54,6 +56,18 @@ class _NewTripPreferencesPageState extends State<NewTripPreferencesPage> {
] ]
), ),
floatingActionButton: NewTripButton(trip: widget.trip, preferences: preferences), floatingActionButton: NewTripButton(trip: widget.trip, preferences: preferences),
),
title: FutureBuilder(
future: widget.trip.cityName,
builder: (context, snapshot) => Text(
'Your trip to ${snapshot.hasData ? snapshot.data! : "..."}',
)
),
helpTexts: [
'Trip preferences',
'Set your preferences for this trip. These will be used to generate a custom itinerary.'
],
); );
} }

View File

@ -2,6 +2,26 @@ import 'package:anyway/modules/onboarding_card.dart';
import 'package:anyway/pages/new_trip_location.dart'; import 'package:anyway/pages/new_trip_location.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
const List<Widget> onboardingCards = [
OnboardingCard(
title: "Welcome to anyway!",
description: "Anyway helps you plan a city trip that suits your wishes.",
imagePath: "assets/city.svg"
),
OnboardingCard(
title: "Find your way",
description: "Bored by churches? No problem! Hate shopping? No worries! Instead of suggesting the generic trips that bore you, anyway will try to give you recommendations that really suit you.",
imagePath: "assets/plan.svg"
),
OnboardingCard(
title: "Change your mind",
description: "Feet get sore, the weather changes. Anyway understands that! Move or remove destinations, visit hidden gems along your journey, do your own thing. Anyway adapts to your spontaneous decisions.",
imagePath: "assets/cat.svg"
),
];
class OnboardingPage extends StatefulWidget { class OnboardingPage extends StatefulWidget {
const OnboardingPage({super.key}); const OnboardingPage({super.key});
@ -13,24 +33,32 @@ class _OnboardingPageState extends State<OnboardingPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final PageController _controller = PageController(); final PageController _controller = PageController();
return Scaffold( return Scaffold(
body: Stack( body: Stack(
children: [ children: [
PageView(
// horizontally scrollable list of pages // horizontally scrollable list of pages
PageView(
controller: _controller, controller: _controller,
children: [ children: List.generate(
OnboardingCard(index: 1, title: "Welcome to anyway!", description: "Anyway helps you plan a city trip that suits your wishes.", imagePath: "assets/city.svg"), onboardingCards.length,
OnboardingCard(index: 2, title: "Find your way", description: "Bored by churches? No problem! Hate shopping? No worries! More than showing you the typical 'must-sees' of a city, anyway will try to give you recommendations that really suit you.", imagePath: "assets/plan.svg"), (index) {
OnboardingCard(index: 3, title: "Change your mind", description: "Life happens when you're busy making plans. Anyway understands that! Move or remove destinations, visit hidden gems along your journey, do your own thing. Anyway adapts to your spontaneous decisions.", imagePath: "assets/cat.svg"), Color currentColor = Colors.red.withAlpha(Colors.red.alpha - index * 30);
], return Container(
color: currentColor,
alignment: Alignment.center,
child: onboardingCards[index],
);
}
)
), ),
], ],
), ),
floatingActionButton: FloatingActionButton(
floatingActionButton: FloatingActionButton.extended(
onPressed: () { onPressed: () {
if (_controller.page == 2) { if (_controller.page == onboardingCards.length - 1) {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => const NewTripPage() builder: (context) => const NewTripPage()
@ -40,7 +68,24 @@ class _OnboardingPageState extends State<OnboardingPage> {
_controller.nextPage(duration: Duration(milliseconds: 500), curve: Curves.ease); _controller.nextPage(duration: Duration(milliseconds: 500), curve: Curves.ease);
} }
}, },
child: Icon(Icons.arrow_forward), label: ListenableBuilder(
listenable: _controller,
builder: (context, child) {
if (_controller.page == onboardingCards.length - 1) {
// icon and text side by side
return Row(
children: [
const Text("Start planning!"),
Padding(padding: const EdgeInsets.only(right: 8.0)),
const Icon(Icons.map_outlined)
],
);
} else {
return const Icon(Icons.arrow_forward);
}
}
)
), ),
); );
} }

View File

@ -1,5 +1,6 @@
import 'package:anyway/constants.dart'; import 'package:anyway/constants.dart';
import 'package:anyway/main.dart'; import 'package:anyway/main.dart';
import 'package:anyway/pages/base_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@ -16,7 +17,8 @@ class SettingsPage extends StatefulWidget {
class _SettingsPageState extends State<SettingsPage> { class _SettingsPageState extends State<SettingsPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListView( return BasePage(
mainScreen: ListView(
padding: EdgeInsets.all(15), padding: EdgeInsets.all(15),
children: [ children: [
// First a round, centered image // First a round, centered image
@ -40,6 +42,12 @@ class _SettingsPageState extends State<SettingsPage> {
privacyInfo(), privacyInfo(),
] ]
),
title: Text('Settings'),
helpTexts: [
'Settings',
'Preferences set in this page are global and will affect the entire application.'
],
); );
} }
@ -169,7 +177,9 @@ class _SettingsPageState extends State<SettingsPage> {
return Center( return Center(
child: Column( child: Column(
children: [ children: [
Text('Our privacy policy is available under:'), Text('AnyWay does not collect or store any of the data that is submitted via the app. The location of your trip is not stored. The location feature is only used to show your current location on the map, it is not transmitted to our servers.', textAlign: TextAlign.center),
Padding(padding: EdgeInsets.only(top: 3)),
Text('Our full privacy policy is available under:', textAlign: TextAlign.center),
TextButton.icon( TextButton.icon(
icon: Icon(Icons.info), icon: Icon(Icons.info),

View File

@ -0,0 +1,27 @@
import 'package:anyway/pages/current_trip.dart';
import 'package:anyway/pages/onboarding.dart';
import 'package:anyway/structs/trip.dart';
import 'package:anyway/utils/load_trips.dart';
import 'package:flutter/material.dart';
Widget getFirstPage() {
Future<List<Trip>> trips = loadTrips();
// test if there are any active trips
// if there are, return the trip list
// if there are not, return the onboarding page
return FutureBuilder(
future: trips,
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Trip> availableTrips = snapshot.data!;
if (availableTrips.isNotEmpty) {
return TripPage(trip: availableTrips[0]);
} else {
return OnboardingPage();
}
} else {
return CircularProgressIndicator();
}
}
);
}

View File

@ -1,7 +1,4 @@
import 'dart:collection';
import 'package:anyway/structs/trip.dart'; import 'package:anyway/structs/trip.dart';
import 'package:anyway/structs/landmark.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
Future<List<Trip>> loadTrips() async { Future<List<Trip>> loadTrips() async {

View File

@ -1,30 +0,0 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
// import 'package:anyway/main.dart';
import 'package:anyway/layout.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(BasePage(mainScreen: "map",));
// Verfiy that the title is displayed
expect(find.text('City Nav'), findsOneWidget);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}