chore(wip): upgrade dependencies, begin refactor

This commit is contained in:
2025-12-23 21:49:54 +01:00
parent 0070e57aec
commit 239b63ca81
82 changed files with 4028 additions and 195 deletions

View File

@@ -0,0 +1,71 @@
import 'package:anyway/constants.dart';
import 'package:anyway/layouts/scaffold.dart';
import 'package:flutter/material.dart';
import 'package:sliding_up_panel/sliding_up_panel.dart';
import 'package:anyway/structs/trip.dart';
import 'package:anyway/modules/current_trip_overview.dart';
import 'package:anyway/modules/current_trip_panel.dart';
final Shader textGradient = APP_GRADIENT.createShader(const Rect.fromLTWH(0.0, 0.0, 200.0, 70.0));
TextStyle greeterStyle = TextStyle(
foreground: Paint()..shader = textGradient,
fontWeight: FontWeight.bold,
fontSize: 25
);
class TripPage extends StatefulWidget {
final Trip trip;
const TripPage({super.key,
required this.trip,
});
@override
State<TripPage> createState() => _TripPageState();
}
class _TripPageState extends State<TripPage> with ScaffoldLayout{
@override
Widget build(BuildContext context) {
return mainScaffold(
context,
child: SlidingUpPanel(
// use panelBuilder instead of panel so that we can reuse the scrollcontroller for the listview
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
// collapsed: Greeter(trip: widget.trip),
body: CurrentTripOverview(trip: widget.trip),
minHeight: MediaQuery.of(context).size.height * TRIP_PANEL_MIN_HEIGHT,
maxHeight: MediaQuery.of(context).size.height * TRIP_PANEL_MAX_HEIGHT,
// padding in this context is annoying: it offsets the notion of vertical alignment.
// children that want to be centered vertically need to have their size adjusted by 2x the padding
// padding: const EdgeInsets.all(10.0),
// Panel snapping should not be disabled because it significantly improves the user experience
// panelSnapping: false
borderRadius: const BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)),
parallaxEnabled: true,
boxShadow: const [
BoxShadow(
blurRadius: 20.0,
color: Colors.black,
)
],
),
title: FutureBuilder(
future: widget.trip.cityName,
builder: (context, snapshot) => Text(
'Trip to ${snapshot.hasData ? snapshot.data! : "..."}',
)
),
helpTexts: [
'Current trip',
'You can see and edit your current trip here. Swipe up from the bottom to see a detailed view of the recommendations.'
],
);
}
}

View File

@@ -0,0 +1,47 @@
import 'package:anyway/layouts/scaffold.dart';
import 'package:anyway/modules/new_trip_options_button.dart';
import 'package:flutter/material.dart';
import "package:anyway/structs/trip.dart";
import 'package:anyway/modules/new_trip_location_search.dart';
import 'package:anyway/modules/new_trip_map.dart';
class NewTripPage extends StatefulWidget {
const NewTripPage({super.key});
@override
_NewTripPageState createState() => _NewTripPageState();
}
class _NewTripPageState extends State<NewTripPage> with ScaffoldLayout {
final TextEditingController latController = TextEditingController();
final TextEditingController lonController = TextEditingController();
Trip trip = Trip();
@override
Widget build(BuildContext context) {
// floating search bar and map as a background
return mainScaffold(
context,
child: Scaffold(
body: Stack(
children: [
NewTripMap(trip),
Padding(
padding: const EdgeInsets.all(15),
child: NewTripLocationSearch(trip),
),
],
),
floatingActionButton: NewTripOptionsButton(trip: trip),
),
title: const 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

@@ -0,0 +1,124 @@
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';
import 'package:flutter/material.dart';
class NewTripPreferencesPage extends StatefulWidget {
final Trip trip;
const NewTripPreferencesPage({super.key, required this.trip});
@override
_NewTripPreferencesPageState createState() => _NewTripPreferencesPageState();
}
class _NewTripPreferencesPageState extends State<NewTripPreferencesPage> with ScaffoldLayout {
UserPreferences preferences = UserPreferences();
@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(
body: ListView(
children: [
const Center(
child: Padding(
padding: EdgeInsets.only(left: 10, right: 10, top: 20, bottom: 0),
child: Text('Tell us about your ideal trip.', style: TextStyle(fontSize: 18))
),
),
const Divider(indent: 25, endIndent: 25, height: 50),
durationPicker(preferences.maxTime),
preferenceSliders([preferences.sightseeing, preferences.shopping, preferences.nature]),
// Add a conditional padding to avoid the floating button covering the last slider
Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom + 80),
),
]
),
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.'
],
);
}
Widget durationPicker(SinglePreference maxTime) {
return Card(
margin: const EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 0),
shadowColor: Colors.grey,
child: ListTile(
leading: preferences.maxTime.icon,
title: Text(preferences.maxTime.description),
subtitle: CupertinoTimerPicker(
mode: CupertinoTimerPickerMode.hm,
initialTimerDuration: const Duration(minutes: 90),
minuteInterval: 15,
onTimerDurationChanged: (Duration newDuration) {
setState(() {
preferences.maxTime.value = newDuration.inMinutes;
});
},
)
),
);
}
Widget preferenceSliders(List<SinglePreference> prefs) {
List<Card> sliders = [];
for (SinglePreference pref in prefs) {
sliders.add(
Card(
child: ListTile(
leading: pref.icon,
title: Text(pref.name),
subtitle: Slider(
value: pref.value.toDouble(),
min: pref.minVal.toDouble(),
max: pref.maxVal.toDouble(),
divisions: pref.maxVal - pref.minVal,
label: pref.value.toString(),
onChanged: (double newValue) {
setState(() {
pref.value = newValue.toInt();
});
},
)
),
)
);
}
return Column(
children: sliders
);
}
}

View File

@@ -0,0 +1,53 @@
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,
textAlign: TextAlign.center,
),
const Padding(padding: EdgeInsets.only(bottom: 10)),
Text(
"You can start a new trip by clicking the button below",
style: Theme.of(context).textTheme.bodyMedium,
textAlign: TextAlign.center,
),
],
),
),
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)
],
)
)
)
);
}

View File

@@ -0,0 +1,198 @@
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:anyway/main.dart';
import 'package:anyway/constants.dart';
import 'package:anyway/layouts/scaffold.dart';
bool debugMode = false;
class SettingsPage extends StatefulWidget {
const SettingsPage({super.key});
@override
_SettingsPageState createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> with ScaffoldLayout {
@override
Widget build (BuildContext context) => mainScaffold(
context,
child: ListView(
padding: const EdgeInsets.all(15),
children: [
// First a round, centered image
const Center(
child: CircleAvatar(
radius: 75,
child: Icon(Icons.settings, size: 100),
)
),
const Center(
child: Text('Global settings', style: TextStyle(fontSize: 24))
),
const Divider(indent: 25, endIndent: 25, height: 50),
darkMode(),
setLocationUsage(),
setDebugMode(),
const Divider(indent: 25, endIndent: 25, height: 50),
privacyInfo(),
]
),
title: const Text('Settings'),
helpTexts: [
'Settings',
'Preferences set in this page are global and will affect the entire application.'
],
);
Widget setDebugMode() {
return Row(
children: [
const Text('Debugging: use a custom API URL'),
// white space
const Spacer(),
Switch(
value: debugMode,
onChanged: (bool? newValue) {
setState(() {
debugMode = newValue!;
if (debugMode) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Debug mode - use a custom API endpoint'),
content: TextField(
controller: TextEditingController(text: API_URL_DEBUG),
onChanged: (value) {
setState(() {
API_URL_BASE = value;
});
},
),
actions: [
TextButton(
child: const Text('OK'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
}
);
}
});
}
)
],
);
}
Widget darkMode() {
return Row(
children: [
const Text('Dark mode'),
const Spacer(),
Switch(
value: Theme.of(context).brightness == Brightness.dark,
onChanged: (bool? newValue) {
setState(() {
rootScaffoldMessengerKey.currentState!.showSnackBar(
const SnackBar(content: Text('Dark mode is not implemented yet'))
);
// if (newValue!) {
// // Dark mode
// Theme.of(context).brightness = Brightness.dark;
// } else {
// // Light mode
// Theme.of(context).brightness = Brightness.light;
// }
});
}
)
],
);
}
Widget setLocationUsage() {
Future<SharedPreferences> preferences = SharedPreferences.getInstance();
return Row(
children: [
const Text('Use location services'),
// white space
const Spacer(),
FutureBuilder(
future: preferences,
builder: (context, snapshot) {
if (snapshot.hasData) {
bool useLocation = snapshot.data!.getBool('useLocation') ?? false;
return Switch(
value: useLocation,
onChanged: setUseLocation,
);
} else {
return const CircularProgressIndicator();
}
}
)
],
);
}
void setUseLocation(bool newValue) async {
await Permission.locationWhenInUse
.onDeniedCallback(() {
rootScaffoldMessengerKey.currentState!.showSnackBar(
const SnackBar(content: Text('Location services are required for this feature'))
);
})
.onGrantedCallback(() {
rootScaffoldMessengerKey.currentState!.showSnackBar(
const SnackBar(content: Text('Location services are now enabled'))
);
SharedPreferences.getInstance().then(
(SharedPreferences prefs) {
setState(() {
prefs.setBool('useLocation', newValue);
});
}
);
})
.onPermanentlyDeniedCallback(() {
rootScaffoldMessengerKey.currentState!.showSnackBar(
const SnackBar(content: Text('Location services are required for this feature'))
);
})
.request();
}
Widget privacyInfo() {
return Center(
child: Column(
children: [
const 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.', textAlign: TextAlign.center),
const Padding(padding: EdgeInsets.only(top: 3)),
const Text('Our full privacy policy is available under:', textAlign: TextAlign.center),
TextButton.icon(
icon: const Icon(Icons.info),
label: Text(PRIVACY_URL),
onPressed: () async{
await launchUrl(Uri.parse(PRIVACY_URL));
}
)
],
)
);
}
}