quite a few UX improvements
This commit is contained in:
parent
4ad867e609
commit
e148c851e1
@ -140,9 +140,10 @@ class _AnimatedDotsTextState extends State<AnimatedDotsText> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
String dots = '.' * dotCount;
|
String dots = '.' * dotCount;
|
||||||
return Text(
|
return AutoSizeText(
|
||||||
'${widget.baseText}$dots',
|
'${widget.baseText}$dots',
|
||||||
style: widget.style,
|
style: widget.style,
|
||||||
|
maxLines: 2,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,9 @@ class CurrentTripSummary extends StatefulWidget {
|
|||||||
|
|
||||||
class _CurrentTripSummaryState extends State<CurrentTripSummary> {
|
class _CurrentTripSummaryState extends State<CurrentTripSummary> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) => ListenableBuilder(
|
||||||
return Padding(
|
listenable: widget.trip,
|
||||||
|
builder: (context, child) => Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
|
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
@ -25,18 +26,18 @@ class _CurrentTripSummaryState extends State<CurrentTripSummary> {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Icons.flag, size: 20),
|
const Icon(Icons.flag, size: 20),
|
||||||
const Padding(padding: EdgeInsets.only(right: 10)),
|
const Padding(padding: EdgeInsets.only(right: 10)),
|
||||||
Text('Stops: ${widget.trip.landmarks.length}', style: Theme.of(context).textTheme.bodyLarge),
|
Text('${widget.trip.landmarks.length} stops', style: Theme.of(context).textTheme.bodyLarge),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.hourglass_bottom_rounded, size: 20),
|
const Icon(Icons.hourglass_bottom_rounded, size: 20),
|
||||||
const Padding(padding: EdgeInsets.only(right: 10)),
|
const Padding(padding: EdgeInsets.only(right: 10)),
|
||||||
Text('Duration: ${widget.trip.totalTime} minutes', style: Theme.of(context).textTheme.bodyLarge),
|
Text('${widget.trip.totalTime.inHours}h ${widget.trip.totalTime.inMinutes.remainder(60)}min', style: Theme.of(context).textTheme.bodyLarge),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
);
|
)
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
@ -78,6 +78,7 @@ class _NewTripMapState extends State<NewTripMap> {
|
|||||||
widget.trip.addListener(updateTripDetails);
|
widget.trip.addListener(updateTripDetails);
|
||||||
Future<SharedPreferences> preferences = SharedPreferences.getInstance();
|
Future<SharedPreferences> preferences = SharedPreferences.getInstance();
|
||||||
|
|
||||||
|
|
||||||
return FutureBuilder(
|
return FutureBuilder(
|
||||||
future: preferences,
|
future: preferences,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:anyway/structs/agreement.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
|
|
||||||
@ -26,7 +27,6 @@ class OnboardingAgreementCard extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _OnboardingAgreementCardState extends State<OnboardingAgreementCard> {
|
class _OnboardingAgreementCardState extends State<OnboardingAgreementCard> {
|
||||||
bool agreed = false;
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
@ -39,21 +39,42 @@ class _OnboardingAgreementCardState extends State<OnboardingAgreementCard> {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Checkbox(
|
// The checkbox of the agreement
|
||||||
value: agreed,
|
FutureBuilder(
|
||||||
onChanged: (value) {
|
future: getAgreement(),
|
||||||
setState(() {
|
builder: (context, snapshot) {
|
||||||
agreed = value!;
|
bool agreed = false;
|
||||||
widget.onAgreementChanged(value);
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
});
|
if (snapshot.hasData) {
|
||||||
|
Agreement agreement = snapshot.data!;
|
||||||
|
agreed = agreement.agreed;
|
||||||
|
} else {
|
||||||
|
agreed = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
agreed = false;
|
||||||
|
}
|
||||||
|
return Checkbox(
|
||||||
|
value: agreed,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
widget.onAgreementChanged(value!);
|
||||||
|
});
|
||||||
|
saveAgreement(value!);
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// The text of the agreement
|
||||||
Text(
|
Text(
|
||||||
"I agree to the ",
|
"I agree to the ",
|
||||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// The clickable text of the agreement that shows the agreement text
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// show a dialog with the agreement text
|
// show a dialog with the agreement text
|
||||||
|
@ -48,7 +48,7 @@ class _StepBetweenLandmarksState extends State<StepBetweenLandmarks> {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Icons.directions_walk),
|
const Icon(Icons.directions_walk),
|
||||||
Text(
|
Text(
|
||||||
time == null ? "" : "About $time min",
|
time == null ? "" : "$time min",
|
||||||
style: const TextStyle(fontSize: 10)
|
style: const TextStyle(fontSize: 10)
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:anyway/layouts/scaffold.dart';
|
import 'package:anyway/layouts/scaffold.dart';
|
||||||
import 'package:anyway/modules/new_trip_button.dart';
|
import 'package:anyway/modules/new_trip_button.dart';
|
||||||
|
import 'package:anyway/structs/landmark.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';
|
||||||
@ -20,6 +21,14 @@ class _NewTripPreferencesPageState extends State<NewTripPreferencesPage> with Sc
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
// Ensure that the trip is "empty" save for the start landmark
|
||||||
|
// This is necessary because users can swipe back to this page even after the trip has been created
|
||||||
|
if (widget.trip.landmarks.length > 1) {
|
||||||
|
Landmark start = widget.trip.landmarks.first;
|
||||||
|
widget.trip.landmarks.clear();
|
||||||
|
widget.trip.addLandmark(start);
|
||||||
|
}
|
||||||
|
|
||||||
return mainScaffold(
|
return mainScaffold(
|
||||||
context,
|
context,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
|
50
frontend/lib/pages/no_trips_page.dart
Normal file
50
frontend/lib/pages/no_trips_page.dart
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import 'package:anyway/pages/new_trip_location.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:anyway/layouts/scaffold.dart';
|
||||||
|
class NoTripsPage extends StatefulWidget {
|
||||||
|
const NoTripsPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<NoTripsPage> createState() => _NoTripsPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NoTripsPageState extends State<NoTripsPage> with ScaffoldLayout {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => mainScaffold(
|
||||||
|
context,
|
||||||
|
child: Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"No trips yet",
|
||||||
|
style: Theme.of(context).textTheme.headlineMedium,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"You can start a new trip by clicking the button below",
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const NewTripPage()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
label: const Row(
|
||||||
|
children: [
|
||||||
|
Text("Start planning!"),
|
||||||
|
Padding(padding: EdgeInsets.only(right: 8.0)),
|
||||||
|
Icon(Icons.map_outlined)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
@ -4,6 +4,7 @@ import 'package:anyway/constants.dart';
|
|||||||
import 'package:anyway/modules/onbarding_agreement_card.dart';
|
import 'package:anyway/modules/onbarding_agreement_card.dart';
|
||||||
import 'package:anyway/modules/onboarding_card.dart';
|
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:anyway/structs/agreement.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';
|
||||||
|
|
||||||
@ -121,11 +122,7 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// only allow the user to proceed if they have agreed to the terms and conditions
|
// only allow the user to proceed if they have agreed to the terms and conditions
|
||||||
Future<bool> hasAgreed = SharedPreferences.getInstance().then(
|
Future<bool> hasAgreed = getAgreement().then((agreement) => agreement.agreed);
|
||||||
(SharedPreferences prefs) {
|
|
||||||
return prefs.getBool('TC_agree') ?? false;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return FutureBuilder(
|
return FutureBuilder(
|
||||||
future: hasAgreed,
|
future: hasAgreed,
|
||||||
@ -157,8 +154,7 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
void onAgreementChanged(bool value) async {
|
void onAgreementChanged(bool value) async {
|
||||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
saveAgreement(value);
|
||||||
await prefs.setBool('TC_agree', value);
|
|
||||||
// Update the state of the OnboardingPage
|
// Update the state of the OnboardingPage
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
17
frontend/lib/structs/agreement.dart
Normal file
17
frontend/lib/structs/agreement.dart
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
final class Agreement{
|
||||||
|
bool agreed;
|
||||||
|
|
||||||
|
Agreement({required this.agreed});
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveAgreement(bool agreed) async {
|
||||||
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
prefs.setBool('agreed_to_terms_and_conditions', agreed);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Agreement> getAgreement() async {
|
||||||
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
return Agreement(agreed: prefs.getBool('agreed_to_terms_and_conditions') ?? false);
|
||||||
|
}
|
@ -12,7 +12,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||||||
|
|
||||||
class Trip with ChangeNotifier {
|
class Trip with ChangeNotifier {
|
||||||
String uuid;
|
String uuid;
|
||||||
int totalTime;
|
Duration totalTime;
|
||||||
LinkedList<Landmark> landmarks;
|
LinkedList<Landmark> landmarks;
|
||||||
// could be empty as well
|
// could be empty as well
|
||||||
String? errorDescription;
|
String? errorDescription;
|
||||||
@ -44,7 +44,7 @@ class Trip with ChangeNotifier {
|
|||||||
|
|
||||||
Trip({
|
Trip({
|
||||||
this.uuid = 'pending',
|
this.uuid = 'pending',
|
||||||
this.totalTime = 0,
|
this.totalTime = Duration.zero,
|
||||||
LinkedList<Landmark>? landmarks
|
LinkedList<Landmark>? landmarks
|
||||||
// a trip can be created with no landmarks, but the list should be initialized anyway
|
// a trip can be created with no landmarks, but the list should be initialized anyway
|
||||||
}) : landmarks = landmarks ?? LinkedList<Landmark>();
|
}) : landmarks = landmarks ?? LinkedList<Landmark>();
|
||||||
@ -53,7 +53,7 @@ class Trip with ChangeNotifier {
|
|||||||
factory Trip.fromJson(Map<String, dynamic> json) {
|
factory Trip.fromJson(Map<String, dynamic> json) {
|
||||||
Trip trip = Trip(
|
Trip trip = Trip(
|
||||||
uuid: json['uuid'],
|
uuid: json['uuid'],
|
||||||
totalTime: json['total_time'],
|
totalTime: Duration(minutes: json['total_time']),
|
||||||
);
|
);
|
||||||
|
|
||||||
return trip;
|
return trip;
|
||||||
@ -61,7 +61,7 @@ class Trip with ChangeNotifier {
|
|||||||
|
|
||||||
void loadFromJson(Map<String, dynamic> json) {
|
void loadFromJson(Map<String, dynamic> json) {
|
||||||
uuid = json['uuid'];
|
uuid = json['uuid'];
|
||||||
totalTime = json['total_time'];
|
totalTime = Duration(minutes: json['total_time']);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,9 +82,12 @@ class Trip with ChangeNotifier {
|
|||||||
// removing the landmark means we need to recompute the time between the two adjoined landmarks
|
// removing the landmark means we need to recompute the time between the two adjoined landmarks
|
||||||
if (previous != null && next != null) {
|
if (previous != null && next != null) {
|
||||||
// previous.next = next happens automatically since we are using a LinkedList
|
// previous.next = next happens automatically since we are using a LinkedList
|
||||||
|
this.totalTime -= previous.tripTime ?? Duration.zero;
|
||||||
previous.tripTime = null;
|
previous.tripTime = null;
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
this.totalTime -= landmark.tripTime ?? Duration.zero;
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,7 +114,7 @@ class Trip with ChangeNotifier {
|
|||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
'uuid': uuid,
|
'uuid': uuid,
|
||||||
'total_time': totalTime,
|
'total_time': totalTime.inMinutes,
|
||||||
'first_landmark_uuid': landmarks.first.uuid
|
'first_landmark_uuid': landmarks.first.uuid
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,44 +1,50 @@
|
|||||||
import 'package:anyway/main.dart';
|
import 'package:anyway/main.dart';
|
||||||
|
import 'package:anyway/pages/no_trips_page.dart';
|
||||||
|
import 'package:anyway/structs/agreement.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:anyway/structs/trip.dart';
|
import 'package:anyway/structs/trip.dart';
|
||||||
import 'package:anyway/utils/load_trips.dart';
|
|
||||||
import 'package:anyway/pages/current_trip.dart';
|
import 'package:anyway/pages/current_trip.dart';
|
||||||
import 'package:anyway/pages/onboarding.dart';
|
import 'package:anyway/pages/onboarding.dart';
|
||||||
|
|
||||||
|
|
||||||
Widget getFirstPage() {
|
Widget getFirstPage() {
|
||||||
SavedTrips trips = savedTrips;
|
// check if the user has already seen the onboarding and agreed to the terms of service
|
||||||
trips.loadTrips();
|
return FutureBuilder(
|
||||||
|
future: getAgreement(),
|
||||||
return ListenableBuilder(
|
builder: (context, snapshot) {
|
||||||
listenable: trips,
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
builder: (BuildContext context, Widget? child) {
|
if (snapshot.hasData) {
|
||||||
List<Trip> items = trips.trips;
|
Agreement agrement = snapshot.data!;
|
||||||
if (items.isNotEmpty) {
|
if (agrement.agreed) {
|
||||||
return TripPage(trip: items[0]);
|
return FutureBuilder(
|
||||||
|
future: savedTrips.loadTrips(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
List<Trip> trips = snapshot.data!;
|
||||||
|
if (trips.length > 0) {
|
||||||
|
return TripPage(trip: trips[0]);
|
||||||
|
} else {
|
||||||
|
return NoTripsPage();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return OnboardingPage();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return OnboardingPage();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return const OnboardingPage();
|
return OnboardingPage();
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
// 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();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
}
|
}
|
@ -8,7 +8,7 @@ class SavedTrips extends ChangeNotifier {
|
|||||||
|
|
||||||
List<Trip> get trips => _trips;
|
List<Trip> get trips => _trips;
|
||||||
|
|
||||||
void loadTrips() async {
|
Future<List<Trip>> loadTrips() async {
|
||||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
List<Trip> trips = [];
|
List<Trip> trips = [];
|
||||||
@ -21,6 +21,7 @@ class SavedTrips extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
_trips = trips;
|
_trips = trips;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
return trips;
|
||||||
}
|
}
|
||||||
|
|
||||||
void addTrip(Trip trip) async {
|
void addTrip(Trip trip) async {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user