Frontend UX improvements #37

Merged
remoll merged 8 commits from feature/frontend/image-loading into main 2025-02-05 12:55:26 +00:00
8 changed files with 149 additions and 105 deletions
Showing only changes of commit d992b62533 - Show all commits

View File

@ -1,10 +1,12 @@
import 'package:anyway/utils/get_first_page.dart'; import 'package:anyway/utils/get_first_page.dart';
import 'package:anyway/utils/load_trips.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:anyway/constants.dart'; import 'package:anyway/constants.dart';
void main() => runApp(const App()); void main() => runApp(const App());
final GlobalKey<ScaffoldMessengerState> rootScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>(); final GlobalKey<ScaffoldMessengerState> rootScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
final SavedTrips savedTrips = SavedTrips();
class App extends StatelessWidget { class App extends StatelessWidget {
const App({super.key}); const App({super.key});

View File

@ -3,7 +3,6 @@ import 'package:anyway/main.dart';
import 'package:anyway/structs/trip.dart'; import 'package:anyway/structs/trip.dart';
import 'package:auto_size_text/auto_size_text.dart'; 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';
class saveButton extends StatefulWidget { class saveButton extends StatefulWidget {
@ -19,8 +18,9 @@ class _saveButtonState extends State<saveButton> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ElevatedButton( return ElevatedButton(
onPressed: () async { onPressed: () async {
SharedPreferences prefs = await SharedPreferences.getInstance(); savedTrips.addTrip(widget.trip);
setState(() => widget.trip.toPrefs(prefs)); // SharedPreferences prefs = await SharedPreferences.getInstance();
// setState(() => widget.trip.toPrefs(prefs));
rootScaffoldMessengerKey.currentState!.showSnackBar( rootScaffoldMessengerKey.currentState!.showSnackBar(
SnackBar( SnackBar(
content: Text('Trip saved'), content: Text('Trip saved'),

View File

@ -1,11 +1,12 @@
import 'package:anyway/pages/current_trip.dart'; import 'package:anyway/pages/current_trip.dart';
import 'package:anyway/utils/load_trips.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:anyway/structs/trip.dart'; import 'package:anyway/structs/trip.dart';
class TripsOverview extends StatefulWidget { class TripsOverview extends StatefulWidget {
final Future<List<Trip>> trips; final SavedTrips trips;
const TripsOverview({ const TripsOverview({
super.key, super.key,
required this.trips, required this.trips,
@ -16,49 +17,34 @@ 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, SavedTrips trips) {
List<Widget> children; List<Widget> children;
if (snapshot.hasData) { List<Trip> items = trips.trips;
children = List<Widget>.generate(snapshot.data!.length, (index) { children = List<Widget>.generate(items.length, (index) {
Trip trip = snapshot.data![index]; Trip trip = items[index];
return ListTile( return ListTile(
title: FutureBuilder( title: FutureBuilder(
future: trip.cityName, future: trip.cityName,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) { builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) { if (snapshot.hasData) {
return Text("Trip to ${snapshot.data}"); return Text("Trip to ${snapshot.data}");
} else if (snapshot.hasError) { } else if (snapshot.hasError) {
return Text("Error: ${snapshot.error}"); return Text("Error: ${snapshot.error}");
} else { } else {
return const Text("Trip to ..."); return const Text("Trip to ...");
} }
},
),
leading: Icon(Icons.pin_drop),
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => TripPage(trip: trip)
)
);
}, },
);
});
} else if (snapshot.hasError) {
children = [
const Icon(
Icons.error_outline,
color: Colors.red,
size: 60,
), ),
Padding( leading: Icon(Icons.pin_drop),
padding: const EdgeInsets.only(top: 16), onTap: () {
child: Text('Error: ${snapshot.error}'), Navigator.of(context).push(
), MaterialPageRoute(
]; builder: (context) => TripPage(trip: trip)
} else { )
children = [Center(child: CircularProgressIndicator())]; );
} },
);
});
return ListView( return ListView(
children: children, children: children,
@ -68,9 +54,11 @@ class _TripsOverviewState extends State<TripsOverview> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FutureBuilder( return ListenableBuilder(
future: widget.trips, listenable: widget.trips,
builder: listBuild, builder: (BuildContext context, Widget? child) {
return listBuild(context, widget.trips);
}
); );
} }
} }

View File

@ -1,3 +1,4 @@
import 'package:anyway/main.dart';
import 'package:anyway/modules/help_dialog.dart'; import 'package:anyway/modules/help_dialog.dart';
import 'package:anyway/pages/current_trip.dart'; import 'package:anyway/pages/current_trip.dart';
import 'package:anyway/pages/settings.dart'; import 'package:anyway/pages/settings.dart';
@ -38,7 +39,8 @@ class _BasePageState extends State<BasePage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Future<List<Trip>> trips = loadTrips(); savedTrips.loadTrips();
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
@ -98,11 +100,11 @@ class _BasePageState extends State<BasePage> {
// through the options in the drawer if there isn't enough vertical // through the options in the drawer if there isn't enough vertical
// space to fit everything. // space to fit everything.
Expanded( Expanded(
child: TripsOverview(trips: trips), child: TripsOverview(trips: savedTrips),
), ),
ElevatedButton( ElevatedButton(
onPressed: () async { onPressed: () async {
removeAllTripsFromPrefs(); savedTrips.clearTrips();
}, },
child: const Text('Clear trips'), child: const Text('Clear trips'),
), ),

View File

@ -1,3 +1,6 @@
import 'dart:ui';
import 'package:anyway/constants.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:flutter/material.dart'; import 'package:flutter/material.dart';
@ -33,32 +36,51 @@ class OnboardingPage extends StatefulWidget {
} }
class _OnboardingPageState extends State<OnboardingPage> { class _OnboardingPageState extends State<OnboardingPage> {
final PageController _controller = PageController();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final PageController _controller = PageController();
return Scaffold( return Scaffold(
body: Stack( body: Stack(
children: [ children: [
Container( AnimatedBuilder(
decoration: BoxDecoration( animation: _controller,
gradient: LinearGradient( builder: (context, child) {
colors: [Colors.red, Colors.blue], return Stack(
begin: Alignment.topLeft, children: [
end: Alignment.bottomRight, Container(
), decoration: BoxDecoration(
), gradient: LinearGradient(
child: PageView( begin: Alignment.topLeft,
controller: _controller, end: Alignment.bottomRight,
children: List.generate( colors: APP_GRADIENT.colors,
onboardingCards.length, stops: [
(index) { (_controller.hasClients ? _controller.page ?? _controller.initialPage : _controller.initialPage) / onboardingCards.length,
return Container( (_controller.hasClients ? _controller.page ?? _controller.initialPage + 1 : _controller.initialPage + 1) / onboardingCards.length,
alignment: Alignment.center, ],
child: onboardingCards[index], ),
); ),
} ),
), BackdropFilter(
filter: ImageFilter.blur(sigmaX: 100, sigmaY: 100),
child: Container(
color: Colors.black.withOpacity(0),
),
),
],
);
},
),
PageView(
controller: _controller,
children: List.generate(
onboardingCards.length,
(index) {
return Container(
alignment: Alignment.center,
child: onboardingCards[index],
);
}
), ),
), ),
], ],
@ -75,10 +97,10 @@ class _OnboardingPageState extends State<OnboardingPage> {
_controller.nextPage(duration: Duration(milliseconds: 500), curve: Curves.ease); _controller.nextPage(duration: Duration(milliseconds: 500), curve: Curves.ease);
} }
}, },
label: ListenableBuilder( label: AnimatedBuilder(
listenable: _controller, animation: _controller,
builder: (context, child) { builder: (context, child) {
if (_controller.page == onboardingCards.length - 1) { if ((_controller.page ?? _controller.initialPage) == onboardingCards.length - 1) {
return Row( return Row(
children: [ children: [
const Text("Start planning!"), const Text("Start planning!"),

View File

@ -113,10 +113,3 @@ LinkedList<Landmark> readLandmarks(SharedPreferences prefs, String? firstUUID) {
} }
return landmarks; return landmarks;
} }
void removeAllTripsFromPrefs () async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.clear();
}

View File

@ -5,23 +5,37 @@ import 'package:anyway/utils/load_trips.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
Widget getFirstPage() { Widget getFirstPage() {
Future<List<Trip>> trips = loadTrips(); SavedTrips trips = SavedTrips();
// test if there are any active trips trips.loadTrips();
// if there are, return the trip list
// if there are not, return the onboarding page return ListenableBuilder(
return FutureBuilder( listenable: trips,
future: trips, builder: (BuildContext context, Widget? child) {
builder: (context, snapshot) { List<Trip> items = trips.trips;
if (snapshot.hasData) { if (items.isNotEmpty) {
List<Trip> availableTrips = snapshot.data!; return TripPage(trip: items[0]);
if (availableTrips.isNotEmpty) {
return TripPage(trip: availableTrips[0]);
} else {
return OnboardingPage();
}
} else { } else {
return CircularProgressIndicator(); 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();
// }
// }
// );
} }

View File

@ -1,16 +1,39 @@
import 'package:anyway/structs/trip.dart'; import 'package:anyway/structs/trip.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
Future<List<Trip>> loadTrips() async { import 'package:flutter/foundation.dart';
SharedPreferences prefs = await SharedPreferences.getInstance();
List<Trip> trips = []; class SavedTrips extends ChangeNotifier {
Set<String> keys = prefs.getKeys(); List<Trip> _trips = [];
for (String key in keys) {
if (key.startsWith('trip_')) { List<Trip> get trips => _trips;
String uuid = key.replaceFirst('trip_', '');
trips.add(Trip.fromPrefs(prefs, uuid)); void loadTrips() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
List<Trip> trips = [];
Set<String> keys = prefs.getKeys();
for (String key in keys) {
if (key.startsWith('trip_')) {
String uuid = key.replaceFirst('trip_', '');
trips.add(Trip.fromPrefs(prefs, uuid));
}
} }
_trips = trips;
notifyListeners();
}
void addTrip(Trip trip) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
trip.toPrefs(prefs);
_trips.add(trip);
notifyListeners();
}
void clearTrips () async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.clear();
_trips = [];
notifyListeners();
} }
return trips;
} }