quite a few UX improvements
This commit is contained in:
parent
4ad867e609
commit
e148c851e1
@ -140,9 +140,10 @@ class _AnimatedDotsTextState extends State<AnimatedDotsText> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String dots = '.' * dotCount;
|
||||
return Text(
|
||||
return AutoSizeText(
|
||||
'${widget.baseText}$dots',
|
||||
style: widget.style,
|
||||
maxLines: 2,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,9 @@ class CurrentTripSummary extends StatefulWidget {
|
||||
|
||||
class _CurrentTripSummaryState extends State<CurrentTripSummary> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
Widget build(BuildContext context) => ListenableBuilder(
|
||||
listenable: widget.trip,
|
||||
builder: (context, child) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
@ -25,18 +26,18 @@ class _CurrentTripSummaryState extends State<CurrentTripSummary> {
|
||||
children: [
|
||||
const Icon(Icons.flag, size: 20),
|
||||
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(
|
||||
children: [
|
||||
const Icon(Icons.hourglass_bottom_rounded, size: 20),
|
||||
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);
|
||||
Future<SharedPreferences> preferences = SharedPreferences.getInstance();
|
||||
|
||||
|
||||
return FutureBuilder(
|
||||
future: preferences,
|
||||
builder: (context, snapshot) {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:anyway/structs/agreement.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
|
||||
@ -26,7 +27,6 @@ class OnboardingAgreementCard extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _OnboardingAgreementCardState extends State<OnboardingAgreementCard> {
|
||||
bool agreed = false;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
@ -39,21 +39,42 @@ class _OnboardingAgreementCardState extends State<OnboardingAgreementCard> {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Checkbox(
|
||||
value: agreed,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
agreed = value!;
|
||||
widget.onAgreementChanged(value);
|
||||
});
|
||||
// The checkbox of the agreement
|
||||
FutureBuilder(
|
||||
future: getAgreement(),
|
||||
builder: (context, snapshot) {
|
||||
bool agreed = false;
|
||||
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(
|
||||
"I agree to the ",
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
|
||||
// The clickable text of the agreement that shows the agreement text
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
// show a dialog with the agreement text
|
||||
|
@ -48,7 +48,7 @@ class _StepBetweenLandmarksState extends State<StepBetweenLandmarks> {
|
||||
children: [
|
||||
const Icon(Icons.directions_walk),
|
||||
Text(
|
||||
time == null ? "" : "About $time min",
|
||||
time == null ? "" : "$time min",
|
||||
style: const TextStyle(fontSize: 10)
|
||||
),
|
||||
],
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:anyway/layouts/scaffold.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/trip.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
@ -20,6 +21,14 @@ class _NewTripPreferencesPageState extends State<NewTripPreferencesPage> with Sc
|
||||
|
||||
@override
|
||||
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(
|
||||
context,
|
||||
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/onboarding_card.dart';
|
||||
import 'package:anyway/pages/new_trip_location.dart';
|
||||
import 'package:anyway/structs/agreement.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
@ -121,11 +122,7 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||
);
|
||||
} else {
|
||||
// only allow the user to proceed if they have agreed to the terms and conditions
|
||||
Future<bool> hasAgreed = SharedPreferences.getInstance().then(
|
||||
(SharedPreferences prefs) {
|
||||
return prefs.getBool('TC_agree') ?? false;
|
||||
}
|
||||
);
|
||||
Future<bool> hasAgreed = getAgreement().then((agreement) => agreement.agreed);
|
||||
|
||||
return FutureBuilder(
|
||||
future: hasAgreed,
|
||||
@ -157,8 +154,7 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||
);
|
||||
|
||||
void onAgreementChanged(bool value) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool('TC_agree', value);
|
||||
saveAgreement(value);
|
||||
// Update the state of the OnboardingPage
|
||||
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 {
|
||||
String uuid;
|
||||
int totalTime;
|
||||
Duration totalTime;
|
||||
LinkedList<Landmark> landmarks;
|
||||
// could be empty as well
|
||||
String? errorDescription;
|
||||
@ -44,7 +44,7 @@ class Trip with ChangeNotifier {
|
||||
|
||||
Trip({
|
||||
this.uuid = 'pending',
|
||||
this.totalTime = 0,
|
||||
this.totalTime = Duration.zero,
|
||||
LinkedList<Landmark>? landmarks
|
||||
// a trip can be created with no landmarks, but the list should be initialized anyway
|
||||
}) : landmarks = landmarks ?? LinkedList<Landmark>();
|
||||
@ -53,7 +53,7 @@ class Trip with ChangeNotifier {
|
||||
factory Trip.fromJson(Map<String, dynamic> json) {
|
||||
Trip trip = Trip(
|
||||
uuid: json['uuid'],
|
||||
totalTime: json['total_time'],
|
||||
totalTime: Duration(minutes: json['total_time']),
|
||||
);
|
||||
|
||||
return trip;
|
||||
@ -61,7 +61,7 @@ class Trip with ChangeNotifier {
|
||||
|
||||
void loadFromJson(Map<String, dynamic> json) {
|
||||
uuid = json['uuid'];
|
||||
totalTime = json['total_time'];
|
||||
totalTime = Duration(minutes: json['total_time']);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@ -82,9 +82,12 @@ class Trip with ChangeNotifier {
|
||||
// removing the landmark means we need to recompute the time between the two adjoined landmarks
|
||||
if (previous != null && next != null) {
|
||||
// previous.next = next happens automatically since we are using a LinkedList
|
||||
this.totalTime -= previous.tripTime ?? Duration.zero;
|
||||
previous.tripTime = null;
|
||||
// TODO
|
||||
}
|
||||
this.totalTime -= landmark.tripTime ?? Duration.zero;
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@ -111,7 +114,7 @@ class Trip with ChangeNotifier {
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'uuid': uuid,
|
||||
'total_time': totalTime,
|
||||
'total_time': totalTime.inMinutes,
|
||||
'first_landmark_uuid': landmarks.first.uuid
|
||||
};
|
||||
|
||||
|
@ -1,44 +1,50 @@
|
||||
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:anyway/structs/trip.dart';
|
||||
import 'package:anyway/utils/load_trips.dart';
|
||||
import 'package:anyway/pages/current_trip.dart';
|
||||
import 'package:anyway/pages/onboarding.dart';
|
||||
|
||||
|
||||
Widget getFirstPage() {
|
||||
SavedTrips trips = savedTrips;
|
||||
trips.loadTrips();
|
||||
|
||||
return ListenableBuilder(
|
||||
listenable: trips,
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
List<Trip> items = trips.trips;
|
||||
if (items.isNotEmpty) {
|
||||
return TripPage(trip: items[0]);
|
||||
// check if the user has already seen the onboarding and agreed to the terms of service
|
||||
return FutureBuilder(
|
||||
future: getAgreement(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.hasData) {
|
||||
Agreement agrement = snapshot.data!;
|
||||
if (agrement.agreed) {
|
||||
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 {
|
||||
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;
|
||||
|
||||
void loadTrips() async {
|
||||
Future<List<Trip>> loadTrips() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
|
||||
List<Trip> trips = [];
|
||||
@ -21,6 +21,7 @@ class SavedTrips extends ChangeNotifier {
|
||||
}
|
||||
_trips = trips;
|
||||
notifyListeners();
|
||||
return trips;
|
||||
}
|
||||
|
||||
void addTrip(Trip trip) async {
|
||||
|
Loading…
x
Reference in New Issue
Block a user