From 2be33e646cc426c3d093142df3da24a936984d1b Mon Sep 17 00:00:00 2001 From: florianpix Date: Thu, 29 Jun 2023 17:54:12 +0200 Subject: [PATCH 01/13] addtrip callback; addspot callback; only allow adding pitches on map page and list page; changed order of addspot and navigationpage; order spotTimeline alphabetically; --- .../lib/components/add/add_spot.dart | 117 +++--- .../lib/components/add/add_trip.dart | 6 +- .../detail/single_pitch_route_details.dart | 15 - .../lib/components/detail/spot_details.dart | 15 - .../lib/components/detail/trip_details.dart | 27 +- .../components/diary_page/spot_details.dart | 338 ++++++++++++++++++ .../diary_page/timeline/spot_timeline.dart | 5 +- .../diary_page/timeline/trip_timeline.dart | 18 +- .../lib/components/info/route_info.dart | 11 +- .../list_page}/list_page.dart | 85 +++-- .../lib/components/list_page/spot_list.dart | 105 +++++- .../lib/components/map_page/add_spot.dart | 117 +++--- .../map_page/details/spot_details.dart | 8 +- .../lib/components/map_page/map_page.dart | 59 ++- .../map_page/navigation_screen_page.dart | 90 ++--- .../lib/components/map_page/route_list.dart | 270 ++++++++++++++ .../map_page/save_location_no_connection.dart | 3 - src/flutter_app/lib/main.dart | 2 +- .../lib/pages/navigation_screen_page.dart | 15 +- .../pages/save_location_no_connection.dart | 3 - .../lib/services/spot_service.dart | 48 +++ 21 files changed, 1051 insertions(+), 306 deletions(-) create mode 100644 src/flutter_app/lib/components/diary_page/spot_details.dart rename src/flutter_app/lib/{pages => components/list_page}/list_page.dart (50%) create mode 100644 src/flutter_app/lib/components/map_page/route_list.dart diff --git a/src/flutter_app/lib/components/add/add_spot.dart b/src/flutter_app/lib/components/add/add_spot.dart index 0e84488..b4d5d42 100644 --- a/src/flutter_app/lib/components/add/add_spot.dart +++ b/src/flutter_app/lib/components/add/add_spot.dart @@ -1,3 +1,5 @@ +import 'package:climbing_diary/components/MyTextStyles.dart'; +import 'package:climbing_diary/components/map_page/navigation_screen_page.dart'; import 'package:climbing_diary/interfaces/trip/update_trip.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -12,12 +14,10 @@ import 'package:internet_connection_checker/internet_connection_checker.dart'; import '../../services/trip_service.dart'; class AddSpot extends StatefulWidget { - const AddSpot({super.key, required this.trips, required this.coordinates, required this.address, required this.onAdd}); + const AddSpot({super.key, this.trip, required this.onAdd}); - final LatLng coordinates; - final String address; final ValueSetter onAdd; - final List trips; + final Trip? trip; @override State createState() => _AddSpotState(); @@ -37,7 +37,6 @@ class _AddSpotState extends State{ final TextEditingController controllerCar = TextEditingController(); double currentSliderValue = 0; - Trip? dropdownValue; @override void initState(){ @@ -47,12 +46,17 @@ class _AddSpotState extends State{ @override Widget build(BuildContext context) { - controllerAddress.text = widget.address; - controllerLat.text = widget.coordinates.latitude.toString(); - controllerLong.text = widget.coordinates.longitude.toString(); + // controllerLat.text = widget.coordinates.latitude.toString(); + // controllerLong.text = widget.coordinates.longitude.toString(); List elements = []; + if (widget.trip != null){ + elements.add(Text( + widget.trip!.name, + style: MyTextStyles.title, + )); + } elements.add( TextFormField( validator: (value) { @@ -65,20 +69,24 @@ class _AddSpotState extends State{ hintText: "name", labelText: "name"), ) ); - elements.add(DropdownButton( - isExpanded: true, - value: dropdownValue, - items: widget.trips.map>((Trip trip) { - return DropdownMenuItem( - value: trip, - child: Text(trip.name), - ); - }).toList(), - onChanged: (Trip? trip) { - setState(() { - dropdownValue = trip!; - }); - } + elements.add(TextButton.icon( + icon: const Icon(Icons.place, size: 30.0), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => NavigationScreenPage( + onAdd: (List results) { + controllerAddress.text = results[0]; + controllerLat.text = results[1]; + controllerLong.text = results[2]; + setState(() {}); + } + ), + ) + ); + }, + label: const Text("select location"), )); elements.add(TextFormField( controller: controllerAddress, @@ -132,6 +140,7 @@ class _AddSpotState extends State{ } return null; }, + keyboardType: TextInputType.number, controller: controllerBus, decoration: const InputDecoration( hintText: "in minutes", @@ -149,6 +158,7 @@ class _AddSpotState extends State{ return null; }, controller: controllerCar, + keyboardType: TextInputType.number, decoration: const InputDecoration( hintText: "in minutes", labelText: "Distance to parking" @@ -161,43 +171,44 @@ class _AddSpotState extends State{ ), title: const Text('Add a new spot'), content: SingleChildScrollView( - child: Form( - key: _formKey, - child: Column( - mainAxisSize: MainAxisSize.min, - children: elements, - ), + child: Form( + key: _formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: elements, ), + ), ), actions: [ TextButton( - onPressed: () async { - bool result = await InternetConnectionChecker().hasConnection; - if (_formKey.currentState!.validate()) { - var valDistanceParking = int.tryParse(controllerCar.text); - var valDistancePublicTransport = int.tryParse(controllerBus.text); - CreateSpot spot = CreateSpot( - comment: controllerComment.text, - coordinates: [double.parse(controllerLat.text), double.parse(controllerLong.text)], - distanceParking: (valDistanceParking != null) ? valDistanceParking : 0, - distancePublicTransport: (valDistancePublicTransport != null) ? valDistancePublicTransport : 0, - location: controllerAddress.text, - name: controllerTitle.text, - rating: currentSliderValue.toInt(), - ); - Navigator.popUntil(context, ModalRoute.withName('/')); - Spot? createdSpot = await spotService.createSpot(spot, result); - if (createdSpot != null) { - if (dropdownValue != null) { - UpdateTrip editTrip = dropdownValue!.toUpdateTrip(); - editTrip.spotIds?.add(createdSpot.id); - Trip? editedTrip = await tripService.editTrip(editTrip); + onPressed: () async { + bool result = await InternetConnectionChecker().hasConnection; + if (_formKey.currentState!.validate()) { + var valDistanceParking = int.tryParse(controllerCar.text); + var valDistancePublicTransport = int.tryParse(controllerBus.text); + CreateSpot spot = CreateSpot( + comment: controllerComment.text, + coordinates: [double.parse(controllerLat.text), double.parse(controllerLong.text)], + distanceParking: (valDistanceParking != null) ? valDistanceParking : 0, + distancePublicTransport: (valDistancePublicTransport != null) ? valDistancePublicTransport : 0, + location: controllerAddress.text, + name: controllerTitle.text, + rating: currentSliderValue.toInt(), + ); + Navigator.popUntil(context, ModalRoute.withName('/')); + Spot? createdSpot = await spotService.createSpot(spot, result); + if (createdSpot != null) { + if (widget.trip != null) { + Trip trip = widget.trip!; + UpdateTrip editTrip = trip.toUpdateTrip(); + editTrip.spotIds?.add(createdSpot.id); + Trip? editedTrip = await tripService.editTrip(editTrip); + } + widget.onAdd.call(createdSpot); } - widget.onAdd.call(createdSpot); } - } - }, - child: const Icon(Icons.save)) + }, + child: const Icon(Icons.save)) ], ); } diff --git a/src/flutter_app/lib/components/add/add_trip.dart b/src/flutter_app/lib/components/add/add_trip.dart index 8ac379a..d8e7282 100644 --- a/src/flutter_app/lib/components/add/add_trip.dart +++ b/src/flutter_app/lib/components/add/add_trip.dart @@ -7,7 +7,8 @@ import '../../services/trip_service.dart'; import 'package:internet_connection_checker/internet_connection_checker.dart'; class AddTrip extends StatefulWidget { - const AddTrip({super.key}); + const AddTrip({super.key, required this.onAdd}); + final ValueSetter onAdd; @override State createState() => _AddTripState(); @@ -127,6 +128,9 @@ class _AddTripState extends State{ ); Navigator.popUntil(context, ModalRoute.withName('/')); Trip? createdTrip = await tripService.createTrip(trip, result); + if (createdTrip != null) { + widget.onAdd.call(createdTrip); + } } }, child: const Text("Save")) diff --git a/src/flutter_app/lib/components/detail/single_pitch_route_details.dart b/src/flutter_app/lib/components/detail/single_pitch_route_details.dart index f160550..e599260 100644 --- a/src/flutter_app/lib/components/detail/single_pitch_route_details.dart +++ b/src/flutter_app/lib/components/detail/single_pitch_route_details.dart @@ -285,21 +285,6 @@ class _SinglePitchRouteDetailsState extends State{ style: MyButtonStyles.rounded ), ); - elements.add( - ElevatedButton.icon( - icon: const Icon(Icons.add, size: 30.0, color: Colors.pink), - label: const Text('Add existing ascent'), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => SelectAscentOfSinglePitchRoute(singlePitchRoute: widget.route), - ) - ); - }, - style: MyButtonStyles.rounded - ), - ); elements.add( Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/src/flutter_app/lib/components/detail/spot_details.dart b/src/flutter_app/lib/components/detail/spot_details.dart index 8de43e7..4a43f99 100644 --- a/src/flutter_app/lib/components/detail/spot_details.dart +++ b/src/flutter_app/lib/components/detail/spot_details.dart @@ -308,21 +308,6 @@ class _SpotDetailsState extends State{ style: MyButtonStyles.rounded ), ); - elements.add( - ElevatedButton.icon( - icon: const Icon(Icons.add, size: 30.0, color: Colors.pink), - label: const Text('Add existing route'), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => SelectRoute(spot: widget.spot), - ) - ); - }, - style: MyButtonStyles.rounded - ), - ); // delete, edit, close elements.add( Row( diff --git a/src/flutter_app/lib/components/detail/trip_details.dart b/src/flutter_app/lib/components/detail/trip_details.dart index 6953e04..4c9d243 100644 --- a/src/flutter_app/lib/components/detail/trip_details.dart +++ b/src/flutter_app/lib/components/detail/trip_details.dart @@ -4,19 +4,21 @@ import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:skeletons/skeletons.dart'; +import '../../interfaces/spot/spot.dart'; import '../../interfaces/trip/trip.dart'; import '../../pages/navigation_screen_page.dart'; import '../../services/media_service.dart'; import '../../services/trip_service.dart'; +import '../add/add_spot.dart'; import '../diary_page/timeline/spot_timeline.dart'; import '../edit/edit_trip.dart'; class TripDetails extends StatefulWidget { - const TripDetails({super.key, required this.trip, required this.onDelete, required this.onUpdate }); + const TripDetails({super.key, required this.trip, required this.onTripDelete, required this.onTripUpdate, required this.onSpotAdd }); final Trip trip; - final ValueSetter onDelete; - final ValueSetter onUpdate; + final ValueSetter onTripDelete, onTripUpdate; + final ValueSetter onSpotAdd; @override State createState() => _TripDetailsState(); @@ -100,7 +102,7 @@ class _TripDetailsState extends State{ showDialog( context: context, builder: (BuildContext context) { - return EditTrip(trip: widget.trip, onUpdate: widget.onUpdate); + return EditTrip(trip: widget.trip, onUpdate: widget.onTripUpdate); }); } @@ -273,10 +275,12 @@ class _TripDetailsState extends State{ Navigator.push( context, MaterialPageRoute( - builder: (context) => NavigationScreenPage( + builder: (context) => AddSpot( onAdd: (spot) { - trip.spotIds.add(spot.id); + widget.onSpotAdd.call(spot); + setState(() {}); }, + trip: trip, ), ) ); @@ -309,7 +313,7 @@ class _TripDetailsState extends State{ onPressed: () { Navigator.pop(context); tripService.deleteTrip(trip); - widget.onDelete.call(trip); + widget.onTripDelete.call(trip); }, icon: const Icon(Icons.delete), ), @@ -326,9 +330,12 @@ class _TripDetailsState extends State{ ); // spots if (trip.spotIds.isNotEmpty){ - elements.add( - SpotTimeline(trip: trip, spotIds: trip.spotIds, startDate: DateTime.parse(trip.startDate), endDate: DateTime.parse(trip.endDate)) - ); + elements.add(SpotTimeline( + trip: trip, + spotIds: trip.spotIds, + startDate: DateTime.parse(trip.startDate), + endDate: DateTime.parse(trip.endDate) + )); } return Stack( diff --git a/src/flutter_app/lib/components/diary_page/spot_details.dart b/src/flutter_app/lib/components/diary_page/spot_details.dart new file mode 100644 index 0000000..03ca194 --- /dev/null +++ b/src/flutter_app/lib/components/diary_page/spot_details.dart @@ -0,0 +1,338 @@ +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:latlong2/latlong.dart'; +import 'package:skeletons/skeletons.dart'; + +import '../../interfaces/spot/spot.dart'; +import '../../interfaces/trip/trip.dart'; +import '../../services/media_service.dart'; +import '../../services/spot_service.dart'; +import '../MyButtonStyles.dart'; +import '../add/add_route.dart'; +import '../diary_page/timeline/route_timeline.dart'; +import '../edit/edit_spot.dart'; +import '../select/select_route.dart'; + +class SpotDetails extends StatefulWidget { + const SpotDetails({super.key, this.trip, required this.spot, required this.onDelete, required this.onUpdate }); + + final Trip? trip; + final Spot spot; + final ValueSetter onDelete, onUpdate; + + @override + State createState() => _SpotDetailsState(); +} + +class _SpotDetailsState extends State{ + final MediaService mediaService = MediaService(); + final SpotService spotService = SpotService(); + + Future> fetchURLs() { + List> futures = []; + for (var mediaId in widget.spot.mediaIds) { + futures.add(mediaService.getMediumUrl(mediaId)); + } + return Future.wait(futures); + } + + XFile? image; + final ImagePicker picker = ImagePicker(); + + Future getImage(ImageSource media) async { + var img = await picker.pickImage(source: media); + if (img != null){ + var mediaId = await mediaService.uploadMedia(img); + Spot spot = widget.spot; + spot.mediaIds.add(mediaId); + spotService.editSpot(spot.toUpdateSpot()); + } + + setState(() { + image = img; + }); + } + + void addImageDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + title: const Text('Please choose media to select'), + content: SizedBox( + height: MediaQuery.of(context).size.height / 6, + child: Column( + children: [ + ElevatedButton( + //if user click this button, user can upload image from gallery + onPressed: () { + Navigator.pop(context); + getImage(ImageSource.gallery); + }, + child: Row( + children: const [ + Icon(Icons.image), + Text('From Gallery'), + ], + ), + ), + ElevatedButton( + //if user click this button. user can upload image from camera + onPressed: () { + Navigator.pop(context); + getImage(ImageSource.camera); + }, + child: Row( + children: const [ + Icon(Icons.camera), + Text('From Camera'), + ], + ), + ), + ], + ), + ), + ); + }); + } + + void editSpotDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return EditSpot(spot: widget.spot, onUpdate: widget.onUpdate); + }); + } + + @override + void initState(){ + super.initState(); + } + + @override + Widget build(BuildContext context) { + List elements = []; + + // general info + elements.addAll([ + Text( + widget.spot.name, + style: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.w600 + ), + ), + Text( + '${round(widget.spot.coordinates[0], decimals: 8)}, ${round(widget.spot.coordinates[1], decimals: 8)}', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400 + ), + ), + Text( + widget.spot.location, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400 + ), + )]); + // rating + List ratingRowElements = []; + + for (var i = 0; i < 5; i++){ + if (widget.spot.rating > i) { + ratingRowElements.add(const Icon(Icons.favorite, size: 30.0, color: Colors.pink)); + } else { + ratingRowElements.add(const Icon(Icons.favorite, size: 30.0, color: Colors.grey)); + } + } + + elements.add(Center(child: Padding( + padding: const EdgeInsets.all(10), + child:Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: ratingRowElements, + ) + ))); + + // time to walk transport + elements.add(Center(child: Padding( + padding: const EdgeInsets.all(5), + child:Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + const Icon(Icons.train, size: 30.0, color: Colors.green), + Text( + '${widget.spot.distancePublicTransport} min', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400 + ), + ), + const Icon(Icons.directions_car, size: 30.0, color: Colors.red), + Text( + '${widget.spot.distanceParking} min', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400 + ), + ) + ], + ) + ))); + + if (widget.spot.comment.isNotEmpty) { + elements.add(Container( + margin: const EdgeInsets.all(15.0), + padding: const EdgeInsets.all(5.0), + decoration: BoxDecoration( + border: Border.all(color: Colors.blueAccent), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + widget.spot.comment, + ) + )); + } + // images + if (widget.spot.mediaIds.isNotEmpty) { + List imageWidgets = []; + Future> futureMediaUrls = fetchURLs(); + + imageWidgets.add( + FutureBuilder>( + future: futureMediaUrls, + builder: (context, snapshot) { + Widget skeleton = const Padding( + padding: EdgeInsets.all(5), + child: SkeletonAvatar( + style: SkeletonAvatarStyle( + shape: BoxShape.rectangle, width: 150, height: 250 + ), + ) + ); + + if (snapshot.data != null){ + List urls = snapshot.data!; + List images = []; + for (var url in urls){ + images.add( + Padding( + padding: const EdgeInsets.all(5.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.0), + child: Image.network( + url, + fit: BoxFit.fitHeight, + loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) { + if (loadingProgress == null) { + return child; + } + return skeleton; + }, + ) + ), + ) + ); + } + return Container( + padding: const EdgeInsets.all(10), + child: Row( + mainAxisSize: MainAxisSize.min, + children: images + ) + ); + } + List skeletons = []; + for (var i = 0; i < widget.spot.mediaIds.length; i++){ + skeletons.add(skeleton); + } + return Container( + padding: const EdgeInsets.all(10), + child: Row( + mainAxisSize: MainAxisSize.min, + children: skeletons + ) + ); + } + ) + ); + imageWidgets.add( + ElevatedButton.icon( + icon: const Icon(Icons.add, size: 30.0, color: Colors.pink), + label: const Text('Add image'), + onPressed: () => addImageDialog(), + style: MyButtonStyles.rounded + ), + ); + elements.add( + SizedBox( + height: 250, + child: ListView( + scrollDirection: Axis.horizontal, + children: imageWidgets + ) + ), + ); + } else { + elements.add( + ElevatedButton.icon( + icon: const Icon(Icons.add, size: 30.0, color: Colors.pink), + label: const Text('Add image'), + onPressed: () => addImageDialog(), + style: MyButtonStyles.rounded + ), + ); + } + // delete, edit, close + elements.add( + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // delete spot button + IconButton( + onPressed: () { + Navigator.pop(context); + spotService.deleteSpot(widget.spot); + widget.onDelete.call(widget.spot); + }, + icon: const Icon(Icons.delete), + ), + IconButton( + onPressed: () => editSpotDialog(), + icon: const Icon(Icons.edit), + ), + IconButton( + onPressed: () => Navigator.pop(context), + icon: const Icon(Icons.close), + ), + ], + ) + ); + // routes + if (widget.spot.multiPitchRouteIds.isNotEmpty){ + elements.add( + RouteTimeline( + trip: widget.trip, + spot: widget.spot, + singlePitchRouteIds: widget.spot.singlePitchRouteIds, + multiPitchRouteIds: widget.spot.multiPitchRouteIds, + startDate: DateTime(1923), + endDate: DateTime(2123) + ) + ); + } + return Stack( + children: [ + Container( + padding: const EdgeInsets.all(20), + child: ListView( + children: elements + ) + ) + ] + ); + } +} \ No newline at end of file diff --git a/src/flutter_app/lib/components/diary_page/timeline/spot_timeline.dart b/src/flutter_app/lib/components/diary_page/timeline/spot_timeline.dart index 85529de..5cd598b 100644 --- a/src/flutter_app/lib/components/diary_page/timeline/spot_timeline.dart +++ b/src/flutter_app/lib/components/diary_page/timeline/spot_timeline.dart @@ -10,7 +10,7 @@ import '../../../interfaces/trip/trip.dart'; import '../../../interfaces/trip/update_trip.dart'; import '../../../services/spot_service.dart'; import '../../../services/trip_service.dart'; -import '../../detail/spot_details.dart'; +import '../spot_details.dart'; import '../../info/spot_info.dart'; class SpotTimeline extends StatefulWidget { @@ -43,12 +43,13 @@ class SpotTimelineState extends State { if (snapshot.hasData) { var online = snapshot.data!; if (online) { + return FutureBuilder>( future: Future.wait(spotIds.map((spotId) => spotService.getSpot(spotId))), builder: (context, snapshot) { if (snapshot.hasData) { List spots = snapshot.data!.whereType().toList(); - + spots.sort((a, b) => a.name.compareTo(b.name)); updateSpotCallback(Spot spot) { var index = -1; for (int i = 0; i < spots.length; i++) { diff --git a/src/flutter_app/lib/components/diary_page/timeline/trip_timeline.dart b/src/flutter_app/lib/components/diary_page/timeline/trip_timeline.dart index e105699..68e328d 100644 --- a/src/flutter_app/lib/components/diary_page/timeline/trip_timeline.dart +++ b/src/flutter_app/lib/components/diary_page/timeline/trip_timeline.dart @@ -68,7 +68,12 @@ class TripTimelineState extends State { Navigator.push( context, MaterialPageRoute( - builder: (context) => const AddTrip(), + builder: (context) => AddTrip( + onAdd: (trip) { + trips.add(trip); + setState(() {}); + } + ), ) ); }, @@ -108,7 +113,7 @@ class TripTimelineState extends State { trip: trips[index], spotIds: trips[index].spotIds, startDate: DateTime.parse(trips[index].startDate), - endDate: DateTime.parse(trips[index].endDate) + endDate: DateTime.parse(trips[index].endDate), ) ); } @@ -122,8 +127,13 @@ class TripTimelineState extends State { borderRadius: BorderRadius.circular(20), ), child: TripDetails(trip: trips[index], - onDelete: deleteTripCallback, - onUpdate: updateTripCallback) + onTripDelete: deleteTripCallback, + onTripUpdate: updateTripCallback, + onSpotAdd: (spot) { + trips[index].spotIds.add(spot.id); + setState(() {}); + }, + ) ), ), child: Ink( diff --git a/src/flutter_app/lib/components/info/route_info.dart b/src/flutter_app/lib/components/info/route_info.dart index b7d53d1..b68895a 100644 --- a/src/flutter_app/lib/components/info/route_info.dart +++ b/src/flutter_app/lib/components/info/route_info.dart @@ -45,12 +45,21 @@ class _RouteInfoState extends State{ style: MyTextStyles.title, )); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: listInfo, + ); + } else { + List listInfo = []; + listInfo.add(Text( + widget.route.name, + style: MyTextStyles.title, + )); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: listInfo, ); } - return const CircularProgressIndicator(); } ); } diff --git a/src/flutter_app/lib/pages/list_page.dart b/src/flutter_app/lib/components/list_page/list_page.dart similarity index 50% rename from src/flutter_app/lib/pages/list_page.dart rename to src/flutter_app/lib/components/list_page/list_page.dart index 7bd5521..a63e280 100644 --- a/src/flutter_app/lib/pages/list_page.dart +++ b/src/flutter_app/lib/components/list_page/list_page.dart @@ -1,7 +1,9 @@ +import 'package:climbing_diary/components/diary_page/timeline/spot_timeline.dart'; +import 'package:climbing_diary/components/list_page/spot_list.dart'; import 'package:flutter/material.dart'; import 'package:latlong2/latlong.dart'; -import '../interfaces/spot/spot.dart'; -import '../services/spot_service.dart'; +import '../../interfaces/spot/spot.dart'; +import '../../services/spot_service.dart'; class ListPage extends StatefulWidget { const ListPage({super.key}); @@ -15,10 +17,16 @@ class ListPageState extends State { final TextEditingController controllerSearch = TextEditingController(); final SpotService spotService = SpotService(); + bool searchSpots = true; + bool searchRoutes = false; + bool searchPitches = false; @override void initState(){ super.initState(); + searchSpots = true; + searchRoutes = false; + searchPitches = false; } @override @@ -43,8 +51,8 @@ class ListPageState extends State { borderRadius: BorderRadius.all(Radius.circular(25.0)), borderSide: BorderSide(color: Colors.blue), ), - hintText: "spot, route or pitch name", - labelText: "spot, route or pitch name" + hintText: "name", + labelText: "name" ), onChanged: (String s) { setState(() {}); @@ -52,38 +60,43 @@ class ListPageState extends State { ), ), )); - for (Spot spot in spots){ - elements.add( - Padding( - padding: const EdgeInsets.all(5), - child: Card( - elevation: 2, - child: Padding( - padding: const EdgeInsets.all(5), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - spot.name, - style: const TextStyle( - fontSize: 22, - fontWeight: FontWeight.w600, - ), - ), - Text( - '${round(spot.coordinates[0], decimals: 8)}, ${round(spot.coordinates[1], decimals: 8)}', - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400 - ), - ), - ] - ), - ) - ) - ) - ); - } + elements.add(Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + const Text("spots"), + Switch( + value: searchSpots, + onChanged: (bool value) { + setState(() { + searchSpots = value; + }); + } + ), + const Text("routes"), + Switch( + value: searchRoutes, + onChanged: (bool value) { + setState(() { + searchRoutes = value; + }); + } + ), + const Text("pitches"), + Switch( + value: searchPitches, + onChanged: (bool value) { + setState(() { + searchPitches = value; + }); + } + ), + ] + )); + elements.add( Padding( + padding: const EdgeInsets.all(10), + child: SpotList( + spots: spots + ))); return ListView( children: elements, ); diff --git a/src/flutter_app/lib/components/list_page/spot_list.dart b/src/flutter_app/lib/components/list_page/spot_list.dart index 3ec05e2..43f95cb 100644 --- a/src/flutter_app/lib/components/list_page/spot_list.dart +++ b/src/flutter_app/lib/components/list_page/spot_list.dart @@ -1,23 +1,122 @@ +import 'package:climbing_diary/components/diary_page/image_list_view.dart'; +import 'package:climbing_diary/components/diary_page/rating_row.dart'; +import 'package:climbing_diary/components/diary_page/timeline/route_timeline.dart'; import 'package:flutter/material.dart'; +import 'package:internet_connection_checker/internet_connection_checker.dart'; +import 'package:timelines/timelines.dart'; + +import '../../../interfaces/spot/spot.dart'; +import '../../../services/spot_service.dart'; +import '../detail/spot_details.dart'; +import '../info/spot_info.dart'; class SpotList extends StatefulWidget { - const SpotList({super.key}); + const SpotList({super.key, required this.spots}); + + final List spots; @override State createState() => SpotListState(); } class SpotListState extends State { + final SpotService spotService = SpotService(); @override void initState(){ super.initState(); + checkConnection(); } @override Widget build(BuildContext context) { - return const Scaffold( - body: Text("spots"), + List spots = widget.spots; + spots.sort((a, b) => a.name.compareTo(b.name)); + + updateSpotCallback(Spot spot) { + var index = -1; + for (int i = 0; i < spots.length; i++) { + if (spots[i].id == spot.id) { + index = i; + } + } + spots.removeAt(index); + spots.add(spot); + setState(() {}); + } + + return Column( + children: [ + FixedTimeline.tileBuilder( + theme: TimelineThemeData( + nodePosition: 0, + color: const Color(0xff989898), + indicatorTheme: const IndicatorThemeData( + position: 0, + size: 20.0, + ), + connectorTheme: const ConnectorThemeData( + thickness: 2.5, + ), + ), + builder: TimelineTileBuilder.connected( + connectionDirection: ConnectionDirection.before, + itemCount: spots.length, + contentsBuilder: (_, index) { + List elements = []; + // spot info + elements.add(SpotInfo(spot: spots[index])); + // rating as hearts in a row + elements.add(RatingRow(rating: spots[index].rating)); + // images list view + if (spots[index].mediaIds.isNotEmpty) { + elements.add( + ImageListView(mediaIds: spots[index].mediaIds) + ); + } + return InkWell( + onTap: () => + showDialog( + context: context, + builder: (BuildContext context) => + Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + child: SpotDetails( + spot: spots[index], + onDelete: (Spot value) { }, + onUpdate: (Spot value) { }, + ) + ), + ), + child: Ink( + child: Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: elements, + ), + ) + ), + ); + }, + indicatorBuilder: (_, index) { + return const OutlinedDotIndicator( + borderWidth: 2.5, + color: Color(0xff66c97f), + ); + }, + connectorBuilder: (_, index, ___) => + const SolidLineConnector(color: Color(0xff66c97f)), + ), + ) + ], ); } + + Future checkConnection() async { + return await InternetConnectionChecker().hasConnection; + } } \ No newline at end of file diff --git a/src/flutter_app/lib/components/map_page/add_spot.dart b/src/flutter_app/lib/components/map_page/add_spot.dart index f07ef95..739376b 100644 --- a/src/flutter_app/lib/components/map_page/add_spot.dart +++ b/src/flutter_app/lib/components/map_page/add_spot.dart @@ -1,3 +1,5 @@ +import 'package:climbing_diary/components/MyTextStyles.dart'; +import 'package:climbing_diary/components/map_page/navigation_screen_page.dart'; import 'package:climbing_diary/interfaces/trip/update_trip.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -12,12 +14,10 @@ import 'package:internet_connection_checker/internet_connection_checker.dart'; import '../../services/trip_service.dart'; class AddSpot extends StatefulWidget { - const AddSpot({super.key, required this.trips, required this.coordinates, required this.address, required this.onAdd}); + const AddSpot({super.key, this.trip, required this.onAdd}); - final LatLng coordinates; - final String address; final ValueSetter onAdd; - final List trips; + final Trip? trip; @override State createState() => _AddSpotState(); @@ -37,7 +37,6 @@ class _AddSpotState extends State{ final TextEditingController controllerCar = TextEditingController(); double currentSliderValue = 0; - Trip? dropdownValue; @override void initState(){ @@ -47,12 +46,17 @@ class _AddSpotState extends State{ @override Widget build(BuildContext context) { - controllerAddress.text = widget.address; - controllerLat.text = widget.coordinates.latitude.toString(); - controllerLong.text = widget.coordinates.longitude.toString(); + // controllerLat.text = widget.coordinates.latitude.toString(); + // controllerLong.text = widget.coordinates.longitude.toString(); List elements = []; + if (widget.trip != null){ + elements.add(Text( + widget.trip!.name, + style: MyTextStyles.title, + )); + } elements.add( TextFormField( validator: (value) { @@ -65,20 +69,24 @@ class _AddSpotState extends State{ hintText: "name", labelText: "name"), ) ); - elements.add(DropdownButton( - isExpanded: true, - value: dropdownValue, - items: widget.trips.map>((Trip trip) { - return DropdownMenuItem( - value: trip, - child: Text(trip.name), - ); - }).toList(), - onChanged: (Trip? trip) { - setState(() { - dropdownValue = trip!; - }); - } + elements.add(TextButton.icon( + icon: const Icon(Icons.place, size: 30.0), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => NavigationScreenPage( + onAdd: (List results) { + controllerAddress.text = results[0]; + controllerLat.text = results[1]; + controllerLong.text = results[2]; + setState(() {}); + } + ), + ) + ); + }, + label: const Text("select location"), )); elements.add(TextFormField( controller: controllerAddress, @@ -132,6 +140,7 @@ class _AddSpotState extends State{ } return null; }, + keyboardType: TextInputType.number, controller: controllerBus, decoration: const InputDecoration( hintText: "in minutes", @@ -148,6 +157,7 @@ class _AddSpotState extends State{ } return null; }, + keyboardType: TextInputType.number, controller: controllerCar, decoration: const InputDecoration( hintText: "in minutes", @@ -161,41 +171,44 @@ class _AddSpotState extends State{ ), title: const Text('Add a new spot'), content: SingleChildScrollView( - child: Form( - key: _formKey, - child: Column( - mainAxisSize: MainAxisSize.min, - children: elements, - ), + child: Form( + key: _formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: elements, ), + ), ), actions: [ TextButton( - onPressed: () async { - bool result = await InternetConnectionChecker().hasConnection; - if (_formKey.currentState!.validate()) { - var valDistanceParking = int.tryParse(controllerCar.text); - var valDistancePublicTransport = int.tryParse(controllerBus.text); - CreateSpot spot = CreateSpot( - comment: controllerComment.text, - coordinates: [double.parse(controllerLat.text), double.parse(controllerLong.text)], - distanceParking: (valDistanceParking != null) ? valDistanceParking : 0, - distancePublicTransport: (valDistancePublicTransport != null) ? valDistancePublicTransport : 0, - location: controllerAddress.text, - name: controllerTitle.text, - rating: currentSliderValue.toInt(), - ); - Navigator.popUntil(context, ModalRoute.withName('/')); - Spot? createdSpot = await spotService.createSpot(spot, result); - UpdateTrip editTrip = dropdownValue!.toUpdateTrip(); - if (createdSpot != null) { - editTrip.spotIds?.add(createdSpot.id); - Trip? editedTrip = await tripService.editTrip(editTrip); - widget.onAdd.call(createdSpot); + onPressed: () async { + bool result = await InternetConnectionChecker().hasConnection; + if (_formKey.currentState!.validate()) { + var valDistanceParking = int.tryParse(controllerCar.text); + var valDistancePublicTransport = int.tryParse(controllerBus.text); + CreateSpot spot = CreateSpot( + comment: controllerComment.text, + coordinates: [double.parse(controllerLat.text), double.parse(controllerLong.text)], + distanceParking: (valDistanceParking != null) ? valDistanceParking : 0, + distancePublicTransport: (valDistancePublicTransport != null) ? valDistancePublicTransport : 0, + location: controllerAddress.text, + name: controllerTitle.text, + rating: currentSliderValue.toInt(), + ); + Navigator.popUntil(context, ModalRoute.withName('/')); + Spot? createdSpot = await spotService.createSpot(spot, result); + if (createdSpot != null) { + if (widget.trip != null) { + Trip trip = widget.trip!; + UpdateTrip editTrip = trip.toUpdateTrip(); + editTrip.spotIds?.add(createdSpot.id); + Trip? editedTrip = await tripService.editTrip(editTrip); + } + widget.onAdd.call(createdSpot); + } } - } - }, - child: const Icon(Icons.save)) + }, + child: const Icon(Icons.save)) ], ); } diff --git a/src/flutter_app/lib/components/map_page/details/spot_details.dart b/src/flutter_app/lib/components/map_page/details/spot_details.dart index 58b0fbc..9a0b71b 100644 --- a/src/flutter_app/lib/components/map_page/details/spot_details.dart +++ b/src/flutter_app/lib/components/map_page/details/spot_details.dart @@ -9,9 +9,8 @@ import '../../../services/media_service.dart'; import '../../../services/spot_service.dart'; import '../../MyButtonStyles.dart'; import '../../add/add_route.dart'; -import '../../diary_page/timeline/route_timeline.dart'; import '../../edit/edit_spot.dart'; -import '../../select/select_route.dart'; +import '../route_list.dart'; class SpotDetails extends StatefulWidget { const SpotDetails({super.key, this.trip, required this.spot, required this.onDelete, required this.onUpdate }); @@ -334,16 +333,13 @@ class _SpotDetailsState extends State{ ) ); // routes - // routes if (widget.spot.multiPitchRouteIds.isNotEmpty || widget.spot.singlePitchRouteIds.isNotEmpty){ elements.add( - RouteTimeline( + RouteList( trip: widget.trip, spot: widget.spot, singlePitchRouteIds: widget.spot.singlePitchRouteIds, multiPitchRouteIds: widget.spot.multiPitchRouteIds, - startDate: DateTime(1923), - endDate: DateTime(2123), ) ); } diff --git a/src/flutter_app/lib/components/map_page/map_page.dart b/src/flutter_app/lib/components/map_page/map_page.dart index d0e2a4d..b4d5498 100644 --- a/src/flutter_app/lib/components/map_page/map_page.dart +++ b/src/flutter_app/lib/components/map_page/map_page.dart @@ -1,9 +1,11 @@ +import 'dart:ffi'; + import 'package:climbing_diary/pages/save_location_no_connection.dart'; import 'package:flutter/material.dart'; +import 'add_spot.dart'; import 'details/spot_details.dart'; import '../../services/cache.dart'; -import 'navigation_screen_page.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; import 'package:internet_connection_checker/internet_connection_checker.dart'; @@ -94,25 +96,17 @@ class _MapPageState extends State { ], ) ), - floatingActionButton: IconButton( - icon: const Icon(Icons.add, size: 50.0), + floatingActionButton: FloatingActionButton( onPressed: () { - if (online) { - Navigator.push( - context, - MaterialPageRoute( - builder: ( - context) => NavigationScreenPage(onAdd: addSpotCallback)), - ); - } else { - Navigator.push( - context, - MaterialPageRoute( - builder: ( - context) => SaveLocationNoConnectionPage(onAdd: addSpotCallback)), - ); - } + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AddSpot(onAdd: (spot) => addSpotCallback(spot))), + ); }, + backgroundColor: Colors.green, + elevation: 5, + child: const Icon(Icons.add, size: 50.0), ) ); } @@ -141,25 +135,18 @@ class _MapPageState extends State { ], ) ), - floatingActionButton: IconButton( - icon: const Icon(Icons.add, size: 50.0), + floatingActionButton: FloatingActionButton( onPressed: () { - if (online) { - Navigator.push( - context, - MaterialPageRoute( - builder: ( - context) => NavigationScreenPage(onAdd: addSpotCallback)), - ); - } else { - Navigator.push( - context, - MaterialPageRoute( - builder: ( - context) => SaveLocationNoConnectionPage(onAdd: addSpotCallback)), - ); - } + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AddSpot(onAdd: (spot) => addSpotCallback(spot)), + ) + ); }, + backgroundColor: Colors.green, + elevation: 5, + child: const Icon(Icons.add, size: 50.0, color: Colors.white,), ) ); } else if (snapshot.hasError) { @@ -182,6 +169,8 @@ class _MapPageState extends State { builder: (context) => SaveLocationNoConnectionPage(onAdd: (Spot value) {})), ); }, + backgroundColor: Colors.green, + elevation: 5, child: const Icon(Icons.add), ), ); diff --git a/src/flutter_app/lib/components/map_page/navigation_screen_page.dart b/src/flutter_app/lib/components/map_page/navigation_screen_page.dart index a4ffe4c..3461ca0 100644 --- a/src/flutter_app/lib/components/map_page/navigation_screen_page.dart +++ b/src/flutter_app/lib/components/map_page/navigation_screen_page.dart @@ -3,17 +3,14 @@ import 'package:geolocator/geolocator.dart'; import 'package:latlong2/latlong.dart'; import 'package:open_street_map_search_and_pick/open_street_map_search_and_pick.dart'; -import '../../interfaces/spot/spot.dart'; import '../../interfaces/trip/trip.dart'; import '../../services/location_service.dart'; import '../../services/trip_service.dart'; -import 'add_spot.dart'; - class NavigationScreenPage extends StatefulWidget { const NavigationScreenPage({super.key, required this.onAdd}); - final ValueSetter onAdd; + final ValueSetter> onAdd; @override State createState() => _NavigationScreenPage(); @@ -34,57 +31,46 @@ class _NavigationScreenPage extends State { @override Widget build(BuildContext context) { return FutureBuilder>( - future: Future.wait([locationService.getPosition(), tripService.getTrips()]), - builder: (context, snapshot) { - if (snapshot.hasData) { - Position position = snapshot.data![0]; - List trips = snapshot.data![1]; + future: Future.wait([locationService.getPosition(), tripService.getTrips()]), + builder: (context, snapshot) { + if (snapshot.hasData) { + Position position = snapshot.data![0]; + List trips = snapshot.data![1]; + return Scaffold( + body: OpenStreetMapSearchAndPick( + center: LatLong(position.latitude, position.longitude), + buttonColor: Theme.of(context).colorScheme.primary, + buttonText: 'Set location', + onPicked: (pickedData) { + setState(() { + address = pickedData.address; + widget.onAdd.call([address.toString(), pickedData.latLong.latitude.toString(), pickedData.latLong.longitude.toString()]); + Navigator.pop(context); + }); + } + ) + ); + } else if (snapshot.hasError) { + return Text('${snapshot.error}'); + } return Scaffold( - body: OpenStreetMapSearchAndPick( - center: LatLong(position.latitude, position.longitude), - buttonColor: Theme.of(context).colorScheme.primary, - buttonText: 'Set location', - onPicked: (pickedData) { - setState(() { - address = pickedData.address; - }); - showDialog( - context: context, - builder: (BuildContext context) => - AddSpot( - trips: trips, - coordinates: LatLng( - pickedData.latLong.latitude, - pickedData.latLong.longitude - ), - address: address, - onAdd: widget.onAdd, - ) - ); - } - ) + body: Center ( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: const [ + Padding( + padding: EdgeInsets.all(50), + child: SizedBox( + height: 100.0, + width: 100.0, + child: CircularProgressIndicator(), + ), + ) + ], + ) + ) ); - } else if (snapshot.hasError) { - return Text('${snapshot.error}'); } - return Scaffold( - body: Center ( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: const [ - Padding( - padding: EdgeInsets.all(50), - child: SizedBox( - height: 100.0, - width: 100.0, - child: CircularProgressIndicator(), - ), - ) - ], - ) - ) - ); - } ); } } diff --git a/src/flutter_app/lib/components/map_page/route_list.dart b/src/flutter_app/lib/components/map_page/route_list.dart new file mode 100644 index 0000000..26909bc --- /dev/null +++ b/src/flutter_app/lib/components/map_page/route_list.dart @@ -0,0 +1,270 @@ +import 'package:climbing_diary/components/diary_page/image_list_view.dart'; +import 'package:climbing_diary/components/diary_page/timeline/pitch_timeline.dart'; +import 'package:climbing_diary/components/diary_page/rating_row.dart'; +import 'package:climbing_diary/interfaces/multi_pitch_route/multi_pitch_route.dart'; +import 'package:climbing_diary/interfaces/single_pitch_route/single_pitch_route.dart'; +import 'package:flutter/material.dart'; +import 'package:internet_connection_checker/internet_connection_checker.dart'; +import 'package:timelines/timelines.dart'; + +import '../../../interfaces/spot/spot.dart'; +import '../../../interfaces/trip/trip.dart'; +import '../../../services/pitch_service.dart'; +import '../../../services/route_service.dart'; +import '../detail/multi_pitch_route_details.dart'; +import '../detail/single_pitch_route_details.dart'; +import '../info/multi_pitch_route_info.dart'; +import '../info/route_info.dart'; +import '../info/single_pitch_route_info.dart'; + +class RouteList extends StatefulWidget { + const RouteList({super.key, this.trip, required this.spot, required this.singlePitchRouteIds, required this.multiPitchRouteIds}); + + final Trip? trip; + final Spot spot; + final List singlePitchRouteIds, multiPitchRouteIds; + + @override + State createState() => RouteListState(); +} + +class RouteListState extends State { + final RouteService routeService = RouteService(); + final PitchService pitchService = PitchService(); + + @override + void initState(){ + super.initState(); + checkConnection(); + } + + @override + Widget build(BuildContext context) { + List singlePitchRouteIds = widget.singlePitchRouteIds; + List multiPitchRouteIds = widget.multiPitchRouteIds; + return FutureBuilder( + future: checkConnection(), + builder: (context, snapshot) { + if (snapshot.hasData) { + var online = snapshot.data!; + if (online) { + return FutureBuilder>( + future: Future.wait(multiPitchRouteIds.map((routeId) => routeService.getMultiPitchRoute(routeId))), + builder: (context, snapshot) { + if (snapshot.hasData) { + List multiPitchRoutes = snapshot.data!.whereType().toList(); + return FutureBuilder>( + future: Future.wait(singlePitchRouteIds.map((routeId) => routeService.getSinglePitchRoute(routeId))), + builder: (context, snapshot) { + if (snapshot.hasData) { + List singlePitchRoutes = snapshot.data!.whereType().toList(); + + updateMultiPitchRouteCallback(MultiPitchRoute route) { + var index = -1; + for (int i = 0; i < multiPitchRoutes.length; i++) { + if (multiPitchRoutes[i].id == route.id) { + index = i; + } + } + multiPitchRoutes.removeAt(index); + multiPitchRoutes.add(route); + setState(() {}); + } + + deleteMultiPitchRouteCallback(MultiPitchRoute route) { + multiPitchRoutes.remove(route); + setState(() {}); + } + + List elements = []; + + if (multiPitchRoutes.isNotEmpty){ + elements.add( + FixedTimeline.tileBuilder( + theme: TimelineThemeData( + nodePosition: 0, + color: const Color(0xff989898), + indicatorTheme: const IndicatorThemeData( + position: 0, + size: 20.0, + ), + connectorTheme: const ConnectorThemeData( + thickness: 2.5, + ), + ), + builder: TimelineTileBuilder.connected( + connectionDirection: ConnectionDirection.before, + itemCount: multiPitchRoutes.length, + contentsBuilder: (_, index) { + List elements = []; + // route info + MultiPitchRoute multiPitchRoute = multiPitchRoutes[index]; + elements.add(RouteInfo(route: multiPitchRoute)); + // rating as hearts in a row + elements.add(RatingRow(rating: multiPitchRoute.rating)); + // images list view + if (multiPitchRoute.mediaIds.isNotEmpty) { + elements.add( + ImageListView(mediaIds: multiPitchRoute.mediaIds) + ); + } + // pitches + if (multiPitchRoute.pitchIds.isNotEmpty) { + // multi pitch + elements.add( + MultiPitchInfo( + pitchIds: multiPitchRoute.pitchIds + ) + ); + elements.add( + PitchTimeline( + trip: widget.trip, + spot: widget.spot, + route: multiPitchRoute, + pitchIds: multiPitchRoute.pitchIds + ) + ); + } + return InkWell( + onTap: () => showDialog( + context: context, + builder: (BuildContext context) => + Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + child: MultiPitchRouteDetails( + spot: widget.spot, + route: multiPitchRoute, + onDelete: deleteMultiPitchRouteCallback, + onUpdate: updateMultiPitchRouteCallback, + spotId: widget.spot.id + ) + ) + ), + child: Ink( + child: Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: elements, + ), + ) + ), + ); + }, + indicatorBuilder: (_, index) { + return const OutlinedDotIndicator( + borderWidth: 2.5, + color: Color(0xff66c97f), + ); + }, + connectorBuilder: (_, index, ___) => + const SolidLineConnector(color: Color(0xff66c97f)), + ), + ), + ); + } + + if (singlePitchRoutes.isNotEmpty){ + elements.add( + FixedTimeline.tileBuilder( + theme: TimelineThemeData( + nodePosition: 0, + color: const Color(0xff989898), + indicatorTheme: const IndicatorThemeData( + position: 0, + size: 20.0, + ), + connectorTheme: const ConnectorThemeData( + thickness: 2.5, + ), + ), + builder: TimelineTileBuilder.connected( + connectionDirection: ConnectionDirection.before, + itemCount: singlePitchRoutes.length, + contentsBuilder: (_, index) { + List elements = []; + // route info + SinglePitchRoute singlePitchRoute = singlePitchRoutes[index]; + elements.add(SinglePitchRouteInfo( + spot: widget.spot, + route: singlePitchRoute + )); + // rating as hearts in a row + elements.add(RatingRow(rating: singlePitchRoute.rating)); + // images list view + if (singlePitchRoute.mediaIds.isNotEmpty) { + elements.add( + ImageListView(mediaIds: singlePitchRoute.mediaIds) + ); + } + + return InkWell( + onTap: () => showDialog( + context: context, + builder: (BuildContext context) => + Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + child: SinglePitchRouteDetails( + spot: widget.spot, + route: singlePitchRoute, + onDelete: (SinglePitchRoute sPR) => {}, + onUpdate: (SinglePitchRoute sPR) => {}, + spotId: widget.spot.id) + ) + ), + child: Ink( + child: Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: elements, + ), + ) + ), + ); + }, + indicatorBuilder: (_, index) { + return const OutlinedDotIndicator( + borderWidth: 2.5, + color: Color(0xff66c97f), + ); + }, + connectorBuilder: (_, index, ___) => + const SolidLineConnector(color: Color(0xff66c97f)), + ), + ) + ); + } + return Column( + children: elements, + ); + } else { + return const CircularProgressIndicator(); + } + } + ); + } else { + return const CircularProgressIndicator(); + } + } + ); + } else { + return const CircularProgressIndicator(); + } + } else { + return const CircularProgressIndicator(); + } + } + ); + } + + Future checkConnection() async { + return await InternetConnectionChecker().hasConnection; + } +} \ No newline at end of file diff --git a/src/flutter_app/lib/components/map_page/save_location_no_connection.dart b/src/flutter_app/lib/components/map_page/save_location_no_connection.dart index ff75e25..61ecdaa 100644 --- a/src/flutter_app/lib/components/map_page/save_location_no_connection.dart +++ b/src/flutter_app/lib/components/map_page/save_location_no_connection.dart @@ -35,9 +35,6 @@ class _SaveLocationNoConnectionPage extends State Position position = snapshot.data!; return Scaffold( body: AddSpot( - trips: [], - coordinates: LatLng(position.latitude, position.longitude), - address: " ", onAdd: widget.onAdd ) ); diff --git a/src/flutter_app/lib/main.dart b/src/flutter_app/lib/main.dart index 39237d6..1ff0cbc 100644 --- a/src/flutter_app/lib/main.dart +++ b/src/flutter_app/lib/main.dart @@ -1,5 +1,5 @@ import 'package:auth0_flutter/auth0_flutter.dart'; -import 'package:climbing_diary/pages/list_page.dart'; +import 'package:climbing_diary/components/list_page/list_page.dart'; import 'package:internet_connection_checker/internet_connection_checker.dart'; import 'package:overlay_support/overlay_support.dart'; import 'config/environment.dart'; diff --git a/src/flutter_app/lib/pages/navigation_screen_page.dart b/src/flutter_app/lib/pages/navigation_screen_page.dart index 14cc686..62c8bf2 100644 --- a/src/flutter_app/lib/pages/navigation_screen_page.dart +++ b/src/flutter_app/lib/pages/navigation_screen_page.dart @@ -12,7 +12,7 @@ import '../services/trip_service.dart'; class NavigationScreenPage extends StatefulWidget { const NavigationScreenPage({super.key, required this.onAdd}); - final ValueSetter onAdd; + final ValueSetter> onAdd; @override State createState() => _NavigationScreenPage(); @@ -47,19 +47,6 @@ class _NavigationScreenPage extends State { setState(() { address = pickedData.address; }); - showDialog( - context: context, - builder: (BuildContext context) => - AddSpot( - trips: trips, - coordinates: LatLng( - pickedData.latLong.latitude, - pickedData.latLong.longitude - ), - address: address, - onAdd: widget.onAdd, - ) - ); } ) ); diff --git a/src/flutter_app/lib/pages/save_location_no_connection.dart b/src/flutter_app/lib/pages/save_location_no_connection.dart index 6a0ecea..529520d 100644 --- a/src/flutter_app/lib/pages/save_location_no_connection.dart +++ b/src/flutter_app/lib/pages/save_location_no_connection.dart @@ -34,9 +34,6 @@ class _SaveLocationNoConnectionPage extends State Position position = snapshot.data!; return Scaffold( body: AddSpot( - trips: [], - coordinates: LatLng(position.latitude, position.longitude), - address: " ", onAdd: widget.onAdd ) ); diff --git a/src/flutter_app/lib/services/spot_service.dart b/src/flutter_app/lib/services/spot_service.dart index fae5dda..683ea35 100644 --- a/src/flutter_app/lib/services/spot_service.dart +++ b/src/flutter_app/lib/services/spot_service.dart @@ -3,6 +3,10 @@ import 'package:hive/hive.dart'; import 'package:overlay_support/overlay_support.dart'; import '../config/environment.dart'; +import '../interfaces/ascent/ascent.dart'; +import '../interfaces/multi_pitch_route/multi_pitch_route.dart'; +import '../interfaces/pitch/pitch.dart'; +import '../interfaces/single_pitch_route/single_pitch_route.dart'; import '../interfaces/spot/create_spot.dart'; import 'package:dio/dio.dart'; @@ -51,6 +55,9 @@ class SpotService { } Future> getSpotsByName(String name) async { + if (name == ""){ + return []; + } try { final Response response = await netWorkLocator.dio.get('$climbingApiHost/spot'); @@ -88,6 +95,47 @@ class SpotService { } } + Future getSpotIfWithinDateRange(String spotId, DateTime startDate, DateTime endDate) async { + final Response spotResponse = + await netWorkLocator.dio.get('$climbingApiHost/spot/$spotId'); + if (spotResponse.statusCode == 200) { + Spot spot = Spot.fromJson(spotResponse.data); + for (String routeId in spot.multiPitchRouteIds){ + final Response multiPitchResponse = await netWorkLocator.dio.get('$climbingApiHost/multi_pitch_route/$routeId'); + if (multiPitchResponse.statusCode == 200) { + MultiPitchRoute multiPitchRoute = MultiPitchRoute.fromJson(multiPitchResponse.data); + for (String pitchId in multiPitchRoute.pitchIds){ + final Response pitchResponse = await netWorkLocator.dio.get('$climbingApiHost/pitch/$pitchId'); + Pitch pitch = Pitch.fromJson(pitchResponse.data); + for (String ascentId in pitch.ascentIds){ + final Response ascentResponse = await netWorkLocator.dio.get('$climbingApiHost/ascent/$ascentId'); + Ascent ascent = Ascent.fromJson(ascentResponse.data); + DateTime dateOfAscent = DateTime.parse(ascent.date); + if ((dateOfAscent.isAfter(startDate) && dateOfAscent.isBefore(endDate)) || dateOfAscent.isAtSameMomentAs(startDate) || dateOfAscent.isAtSameMomentAs(endDate)){ + return spot; + } + } + } + } + } + for (String routeId in spot.singlePitchRouteIds){ + final Response singlePitchResponse = await netWorkLocator.dio.get('$climbingApiHost/single_pitch_route/$routeId'); + if (singlePitchResponse.statusCode == 200) { + SinglePitchRoute singlePitchRoute = SinglePitchRoute.fromJson(singlePitchResponse.data); + for (String ascentId in singlePitchRoute.ascentIds){ + final Response ascentResponse = await netWorkLocator.dio.get('$climbingApiHost/ascent/$ascentId'); + Ascent ascent = Ascent.fromJson(ascentResponse.data); + DateTime dateOfAscent = DateTime.parse(ascent.date); + if ((dateOfAscent.isAfter(startDate) && dateOfAscent.isBefore(endDate)) || dateOfAscent.isAtSameMomentAs(startDate) || dateOfAscent.isAtSameMomentAs(endDate)){ + return spot; + } + } + } + } + } + return null; + } + Future createSpot(CreateSpot createSpot, bool hasConnection) async { CreateSpot spot = CreateSpot( name: createSpot.name, From c5bc669c7ef3589d46a5272a93df0e5a8ee2e91f Mon Sep 17 00:00:00 2001 From: florianpix Date: Thu, 29 Jun 2023 18:29:32 +0200 Subject: [PATCH 02/13] fixed removal of spotid from trips at delete of spot; --- src/climbingAPI/app/routers/spot.py | 2 +- src/climbingAPI/app/routers/trip.py | 2 +- .../lib/components/diary_page/timeline/spot_timeline.dart | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/climbingAPI/app/routers/spot.py b/src/climbingAPI/app/routers/spot.py index 78dd99e..9b83ebf 100644 --- a/src/climbingAPI/app/routers/spot.py +++ b/src/climbingAPI/app/routers/spot.py @@ -150,6 +150,6 @@ async def delete_spot(spot_id: str, user: Auth0User = Security(auth.get_user, sc trips = await db["trip"].find({"user_id": user.id}).to_list(None) if trips: for trip in trips: - update_result = await db["trip"].update_one({"_id": ObjectId(trip['_id'])}, {"$pull": {"spot_ids": ObjectId(spot_id)}}) + update_result = await db["trip"].update_one({"_id": ObjectId(trip['_id']), "user_id": user.id}, {"$pull": {"spot_ids": spot_id}}) # spot_id was removed from trips return Response(status_code=status.HTTP_204_NO_CONTENT) diff --git a/src/climbingAPI/app/routers/trip.py b/src/climbingAPI/app/routers/trip.py index 9e49744..91940f9 100644 --- a/src/climbingAPI/app/routers/trip.py +++ b/src/climbingAPI/app/routers/trip.py @@ -38,7 +38,7 @@ async def retrieve_trips(user: Auth0User = Security(auth.get_user, scopes=["read @router.get('/{trip_id}', description="Get a trip", response_model=TripModel, dependencies=[Depends(auth.implicit_scheme)]) async def retrieve_trip(trip_id: str, user: Auth0User = Security(auth.get_user, scopes=["read:diary"])): db = await get_db() - if (trip := await db["trips"].find_one({"_id": ObjectId(trip_id), "user_id": user.id})) is not None: + if (trip := await db["trip"].find_one({"_id": ObjectId(trip_id), "user_id": user.id})) is not None: return trip raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Trip {trip_id} not found") diff --git a/src/flutter_app/lib/components/diary_page/timeline/spot_timeline.dart b/src/flutter_app/lib/components/diary_page/timeline/spot_timeline.dart index 5cd598b..8ccddb4 100644 --- a/src/flutter_app/lib/components/diary_page/timeline/spot_timeline.dart +++ b/src/flutter_app/lib/components/diary_page/timeline/spot_timeline.dart @@ -65,8 +65,6 @@ class SpotTimelineState extends State { deleteSpotCallback(Spot spot) { spots.remove(spot); widget.trip.spotIds.remove(spot.id); - UpdateTrip editTrip = widget.trip.toUpdateTrip(); - tripService.editTrip(editTrip); setState(() {}); } From 1ffd5969d6ddea9d3079014ffc9947ef0a3b87ff Mon Sep 17 00:00:00 2001 From: florianpix Date: Thu, 29 Jun 2023 18:33:55 +0200 Subject: [PATCH 03/13] fixed bug in spotDetails that endlessly showed circular progress indicator if only singlepitchroutes existed; --- .../lib/components/map_page/route_list.dart | 356 +++++++++--------- 1 file changed, 176 insertions(+), 180 deletions(-) diff --git a/src/flutter_app/lib/components/map_page/route_list.dart b/src/flutter_app/lib/components/map_page/route_list.dart index 26909bc..9659de9 100644 --- a/src/flutter_app/lib/components/map_page/route_list.dart +++ b/src/flutter_app/lib/components/map_page/route_list.dart @@ -48,197 +48,196 @@ class RouteListState extends State { if (snapshot.hasData) { var online = snapshot.data!; if (online) { + List elements = []; return FutureBuilder>( future: Future.wait(multiPitchRouteIds.map((routeId) => routeService.getMultiPitchRoute(routeId))), builder: (context, snapshot) { if (snapshot.hasData) { List multiPitchRoutes = snapshot.data!.whereType().toList(); - return FutureBuilder>( - future: Future.wait(singlePitchRouteIds.map((routeId) => routeService.getSinglePitchRoute(routeId))), - builder: (context, snapshot) { - if (snapshot.hasData) { - List singlePitchRoutes = snapshot.data!.whereType().toList(); - - updateMultiPitchRouteCallback(MultiPitchRoute route) { - var index = -1; - for (int i = 0; i < multiPitchRoutes.length; i++) { - if (multiPitchRoutes[i].id == route.id) { - index = i; - } - } - multiPitchRoutes.removeAt(index); - multiPitchRoutes.add(route); - setState(() {}); - } - - deleteMultiPitchRouteCallback(MultiPitchRoute route) { - multiPitchRoutes.remove(route); - setState(() {}); - } + updateMultiPitchRouteCallback(MultiPitchRoute route) { + var index = -1; + for (int i = 0; i < multiPitchRoutes.length; i++) { + if (multiPitchRoutes[i].id == route.id) { + index = i; + } + } + multiPitchRoutes.removeAt(index); + multiPitchRoutes.add(route); + setState(() {}); + } - List elements = []; + deleteMultiPitchRouteCallback(MultiPitchRoute route) { + multiPitchRoutes.remove(route); + setState(() {}); + } - if (multiPitchRoutes.isNotEmpty){ - elements.add( - FixedTimeline.tileBuilder( - theme: TimelineThemeData( - nodePosition: 0, - color: const Color(0xff989898), - indicatorTheme: const IndicatorThemeData( - position: 0, - size: 20.0, - ), - connectorTheme: const ConnectorThemeData( - thickness: 2.5, - ), - ), - builder: TimelineTileBuilder.connected( - connectionDirection: ConnectionDirection.before, - itemCount: multiPitchRoutes.length, - contentsBuilder: (_, index) { - List elements = []; - // route info - MultiPitchRoute multiPitchRoute = multiPitchRoutes[index]; - elements.add(RouteInfo(route: multiPitchRoute)); - // rating as hearts in a row - elements.add(RatingRow(rating: multiPitchRoute.rating)); - // images list view - if (multiPitchRoute.mediaIds.isNotEmpty) { - elements.add( - ImageListView(mediaIds: multiPitchRoute.mediaIds) - ); - } - // pitches - if (multiPitchRoute.pitchIds.isNotEmpty) { - // multi pitch - elements.add( - MultiPitchInfo( - pitchIds: multiPitchRoute.pitchIds - ) - ); - elements.add( - PitchTimeline( - trip: widget.trip, - spot: widget.spot, - route: multiPitchRoute, - pitchIds: multiPitchRoute.pitchIds - ) - ); - } - return InkWell( - onTap: () => showDialog( - context: context, - builder: (BuildContext context) => - Dialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - child: MultiPitchRouteDetails( - spot: widget.spot, - route: multiPitchRoute, - onDelete: deleteMultiPitchRouteCallback, - onUpdate: updateMultiPitchRouteCallback, - spotId: widget.spot.id - ) - ) - ), - child: Ink( - child: Padding( - padding: const EdgeInsets.only(left: 8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: elements, + if (multiPitchRoutes.isNotEmpty){ + elements.add( + FixedTimeline.tileBuilder( + theme: TimelineThemeData( + nodePosition: 0, + color: const Color(0xff989898), + indicatorTheme: const IndicatorThemeData( + position: 0, + size: 20.0, + ), + connectorTheme: const ConnectorThemeData( + thickness: 2.5, + ), + ), + builder: TimelineTileBuilder.connected( + connectionDirection: ConnectionDirection.before, + itemCount: multiPitchRoutes.length, + contentsBuilder: (_, index) { + List elements = []; + // route info + MultiPitchRoute multiPitchRoute = multiPitchRoutes[index]; + elements.add(RouteInfo(route: multiPitchRoute)); + // rating as hearts in a row + elements.add(RatingRow(rating: multiPitchRoute.rating)); + // images list view + if (multiPitchRoute.mediaIds.isNotEmpty) { + elements.add( + ImageListView(mediaIds: multiPitchRoute.mediaIds) + ); + } + // pitches + if (multiPitchRoute.pitchIds.isNotEmpty) { + // multi pitch + elements.add( + MultiPitchInfo( + pitchIds: multiPitchRoute.pitchIds + ) + ); + elements.add( + PitchTimeline( + trip: widget.trip, + spot: widget.spot, + route: multiPitchRoute, + pitchIds: multiPitchRoute.pitchIds + ) + ); + } + return InkWell( + onTap: () => showDialog( + context: context, + builder: (BuildContext context) => + Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), ), - ) + child: MultiPitchRouteDetails( + spot: widget.spot, + route: multiPitchRoute, + onDelete: deleteMultiPitchRouteCallback, + onUpdate: updateMultiPitchRouteCallback, + spotId: widget.spot.id + ) + ) + ), + child: Ink( + child: Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: elements, ), - ); - }, - indicatorBuilder: (_, index) { - return const OutlinedDotIndicator( - borderWidth: 2.5, - color: Color(0xff66c97f), - ); - }, - connectorBuilder: (_, index, ___) => - const SolidLineConnector(color: Color(0xff66c97f)), + ) ), - ), - ); - } + ); + }, + indicatorBuilder: (_, index) { + return const OutlinedDotIndicator( + borderWidth: 2.5, + color: Color(0xff66c97f), + ); + }, + connectorBuilder: (_, index, ___) => + const SolidLineConnector(color: Color(0xff66c97f)), + ), + ), + ); + } + } + return FutureBuilder>( + future: Future.wait(singlePitchRouteIds.map((routeId) => routeService.getSinglePitchRoute(routeId))), + builder: (context, snapshot) { + if (snapshot.hasData) { + List singlePitchRoutes = snapshot.data!.whereType().toList(); if (singlePitchRoutes.isNotEmpty){ elements.add( - FixedTimeline.tileBuilder( - theme: TimelineThemeData( - nodePosition: 0, - color: const Color(0xff989898), - indicatorTheme: const IndicatorThemeData( - position: 0, - size: 20.0, + FixedTimeline.tileBuilder( + theme: TimelineThemeData( + nodePosition: 0, + color: const Color(0xff989898), + indicatorTheme: const IndicatorThemeData( + position: 0, + size: 20.0, + ), + connectorTheme: const ConnectorThemeData( + thickness: 2.5, + ), ), - connectorTheme: const ConnectorThemeData( - thickness: 2.5, - ), - ), - builder: TimelineTileBuilder.connected( - connectionDirection: ConnectionDirection.before, - itemCount: singlePitchRoutes.length, - contentsBuilder: (_, index) { - List elements = []; - // route info - SinglePitchRoute singlePitchRoute = singlePitchRoutes[index]; - elements.add(SinglePitchRouteInfo( - spot: widget.spot, - route: singlePitchRoute - )); - // rating as hearts in a row - elements.add(RatingRow(rating: singlePitchRoute.rating)); - // images list view - if (singlePitchRoute.mediaIds.isNotEmpty) { - elements.add( - ImageListView(mediaIds: singlePitchRoute.mediaIds) - ); - } + builder: TimelineTileBuilder.connected( + connectionDirection: ConnectionDirection.before, + itemCount: singlePitchRoutes.length, + contentsBuilder: (_, index) { + List elements = []; + // route info + SinglePitchRoute singlePitchRoute = singlePitchRoutes[index]; + elements.add(SinglePitchRouteInfo( + spot: widget.spot, + route: singlePitchRoute + )); + // rating as hearts in a row + elements.add(RatingRow(rating: singlePitchRoute.rating)); + // images list view + if (singlePitchRoute.mediaIds.isNotEmpty) { + elements.add( + ImageListView(mediaIds: singlePitchRoute.mediaIds) + ); + } - return InkWell( - onTap: () => showDialog( - context: context, - builder: (BuildContext context) => - Dialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - child: SinglePitchRouteDetails( - spot: widget.spot, - route: singlePitchRoute, - onDelete: (SinglePitchRoute sPR) => {}, - onUpdate: (SinglePitchRoute sPR) => {}, - spotId: widget.spot.id) - ) - ), - child: Ink( - child: Padding( - padding: const EdgeInsets.only(left: 8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: elements, - ), - ) - ), - ); - }, - indicatorBuilder: (_, index) { - return const OutlinedDotIndicator( - borderWidth: 2.5, - color: Color(0xff66c97f), - ); - }, - connectorBuilder: (_, index, ___) => - const SolidLineConnector(color: Color(0xff66c97f)), - ), - ) + return InkWell( + onTap: () => showDialog( + context: context, + builder: (BuildContext context) => + Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + child: SinglePitchRouteDetails( + spot: widget.spot, + route: singlePitchRoute, + onDelete: (SinglePitchRoute sPR) => {}, + onUpdate: (SinglePitchRoute sPR) => {}, + spotId: widget.spot.id) + ) + ), + child: Ink( + child: Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: elements, + ), + ) + ), + ); + }, + indicatorBuilder: (_, index) { + return const OutlinedDotIndicator( + borderWidth: 2.5, + color: Color(0xff66c97f), + ); + }, + connectorBuilder: (_, index, ___) => + const SolidLineConnector(color: Color(0xff66c97f)), + ), + ) ); } return Column( @@ -248,10 +247,7 @@ class RouteListState extends State { return const CircularProgressIndicator(); } } - ); - } else { - return const CircularProgressIndicator(); - } + ); } ); } else { From 9f3435ca52594973672298493e878dfcf1665bd2 Mon Sep 17 00:00:00 2001 From: florianpix Date: Thu, 29 Jun 2023 18:39:16 +0200 Subject: [PATCH 04/13] removed unused select widgets; --- .../detail/multi_pitch_route_details.dart | 18 ----- .../lib/components/detail/pitch_details.dart | 16 ----- .../lib/components/detail/route_details.dart | 17 ----- .../detail/single_pitch_route_details.dart | 5 -- .../lib/components/detail/spot_details.dart | 1 - .../components/diary_page/spot_details.dart | 2 - .../lib/components/select/select_ascent.dart | 65 ------------------ .../select_ascent_of_single_pitch_route.dart | 67 ------------------- .../lib/components/select/select_pitch.dart | 67 ------------------- .../lib/components/select/select_route.dart | 65 ------------------ 10 files changed, 323 deletions(-) delete mode 100644 src/flutter_app/lib/components/select/select_ascent.dart delete mode 100644 src/flutter_app/lib/components/select/select_ascent_of_single_pitch_route.dart delete mode 100644 src/flutter_app/lib/components/select/select_pitch.dart delete mode 100644 src/flutter_app/lib/components/select/select_route.dart diff --git a/src/flutter_app/lib/components/detail/multi_pitch_route_details.dart b/src/flutter_app/lib/components/detail/multi_pitch_route_details.dart index 247026c..af78f63 100644 --- a/src/flutter_app/lib/components/detail/multi_pitch_route_details.dart +++ b/src/flutter_app/lib/components/detail/multi_pitch_route_details.dart @@ -3,7 +3,6 @@ import 'package:image_picker/image_picker.dart'; import 'package:skeletons/skeletons.dart'; import '../../interfaces/multi_pitch_route/multi_pitch_route.dart'; -import '../../interfaces/route/route.dart'; import '../../interfaces/spot/spot.dart'; import '../../interfaces/trip/trip.dart'; import '../../services/media_service.dart'; @@ -13,9 +12,7 @@ import '../MyButtonStyles.dart'; import '../add/add_pitch.dart'; import '../diary_page/timeline/pitch_timeline.dart'; import '../edit/edit_multi_pitch_route.dart'; -import '../edit/edit_route.dart'; import '../info/multi_pitch_route_info.dart'; -import '../select/select_pitch.dart'; class MultiPitchRouteDetails extends StatefulWidget { const MultiPitchRouteDetails({super.key, this.trip, required this.spot, required this.route, required this.onDelete, required this.onUpdate, required this.spotId }); @@ -283,21 +280,6 @@ class _MultiPitchRouteDetailsState extends State{ style: MyButtonStyles.rounded ), ); - elements.add( - ElevatedButton.icon( - icon: const Icon(Icons.add, size: 30.0, color: Colors.pink), - label: const Text('Add existing pitch'), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => SelectPitch(route: widget.route), - ) - ); - }, - style: MyButtonStyles.rounded - ), - ); elements.add( Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/src/flutter_app/lib/components/detail/pitch_details.dart b/src/flutter_app/lib/components/detail/pitch_details.dart index e41c2e5..9e8825f 100644 --- a/src/flutter_app/lib/components/detail/pitch_details.dart +++ b/src/flutter_app/lib/components/detail/pitch_details.dart @@ -13,7 +13,6 @@ import '../add/add_ascent.dart'; import '../diary_page/timeline/ascent_timeline.dart'; import '../edit/edit_pitch.dart'; import '../info/pitch_info.dart'; -import '../select/select_ascent.dart'; class PitchDetails extends StatefulWidget { const PitchDetails({super.key, this.trip, required this.spot, required this.route, required this.pitch, required this.onDelete, required this.onUpdate }); @@ -267,21 +266,6 @@ class _PitchDetailsState extends State{ style: MyButtonStyles.rounded ), ); - elements.add( - ElevatedButton.icon( - icon: const Icon(Icons.add, size: 30.0, color: Colors.pink), - label: const Text('Add existing ascent'), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => SelectAscent(pitch: widget.pitch), - ) - ); - }, - style: MyButtonStyles.rounded - ), - ); // delete, edit, close elements.add( Row( diff --git a/src/flutter_app/lib/components/detail/route_details.dart b/src/flutter_app/lib/components/detail/route_details.dart index 3a4c76f..a2df26c 100644 --- a/src/flutter_app/lib/components/detail/route_details.dart +++ b/src/flutter_app/lib/components/detail/route_details.dart @@ -10,9 +10,7 @@ import '../../services/pitch_service.dart'; import '../../services/route_service.dart'; import '../MyButtonStyles.dart'; import '../add/add_pitch.dart'; -import '../diary_page/timeline/pitch_timeline.dart'; import '../edit/edit_route.dart'; -import '../select/select_pitch.dart'; class RouteDetails extends StatefulWidget { const RouteDetails({super.key, this.trip, required this.spot, required this.route, required this.onDelete, required this.onUpdate, required this.spotId }); @@ -275,21 +273,6 @@ class _RouteDetailsState extends State{ style: MyButtonStyles.rounded ), ); - elements.add( - ElevatedButton.icon( - icon: const Icon(Icons.add, size: 30.0, color: Colors.pink), - label: const Text('Add existing pitch'), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => SelectPitch(route: widget.route), - ) - ); - }, - style: MyButtonStyles.rounded - ), - ); elements.add( Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/src/flutter_app/lib/components/detail/single_pitch_route_details.dart b/src/flutter_app/lib/components/detail/single_pitch_route_details.dart index e599260..43cc49a 100644 --- a/src/flutter_app/lib/components/detail/single_pitch_route_details.dart +++ b/src/flutter_app/lib/components/detail/single_pitch_route_details.dart @@ -1,5 +1,4 @@ import 'package:climbing_diary/components/add/add_ascent_to_single_pitch_route.dart'; -import 'package:climbing_diary/components/select/select_ascent_of_single_pitch_route.dart'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:skeletons/skeletons.dart'; @@ -11,13 +10,9 @@ import '../../services/media_service.dart'; import '../../services/pitch_service.dart'; import '../../services/route_service.dart'; import '../MyButtonStyles.dart'; -import '../add/add_ascent.dart'; -import '../add/add_pitch.dart'; import '../diary_page/timeline/ascent_timeline.dart'; import '../edit/edit_single_pitch_route.dart'; import '../info/single_pitch_route_info.dart'; -import '../select/select_ascent.dart'; -import '../select/select_pitch.dart'; class SinglePitchRouteDetails extends StatefulWidget { const SinglePitchRouteDetails({super.key, this.trip, required this.spot, required this.route, required this.onDelete, required this.onUpdate, required this.spotId }); diff --git a/src/flutter_app/lib/components/detail/spot_details.dart b/src/flutter_app/lib/components/detail/spot_details.dart index 4a43f99..ddd2232 100644 --- a/src/flutter_app/lib/components/detail/spot_details.dart +++ b/src/flutter_app/lib/components/detail/spot_details.dart @@ -11,7 +11,6 @@ import '../MyButtonStyles.dart'; import '../add/add_route.dart'; import '../diary_page/timeline/route_timeline.dart'; import '../edit/edit_spot.dart'; -import '../select/select_route.dart'; class SpotDetails extends StatefulWidget { const SpotDetails({super.key, this.trip, required this.spot, required this.onDelete, required this.onUpdate }); diff --git a/src/flutter_app/lib/components/diary_page/spot_details.dart b/src/flutter_app/lib/components/diary_page/spot_details.dart index 03ca194..9629d3c 100644 --- a/src/flutter_app/lib/components/diary_page/spot_details.dart +++ b/src/flutter_app/lib/components/diary_page/spot_details.dart @@ -8,10 +8,8 @@ import '../../interfaces/trip/trip.dart'; import '../../services/media_service.dart'; import '../../services/spot_service.dart'; import '../MyButtonStyles.dart'; -import '../add/add_route.dart'; import '../diary_page/timeline/route_timeline.dart'; import '../edit/edit_spot.dart'; -import '../select/select_route.dart'; class SpotDetails extends StatefulWidget { const SpotDetails({super.key, this.trip, required this.spot, required this.onDelete, required this.onUpdate }); diff --git a/src/flutter_app/lib/components/select/select_ascent.dart b/src/flutter_app/lib/components/select/select_ascent.dart deleted file mode 100644 index 5b6d3d0..0000000 --- a/src/flutter_app/lib/components/select/select_ascent.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../interfaces/ascent/ascent.dart'; -import '../../interfaces/pitch/pitch.dart'; -import '../../services/ascent_service.dart'; -import '../../services/pitch_service.dart'; -import '../MyButtonStyles.dart'; - -class SelectAscent extends StatefulWidget { - const SelectAscent({super.key, required this.pitch}); - - final Pitch pitch; - - @override - State createState() => _SelectAscentState(); -} - -class _SelectAscentState extends State{ - final GlobalKey _formKey = GlobalKey(); - final AscentService ascentService = AscentService(); - final PitchService pitchService = PitchService(); - - @override - void initState(){ - super.initState(); - } - - @override - Widget build(BuildContext context) { - return AlertDialog( - shape: - RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), - title: const Text('Please choose a ascent'), - content: FutureBuilder>( - future: ascentService.getAscents(), - builder: (context, snapshot) { - if (snapshot.hasData) { - List ascents = snapshot.data!; - List elements = []; - - for (int i = 0; i < ascents.length; i++){ - elements.add(ElevatedButton.icon( - icon: const Icon(Icons.arrow_forward, size: 30.0, color: Colors.pink), - label: Text(ascents[i].date), - onPressed: () { - if (!widget.pitch.ascentIds.contains(ascents[i].id)){ - widget.pitch.ascentIds.add(ascents[i].id); - pitchService.editPitch(widget.pitch.toUpdatePitch()); - } - Navigator.of(context).pop(); - }, - style: MyButtonStyles.rounded - )); - } - return Column( - children: elements, - ); - } else { - return const CircularProgressIndicator(); - } - }, - ), - ); - } -} \ No newline at end of file diff --git a/src/flutter_app/lib/components/select/select_ascent_of_single_pitch_route.dart b/src/flutter_app/lib/components/select/select_ascent_of_single_pitch_route.dart deleted file mode 100644 index 7b970ce..0000000 --- a/src/flutter_app/lib/components/select/select_ascent_of_single_pitch_route.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:climbing_diary/interfaces/single_pitch_route/single_pitch_route.dart'; -import 'package:flutter/material.dart'; - -import '../../interfaces/ascent/ascent.dart'; -import '../../interfaces/pitch/pitch.dart'; -import '../../services/ascent_service.dart'; -import '../../services/pitch_service.dart'; -import '../../services/route_service.dart'; -import '../MyButtonStyles.dart'; - -class SelectAscentOfSinglePitchRoute extends StatefulWidget { - const SelectAscentOfSinglePitchRoute({super.key, required this.singlePitchRoute}); - - final SinglePitchRoute singlePitchRoute; - - @override - State createState() => _SelectAscentOfSinglePitchRouteState(); -} - -class _SelectAscentOfSinglePitchRouteState extends State{ - final GlobalKey _formKey = GlobalKey(); - final AscentService ascentService = AscentService(); - final RouteService routeService = RouteService(); - - @override - void initState(){ - super.initState(); - } - - @override - Widget build(BuildContext context) { - return AlertDialog( - shape: - RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), - title: const Text('Please choose a ascent'), - content: FutureBuilder>( - future: ascentService.getAscents(), - builder: (context, snapshot) { - if (snapshot.hasData) { - List ascents = snapshot.data!; - List elements = []; - - for (int i = 0; i < ascents.length; i++){ - elements.add(ElevatedButton.icon( - icon: const Icon(Icons.arrow_forward, size: 30.0, color: Colors.pink), - label: Text(ascents[i].date), - onPressed: () { - if (!widget.singlePitchRoute.ascentIds.contains(ascents[i].id)){ - widget.singlePitchRoute.ascentIds.add(ascents[i].id); - routeService.editSinglePitchRoute(widget.singlePitchRoute.toUpdateSinglePitchRoute()); - } - Navigator.of(context).pop(); - }, - style: MyButtonStyles.rounded - )); - } - return Column( - children: elements, - ); - } else { - return const CircularProgressIndicator(); - } - }, - ), - ); - } -} \ No newline at end of file diff --git a/src/flutter_app/lib/components/select/select_pitch.dart b/src/flutter_app/lib/components/select/select_pitch.dart deleted file mode 100644 index cdc5947..0000000 --- a/src/flutter_app/lib/components/select/select_pitch.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../interfaces/pitch/pitch.dart'; -import '../../interfaces/route/route.dart'; -import '../../services/pitch_service.dart'; -import '../../services/route_service.dart'; - -import '../MyButtonStyles.dart'; - -class SelectPitch extends StatefulWidget { - const SelectPitch({super.key, required this.route}); - - final ClimbingRoute route; - @override - State createState() => _SelectPitchState(); -} - -class _SelectPitchState extends State{ - final GlobalKey _formKey = GlobalKey(); - final PitchService pitchService = PitchService(); - final RouteService routeService = RouteService(); - - @override - void initState(){ - super.initState(); - } - - @override - Widget build(BuildContext context) { - return AlertDialog( - shape: - RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), - title: const Text('Please choose a pitch'), - content: FutureBuilder>( - future: pitchService.getPitches(), - builder: (context, snapshot) { - if (snapshot.hasData) { - List pitches = snapshot.data!; - List elements = []; - - for (int i = 0; i < pitches.length; i++){ - elements.add(ElevatedButton.icon( - icon: const Icon(Icons.arrow_forward, size: 30.0, color: Colors.pink), - label: Text(pitches[i].name), - onPressed: () { - /* - if (!widget.route.pitchIds.contains(pitches[i].id)){ - widget.route.pitchIds.add(pitches[i].id); - routeService.editRoute(widget.route.toUpdateClimbingRoute()); - } - */ - Navigator.of(context).pop(); - }, - style: MyButtonStyles.rounded - )); - } - return Column( - children: elements, - ); - } else { - return const CircularProgressIndicator(); - } - }, - ), - ); - } -} \ No newline at end of file diff --git a/src/flutter_app/lib/components/select/select_route.dart b/src/flutter_app/lib/components/select/select_route.dart deleted file mode 100644 index bc5bcf5..0000000 --- a/src/flutter_app/lib/components/select/select_route.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../interfaces/route/route.dart'; -import '../../interfaces/spot/spot.dart'; -import '../../services/route_service.dart'; -import '../../services/spot_service.dart'; - -import '../MyButtonStyles.dart'; - -class SelectRoute extends StatefulWidget { - const SelectRoute({super.key, required this.spot}); - - final Spot spot; - @override - State createState() => _SelectRouteState(); -} - -class _SelectRouteState extends State{ - final GlobalKey _formKey = GlobalKey(); - final RouteService routeService = RouteService(); - final SpotService tripService = SpotService(); - - @override - void initState(){ - super.initState(); - } - - @override - Widget build(BuildContext context) { - return AlertDialog( - shape: - RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), - title: const Text('Please choose a route'), - content: FutureBuilder>( - future: routeService.getRoutes(), - builder: (context, snapshot) { - if (snapshot.hasData) { - List routes = snapshot.data!; - List elements = []; - - for (int i = 0; i < routes.length; i++){ - elements.add(ElevatedButton.icon( - icon: const Icon(Icons.arrow_forward, size: 30.0, color: Colors.pink), - label: Text(routes[i].name), - onPressed: () { - if (!widget.spot.multiPitchRouteIds.contains(routes[i].id)){ - widget.spot.multiPitchRouteIds.add(routes[i].id); - tripService.editSpot(widget.spot.toUpdateSpot()); - } - Navigator.of(context).pop(); - }, - style: MyButtonStyles.rounded - )); - } - return Column( - children: elements, - ); - } else { - return const CircularProgressIndicator(); - } - }, - ), - ); - } -} \ No newline at end of file From 3cc76ef61ddeeb1e0bc9aa248d1acd5172d1dd3b Mon Sep 17 00:00:00 2001 From: florianpix Date: Thu, 29 Jun 2023 18:58:10 +0200 Subject: [PATCH 05/13] removed old API endpoint service (route); --- .../detail/multi_pitch_route_details.dart | 4 +- .../lib/components/detail/route_details.dart | 15 +- .../detail/single_pitch_route_details.dart | 4 +- .../diary_page/timeline/spot_timeline.dart | 1 + .../lib/components/edit/edit_route.dart | 8 +- .../lib/components/map_page/route_list.dart | 186 +++++++++--------- .../lib/services/route_service.dart | 147 -------------- 7 files changed, 114 insertions(+), 251 deletions(-) diff --git a/src/flutter_app/lib/components/detail/multi_pitch_route_details.dart b/src/flutter_app/lib/components/detail/multi_pitch_route_details.dart index af78f63..fe1eeed 100644 --- a/src/flutter_app/lib/components/detail/multi_pitch_route_details.dart +++ b/src/flutter_app/lib/components/detail/multi_pitch_route_details.dart @@ -50,7 +50,7 @@ class _MultiPitchRouteDetailsState extends State{ var mediaId = await mediaService.uploadMedia(img); MultiPitchRoute route = widget.route; route.mediaIds.add(mediaId); - routeService.editRoute(route.toUpdateMultiPitchRoute()); + routeService.editMultiPitchRoute(route.toUpdateMultiPitchRoute()); } setState(() { @@ -288,7 +288,7 @@ class _MultiPitchRouteDetailsState extends State{ IconButton( onPressed: () { Navigator.pop(context); - routeService.deleteRoute(route, widget.spotId); + routeService.deleteMultiPitchRoute(route, widget.spotId); widget.onDelete.call(route); }, icon: const Icon(Icons.delete), diff --git a/src/flutter_app/lib/components/detail/route_details.dart b/src/flutter_app/lib/components/detail/route_details.dart index a2df26c..8bb565c 100644 --- a/src/flutter_app/lib/components/detail/route_details.dart +++ b/src/flutter_app/lib/components/detail/route_details.dart @@ -1,3 +1,4 @@ +import 'package:climbing_diary/interfaces/multi_pitch_route/multi_pitch_route.dart'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:skeletons/skeletons.dart'; @@ -17,9 +18,9 @@ class RouteDetails extends StatefulWidget { final Trip? trip; final Spot spot; - final ClimbingRoute route; - final ValueSetter onDelete; - final ValueSetter onUpdate; + final MultiPitchRoute route; + final ValueSetter onDelete; + final ValueSetter onUpdate; final String spotId; @override @@ -46,9 +47,9 @@ class _RouteDetailsState extends State{ var img = await picker.pickImage(source: media); if (img != null){ var mediaId = await mediaService.uploadMedia(img); - ClimbingRoute route = widget.route; + MultiPitchRoute route = widget.route; route.mediaIds.add(mediaId); - routeService.editRoute(route.toUpdateClimbingRoute()); + routeService.editMultiPitchRoute(route.toUpdateMultiPitchRoute()); } setState(() { @@ -117,7 +118,7 @@ class _RouteDetailsState extends State{ @override Widget build(BuildContext context) { List elements = []; - ClimbingRoute route = widget.route; + MultiPitchRoute route = widget.route; // general info elements.addAll([ @@ -281,7 +282,7 @@ class _RouteDetailsState extends State{ IconButton( onPressed: () { Navigator.pop(context); - routeService.deleteRoute(route, widget.spotId); + routeService.deleteMultiPitchRoute(route, widget.spotId); widget.onDelete.call(route); }, icon: const Icon(Icons.delete), diff --git a/src/flutter_app/lib/components/detail/single_pitch_route_details.dart b/src/flutter_app/lib/components/detail/single_pitch_route_details.dart index 43cc49a..66c53c6 100644 --- a/src/flutter_app/lib/components/detail/single_pitch_route_details.dart +++ b/src/flutter_app/lib/components/detail/single_pitch_route_details.dart @@ -50,7 +50,7 @@ class _SinglePitchRouteDetailsState extends State{ var mediaId = await mediaService.uploadMedia(img); SinglePitchRoute route = widget.route; route.mediaIds.add(mediaId); - routeService.editRoute(route.toUpdateSinglePitchRoute()); + routeService.editSinglePitchRoute(route.toUpdateSinglePitchRoute()); } setState(() { @@ -288,7 +288,7 @@ class _SinglePitchRouteDetailsState extends State{ IconButton( onPressed: () { Navigator.pop(context); - routeService.deleteRoute(route, widget.spotId); + routeService.deleteSinglePitchRoute(route, widget.spotId); widget.onDelete.call(route); }, icon: const Icon(Icons.delete), diff --git a/src/flutter_app/lib/components/diary_page/timeline/spot_timeline.dart b/src/flutter_app/lib/components/diary_page/timeline/spot_timeline.dart index 8ccddb4..b76730c 100644 --- a/src/flutter_app/lib/components/diary_page/timeline/spot_timeline.dart +++ b/src/flutter_app/lib/components/diary_page/timeline/spot_timeline.dart @@ -50,6 +50,7 @@ class SpotTimelineState extends State { if (snapshot.hasData) { List spots = snapshot.data!.whereType().toList(); spots.sort((a, b) => a.name.compareTo(b.name)); + updateSpotCallback(Spot spot) { var index = -1; for (int i = 0; i < spots.length; i++) { diff --git a/src/flutter_app/lib/components/edit/edit_route.dart b/src/flutter_app/lib/components/edit/edit_route.dart index a362350..9f76bf3 100644 --- a/src/flutter_app/lib/components/edit/edit_route.dart +++ b/src/flutter_app/lib/components/edit/edit_route.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import '../../interfaces/multi_pitch_route/multi_pitch_route.dart'; import '../../interfaces/route/route.dart'; import '../../interfaces/route/update_route.dart'; import '../../services/route_service.dart'; @@ -9,8 +10,8 @@ import 'package:internet_connection_checker/internet_connection_checker.dart'; class EditRoute extends StatefulWidget { const EditRoute({super.key, required this.route, required this.onUpdate}); - final ClimbingRoute route; - final ValueSetter onUpdate; + final MultiPitchRoute route; + final ValueSetter onUpdate; @override State createState() => _EditRouteState(); @@ -110,10 +111,13 @@ class _EditRouteState extends State{ ); Navigator.of(context).pop(); Navigator.of(context).pop(); + /* + TODO single/multi ? ClimbingRoute? updatedRoute = await routeService.editRoute(route); if (updatedRoute != null) { widget.onUpdate.call(updatedRoute); } + */ } }, icon: const Icon(Icons.save) diff --git a/src/flutter_app/lib/components/map_page/route_list.dart b/src/flutter_app/lib/components/map_page/route_list.dart index 9659de9..10177e3 100644 --- a/src/flutter_app/lib/components/map_page/route_list.dart +++ b/src/flutter_app/lib/components/map_page/route_list.dart @@ -54,6 +54,8 @@ class RouteListState extends State { builder: (context, snapshot) { if (snapshot.hasData) { List multiPitchRoutes = snapshot.data!.whereType().toList(); + multiPitchRoutes.sort((a, b) => a.name.compareTo(b.name)); + updateMultiPitchRouteCallback(MultiPitchRoute route) { var index = -1; for (int i = 0; i < multiPitchRoutes.length; i++) { @@ -70,7 +72,6 @@ class RouteListState extends State { multiPitchRoutes.remove(route); setState(() {}); } - if (multiPitchRoutes.isNotEmpty){ elements.add( FixedTimeline.tileBuilder( @@ -89,27 +90,27 @@ class RouteListState extends State { connectionDirection: ConnectionDirection.before, itemCount: multiPitchRoutes.length, contentsBuilder: (_, index) { - List elements = []; + List timeLineElements = []; // route info MultiPitchRoute multiPitchRoute = multiPitchRoutes[index]; - elements.add(RouteInfo(route: multiPitchRoute)); + timeLineElements.add(RouteInfo(route: multiPitchRoute)); // rating as hearts in a row - elements.add(RatingRow(rating: multiPitchRoute.rating)); + timeLineElements.add(RatingRow(rating: multiPitchRoute.rating)); // images list view if (multiPitchRoute.mediaIds.isNotEmpty) { - elements.add( + timeLineElements.add( ImageListView(mediaIds: multiPitchRoute.mediaIds) ); } // pitches if (multiPitchRoute.pitchIds.isNotEmpty) { // multi pitch - elements.add( + timeLineElements.add( MultiPitchInfo( pitchIds: multiPitchRoute.pitchIds ) ); - elements.add( + timeLineElements.add( PitchTimeline( trip: widget.trip, spot: widget.spot, @@ -141,7 +142,7 @@ class RouteListState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, - children: elements, + children: timeLineElements, ), ) ), @@ -159,95 +160,98 @@ class RouteListState extends State { ), ); } - } - return FutureBuilder>( - future: Future.wait(singlePitchRouteIds.map((routeId) => routeService.getSinglePitchRoute(routeId))), - builder: (context, snapshot) { - if (snapshot.hasData) { - List singlePitchRoutes = snapshot.data!.whereType().toList(); + return FutureBuilder>( + future: Future.wait(singlePitchRouteIds.map((routeId) => routeService.getSinglePitchRoute(routeId))), + builder: (context, snapshot) { + if (snapshot.hasData) { + List singlePitchRoutes = snapshot.data!.whereType().toList(); + singlePitchRoutes.sort((a, b) => a.name.compareTo(b.name)); - if (singlePitchRoutes.isNotEmpty){ - elements.add( - FixedTimeline.tileBuilder( - theme: TimelineThemeData( - nodePosition: 0, - color: const Color(0xff989898), - indicatorTheme: const IndicatorThemeData( - position: 0, - size: 20.0, - ), - connectorTheme: const ConnectorThemeData( - thickness: 2.5, + if (singlePitchRoutes.isNotEmpty){ + elements.add( + FixedTimeline.tileBuilder( + theme: TimelineThemeData( + nodePosition: 0, + color: const Color(0xff989898), + indicatorTheme: const IndicatorThemeData( + position: 0, + size: 20.0, + ), + connectorTheme: const ConnectorThemeData( + thickness: 2.5, + ), ), - ), - builder: TimelineTileBuilder.connected( - connectionDirection: ConnectionDirection.before, - itemCount: singlePitchRoutes.length, - contentsBuilder: (_, index) { - List elements = []; - // route info - SinglePitchRoute singlePitchRoute = singlePitchRoutes[index]; - elements.add(SinglePitchRouteInfo( - spot: widget.spot, - route: singlePitchRoute - )); - // rating as hearts in a row - elements.add(RatingRow(rating: singlePitchRoute.rating)); - // images list view - if (singlePitchRoute.mediaIds.isNotEmpty) { - elements.add( - ImageListView(mediaIds: singlePitchRoute.mediaIds) - ); - } + builder: TimelineTileBuilder.connected( + connectionDirection: ConnectionDirection.before, + itemCount: singlePitchRoutes.length, + contentsBuilder: (_, index) { + List elements = []; + // route info + SinglePitchRoute singlePitchRoute = singlePitchRoutes[index]; + elements.add(SinglePitchRouteInfo( + spot: widget.spot, + route: singlePitchRoute + )); + // rating as hearts in a row + elements.add(RatingRow(rating: singlePitchRoute.rating)); + // images list view + if (singlePitchRoute.mediaIds.isNotEmpty) { + elements.add( + ImageListView(mediaIds: singlePitchRoute.mediaIds) + ); + } - return InkWell( - onTap: () => showDialog( - context: context, - builder: (BuildContext context) => - Dialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - child: SinglePitchRouteDetails( - spot: widget.spot, - route: singlePitchRoute, - onDelete: (SinglePitchRoute sPR) => {}, - onUpdate: (SinglePitchRoute sPR) => {}, - spotId: widget.spot.id) - ) - ), - child: Ink( - child: Padding( - padding: const EdgeInsets.only(left: 8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: elements, - ), - ) - ), - ); - }, - indicatorBuilder: (_, index) { - return const OutlinedDotIndicator( - borderWidth: 2.5, - color: Color(0xff66c97f), - ); - }, - connectorBuilder: (_, index, ___) => - const SolidLineConnector(color: Color(0xff66c97f)), - ), - ) + return InkWell( + onTap: () => showDialog( + context: context, + builder: (BuildContext context) => + Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + child: SinglePitchRouteDetails( + spot: widget.spot, + route: singlePitchRoute, + onDelete: (SinglePitchRoute sPR) => {}, + onUpdate: (SinglePitchRoute sPR) => {}, + spotId: widget.spot.id) + ) + ), + child: Ink( + child: Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: elements, + ), + ) + ), + ); + }, + indicatorBuilder: (_, index) { + return const OutlinedDotIndicator( + borderWidth: 2.5, + color: Color(0xff66c97f), + ); + }, + connectorBuilder: (_, index, ___) => + const SolidLineConnector(color: Color(0xff66c97f)), + ), + ) + ); + } + return Column( + children: elements, ); + } else { + return const CircularProgressIndicator(); } - return Column( - children: elements, - ); - } else { - return const CircularProgressIndicator(); } - } - ); + ); + } else { + return const CircularProgressIndicator(); + } } ); } else { diff --git a/src/flutter_app/lib/services/route_service.dart b/src/flutter_app/lib/services/route_service.dart index 3ed7dd3..5d69ed0 100644 --- a/src/flutter_app/lib/services/route_service.dart +++ b/src/flutter_app/lib/services/route_service.dart @@ -27,153 +27,6 @@ class RouteService { final String climbingApiHost = Environment().config.climbingApiHost; final String mediaApiHost = Environment().config.mediaApiHost; - Future> getRoutes() async { - try { - final Response response = await netWorkLocator.dio.get('$climbingApiHost/route'); - - if (response.statusCode == 200) { - // If the server did return a 200 OK response, then parse the JSON. - List routes = []; - // save to cache - Box box = Hive.box('routes'); - response.data.forEach((s) { - ClimbingRoute route = ClimbingRoute.fromJson(s); - if (!box.containsKey(route.id)) { - box.put(route.id, route.toJson()); - } - routes.add(route); - }); - return routes; - } - } catch (e) { - if (e is DioError) { - if (e.error.toString().contains("OS Error: Connection refused, errno = 111")){ - showSimpleNotification( - const Text('Couldn\'t connect to API'), - background: Colors.red, - ); - } - } else { - print(e); - } - } - return []; - } - - Future getRoute(String routeId) async { - try{ - return getMultiPitchRoute(routeId); - } catch(e) { - return getSinglePitchRoute(routeId); - } - } - - Future createRoute(CreateClimbingRoute createRoute, String spotId, bool hasConnection) async { - CreateClimbingRoute route = CreateClimbingRoute( - comment: (createRoute.comment != null) ? createRoute.comment! : "", - location: createRoute.location, - name: createRoute.name, - rating: createRoute.rating, - ); - if (hasConnection) { - var data = route.toJson(); - return uploadRoute(spotId, data); - } else { - // save to cache - Box box = Hive.box('upload_later_routes'); - Map routeJson = route.toJson(); - box.put(routeJson.hashCode, routeJson); - } - return null; - } - - Future editRoute(UpdateClimbingRoute route) async { - try { - final Response response = await netWorkLocator.dio - .put('$climbingApiHost/route/${route.id}', data: route.toJson()); - if (response.statusCode == 200) { - // TODO deleteRouteFromEditQueue(route.hashCode); - return ClimbingRoute.fromJson(response.data); - } else { - throw Exception('Failed to edit route'); - } - } catch (e) { - if (e is DioError) { - if (e.error.toString().contains('OS Error: No address associated with hostname, errno = 7')){ - // this means we are offline so queue this route and edit later - Box box = Hive.box('edit_later_routes'); - Map routeJson = route.toJson(); - box.put(routeJson.hashCode, routeJson); - } - } - } finally { - // TODO editRouteFromCache(route); - } - return null; - } - - Future deleteRoute(ClimbingRoute route, String spotId) async { - try { - for (var id in route.mediaIds) { - final Response mediaResponse = - await netWorkLocator.dio.delete('$mediaApiHost/media/$id'); - if (mediaResponse.statusCode != 204) { - throw Exception('Failed to delete medium'); - } - } - - final Response routeResponse = - await netWorkLocator.dio.delete('$climbingApiHost/route/${route.id}/spot/$spotId'); - if (routeResponse.statusCode != 204) { - throw Exception('Failed to delete route'); - } - // TODO deleteRouteFromDeleteQueue(route.toJson().hashCode); - return routeResponse.data; - } catch (e) { - if (e is DioError) { - if (e.error.toString().contains('OS Error: No address associated with hostname, errno = 7')){ - // this means we are offline so queue this route and delete later - Box box = Hive.box('delete_later_routes'); - Map routeJson = route.toJson(); - box.put(routeJson.hashCode, routeJson); - } - } - } finally { - // TODO deleteRouteFromCache(route.id); - } - } - - Future uploadRoute(String spotId, Map data) async { - try { - final Response response = await netWorkLocator.dio - .post('$climbingApiHost/route/spot/$spotId', data: data); - if (response.statusCode == 201) { - return ClimbingRoute.fromJson(response.data); - } else { - throw Exception('Failed to create route'); - } - } catch (e) { - if (e is DioError) { - final response = e.response; - if (response != null) { - switch (response.statusCode) { - case 409: - showSimpleNotification( - const Text('This route already exists!'), - background: Colors.red, - ); - break; - default: - throw Exception('Failed to create route'); - } - } - } - } finally { - // TODO deleteRouteFromUploadQueue(data.hashCode); - } - return null; - } - Future> getMultiPitchRoutes() async { try { final Response response = await netWorkLocator.dio.get('$climbingApiHost/multi_pitch_route'); From 07b4a0089b48913c6bb9366ba760a231cfb32551 Mon Sep 17 00:00:00 2001 From: florianpix Date: Fri, 30 Jun 2023 10:40:32 +0200 Subject: [PATCH 06/13] refactoring; fixed edit single pitch route; --- .../lib/components/add/add_route.dart | 98 +++--- .../lib/components/add/add_spot.dart | 2 +- .../detail/multi_pitch_route_details.dart | 2 +- .../lib/components/detail/pitch_details.dart | 2 +- .../lib/components/detail/route_details.dart | 312 ------------------ .../detail/single_pitch_route_details.dart | 2 +- .../lib/components/detail/spot_details.dart | 10 +- .../lib/components/detail/trip_details.dart | 3 +- .../edit/edit_multi_pitch_route.dart | 8 +- .../lib/components/edit/edit_route.dart | 132 -------- .../edit/edit_single_pitch_route.dart | 74 +++-- .../lib/components/edit/edit_spot.dart | 3 +- src/flutter_app/lib/main.dart | 8 +- .../pages/{ => diary_page}/diary_page.dart | 2 +- .../diary_page/image_list_view.dart | 0 .../diary_page/rating_row.dart | 0 .../diary_page/spot_details.dart | 6 +- .../diary_page/timeline/ascent_timeline.dart | 7 +- .../diary_page/timeline/pitch_timeline.dart | 8 +- .../diary_page/timeline/route_timeline.dart | 16 +- .../diary_page/timeline/spot_timeline.dart | 8 +- .../diary_page/timeline/trip_timeline.dart | 12 +- .../list_page/list_page.dart | 3 +- .../list_page/spot_list.dart | 9 +- .../map_page/add_spot.dart | 3 +- .../map_page/map_page.dart | 4 +- .../map_page/navigation_screen_page.dart | 0 .../map_page/route_list.dart | 16 +- .../map_page/save_location_no_connection.dart | 0 .../map_page}/spot_details.dart | 24 +- .../{ => statistic_page}/statistic_page.dart | 4 +- 31 files changed, 180 insertions(+), 598 deletions(-) delete mode 100644 src/flutter_app/lib/components/detail/route_details.dart delete mode 100644 src/flutter_app/lib/components/edit/edit_route.dart rename src/flutter_app/lib/pages/{ => diary_page}/diary_page.dart (83%) rename src/flutter_app/lib/{components => pages}/diary_page/image_list_view.dart (100%) rename src/flutter_app/lib/{components => pages}/diary_page/rating_row.dart (100%) rename src/flutter_app/lib/{components => pages}/diary_page/spot_details.dart (98%) rename src/flutter_app/lib/{components => pages}/diary_page/timeline/ascent_timeline.dart (97%) rename src/flutter_app/lib/{components => pages}/diary_page/timeline/pitch_timeline.dart (96%) rename src/flutter_app/lib/{components => pages}/diary_page/timeline/route_timeline.dart (96%) rename src/flutter_app/lib/{components => pages}/diary_page/timeline/spot_timeline.dart (96%) rename src/flutter_app/lib/{components => pages}/diary_page/timeline/trip_timeline.dart (95%) rename src/flutter_app/lib/{components => pages}/list_page/list_page.dart (95%) rename src/flutter_app/lib/{components => pages}/list_page/spot_list.dart (92%) rename src/flutter_app/lib/{components => pages}/map_page/add_spot.dart (98%) rename src/flutter_app/lib/{components => pages}/map_page/map_page.dart (99%) rename src/flutter_app/lib/{components => pages}/map_page/navigation_screen_page.dart (100%) rename src/flutter_app/lib/{components => pages}/map_page/route_list.dart (96%) rename src/flutter_app/lib/{components => pages}/map_page/save_location_no_connection.dart (100%) rename src/flutter_app/lib/{components/map_page/details => pages/map_page}/spot_details.dart (94%) rename src/flutter_app/lib/pages/{ => statistic_page}/statistic_page.dart (95%) diff --git a/src/flutter_app/lib/components/add/add_route.dart b/src/flutter_app/lib/components/add/add_route.dart index 062f0d0..957318c 100644 --- a/src/flutter_app/lib/components/add/add_route.dart +++ b/src/flutter_app/lib/components/add/add_route.dart @@ -4,17 +4,22 @@ import 'package:flutter/services.dart'; import '../../interfaces/grade.dart'; import '../../interfaces/grading_system.dart'; import '../../interfaces/multi_pitch_route/create_multi_pitch_route.dart'; +import '../../interfaces/multi_pitch_route/multi_pitch_route.dart'; import '../../interfaces/route/route.dart'; import '../../interfaces/single_pitch_route/create_single_pitch_route.dart'; +import '../../interfaces/single_pitch_route/single_pitch_route.dart'; import '../../interfaces/spot/spot.dart'; import '../../services/route_service.dart'; import 'package:internet_connection_checker/internet_connection_checker.dart'; +import '../MyTextStyles.dart'; + class AddRoute extends StatefulWidget { - const AddRoute({super.key, required this.spots, this.onAdd}); + const AddRoute({super.key, required this.spot, this.onAddMultiPitchRoute, this.onAddSinglePitchRoute}); - final List spots; - final ValueSetter? onAdd; + final Spot spot; + final ValueSetter? onAddMultiPitchRoute; + final ValueSetter? onAddSinglePitchRoute; @override State createState() => _AddRouteState(); @@ -23,7 +28,6 @@ class AddRoute extends StatefulWidget { class _AddRouteState extends State{ final GlobalKey _formKey = GlobalKey(); final RouteService routeService = RouteService(); - final TextEditingController controllerSpotId = TextEditingController(); final TextEditingController controllerComment = TextEditingController(); final TextEditingController controllerLocation = TextEditingController(); final TextEditingController controllerName = TextEditingController(); @@ -32,18 +36,17 @@ class _AddRouteState extends State{ final TextEditingController controllerLength = TextEditingController(); double currentSliderValue = 0; - Spot? dropdownValue; GradingSystem? gradingSystem; bool isMultiPitch = false; @override void initState(){ - dropdownValue = widget.spots[0]; super.initState(); } @override Widget build(BuildContext context) { + Spot spot = widget.spot; return AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), @@ -55,6 +58,20 @@ class _AddRouteState extends State{ child: Column( mainAxisSize: MainAxisSize.min, children: [ + Text( + spot.name, + style: MyTextStyles.title, + ), + TextFormField( + validator: (value) { + return value!.isNotEmpty + ? null + : "please add a name"; + }, + controller: controllerName, + decoration: const InputDecoration( + hintText: "name of the route", labelText: "name"), + ), Row(children: [ const Text("Multi-pitch"), Switch( @@ -103,30 +120,6 @@ class _AddRouteState extends State{ hintText: "length of the route", labelText: "length"), ), ), - DropdownButton( - value: dropdownValue, - items: widget.spots.map>((Spot spot) { - return DropdownMenuItem( - value: spot, - child: Text(spot.name), - ); - }).toList(), - onChanged: (Spot? spot) { - setState(() { - dropdownValue = spot!; - }); - } - ), - TextFormField( - validator: (value) { - return value!.isNotEmpty - ? null - : "please add a name"; - }, - controller: controllerName, - decoration: const InputDecoration( - hintText: "name of the route", labelText: "name"), - ), TextFormField( controller: controllerLocation, decoration: const InputDecoration( @@ -171,31 +164,28 @@ class _AddRouteState extends State{ bool result = await InternetConnectionChecker().hasConnection; if (_formKey.currentState!.validate()) { Navigator.popUntil(context, ModalRoute.withName('/')); - final dropdownValue = this.dropdownValue; - if (dropdownValue != null){ - ClimbingRoute? createdRoute; - if (isMultiPitch){ - CreateMultiPitchRoute route = CreateMultiPitchRoute( - name: controllerName.text, - location: controllerLocation.text, - rating: currentSliderValue.toInt(), - comment: controllerComment.text, - ); - createdRoute = await routeService.createMultiPitchRoute(route, dropdownValue.id, result); - } else { - CreateSinglePitchRoute route = CreateSinglePitchRoute( - name: controllerName.text, - location: controllerLocation.text, - rating: currentSliderValue.toInt(), - comment: controllerComment.text, - grade: Grade(grade: controllerGrade.text, system: gradingSystem!), - length: int.parse(controllerLength.text) - ); - createdRoute = await routeService.createSinglePitchRoute(route, dropdownValue.id, result); - } - widget.onAdd?.call(createdRoute!); - setState(() {}); + if (isMultiPitch){ + CreateMultiPitchRoute route = CreateMultiPitchRoute( + name: controllerName.text, + location: controllerLocation.text, + rating: currentSliderValue.toInt(), + comment: controllerComment.text, + ); + MultiPitchRoute? createdRoute = await routeService.createMultiPitchRoute(route, spot.id, result); + widget.onAddMultiPitchRoute?.call(createdRoute!); + } else { + CreateSinglePitchRoute route = CreateSinglePitchRoute( + name: controllerName.text, + location: controllerLocation.text, + rating: currentSliderValue.toInt(), + comment: controllerComment.text, + grade: Grade(grade: controllerGrade.text, system: gradingSystem!), + length: int.parse(controllerLength.text) + ); + SinglePitchRoute? createdRoute = await routeService.createSinglePitchRoute(route, spot.id, result); + widget.onAddSinglePitchRoute?.call(createdRoute!); } + setState(() {}); } }, child: const Text("Save")) diff --git a/src/flutter_app/lib/components/add/add_spot.dart b/src/flutter_app/lib/components/add/add_spot.dart index b4d5d42..9ed5a0e 100644 --- a/src/flutter_app/lib/components/add/add_spot.dart +++ b/src/flutter_app/lib/components/add/add_spot.dart @@ -1,5 +1,4 @@ import 'package:climbing_diary/components/MyTextStyles.dart'; -import 'package:climbing_diary/components/map_page/navigation_screen_page.dart'; import 'package:climbing_diary/interfaces/trip/update_trip.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -8,6 +7,7 @@ import 'package:latlong2/latlong.dart'; import '../../interfaces/spot/create_spot.dart'; import '../../interfaces/spot/spot.dart'; import '../../interfaces/trip/trip.dart'; +import '../../pages/map_page/navigation_screen_page.dart'; import '../../services/spot_service.dart'; import 'package:internet_connection_checker/internet_connection_checker.dart'; diff --git a/src/flutter_app/lib/components/detail/multi_pitch_route_details.dart b/src/flutter_app/lib/components/detail/multi_pitch_route_details.dart index fe1eeed..5c0e5f6 100644 --- a/src/flutter_app/lib/components/detail/multi_pitch_route_details.dart +++ b/src/flutter_app/lib/components/detail/multi_pitch_route_details.dart @@ -5,12 +5,12 @@ import 'package:skeletons/skeletons.dart'; import '../../interfaces/multi_pitch_route/multi_pitch_route.dart'; import '../../interfaces/spot/spot.dart'; import '../../interfaces/trip/trip.dart'; +import '../../pages/diary_page/timeline/pitch_timeline.dart'; import '../../services/media_service.dart'; import '../../services/pitch_service.dart'; import '../../services/route_service.dart'; import '../MyButtonStyles.dart'; import '../add/add_pitch.dart'; -import '../diary_page/timeline/pitch_timeline.dart'; import '../edit/edit_multi_pitch_route.dart'; import '../info/multi_pitch_route_info.dart'; diff --git a/src/flutter_app/lib/components/detail/pitch_details.dart b/src/flutter_app/lib/components/detail/pitch_details.dart index 9e8825f..ac91158 100644 --- a/src/flutter_app/lib/components/detail/pitch_details.dart +++ b/src/flutter_app/lib/components/detail/pitch_details.dart @@ -6,11 +6,11 @@ import 'package:skeletons/skeletons.dart'; import '../../interfaces/pitch/pitch.dart'; import '../../interfaces/spot/spot.dart'; import '../../interfaces/trip/trip.dart'; +import '../../pages/diary_page/timeline/ascent_timeline.dart'; import '../../services/media_service.dart'; import '../../services/pitch_service.dart'; import '../MyButtonStyles.dart'; import '../add/add_ascent.dart'; -import '../diary_page/timeline/ascent_timeline.dart'; import '../edit/edit_pitch.dart'; import '../info/pitch_info.dart'; diff --git a/src/flutter_app/lib/components/detail/route_details.dart b/src/flutter_app/lib/components/detail/route_details.dart deleted file mode 100644 index 8bb565c..0000000 --- a/src/flutter_app/lib/components/detail/route_details.dart +++ /dev/null @@ -1,312 +0,0 @@ -import 'package:climbing_diary/interfaces/multi_pitch_route/multi_pitch_route.dart'; -import 'package:flutter/material.dart'; -import 'package:image_picker/image_picker.dart'; -import 'package:skeletons/skeletons.dart'; - -import '../../interfaces/route/route.dart'; -import '../../interfaces/spot/spot.dart'; -import '../../interfaces/trip/trip.dart'; -import '../../services/media_service.dart'; -import '../../services/pitch_service.dart'; -import '../../services/route_service.dart'; -import '../MyButtonStyles.dart'; -import '../add/add_pitch.dart'; -import '../edit/edit_route.dart'; - -class RouteDetails extends StatefulWidget { - const RouteDetails({super.key, this.trip, required this.spot, required this.route, required this.onDelete, required this.onUpdate, required this.spotId }); - - final Trip? trip; - final Spot spot; - final MultiPitchRoute route; - final ValueSetter onDelete; - final ValueSetter onUpdate; - final String spotId; - - @override - State createState() => _RouteDetailsState(); -} - -class _RouteDetailsState extends State{ - final MediaService mediaService = MediaService(); - final RouteService routeService = RouteService(); - final PitchService pitchService = PitchService(); - - Future> fetchURLs() { - List> futures = []; - for (var mediaId in widget.route.mediaIds) { - futures.add(mediaService.getMediumUrl(mediaId)); - } - return Future.wait(futures); - } - - XFile? image; - final ImagePicker picker = ImagePicker(); - - Future getImage(ImageSource media) async { - var img = await picker.pickImage(source: media); - if (img != null){ - var mediaId = await mediaService.uploadMedia(img); - MultiPitchRoute route = widget.route; - route.mediaIds.add(mediaId); - routeService.editMultiPitchRoute(route.toUpdateMultiPitchRoute()); - } - - setState(() { - image = img; - }); - } - - void addImageDialog() { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - shape: - RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), - title: const Text('Please choose media to select'), - content: SizedBox( - height: MediaQuery.of(context).size.height / 6, - child: Column( - children: [ - ElevatedButton( - //if user click this button, user can upload image from gallery - onPressed: () { - Navigator.pop(context); - getImage(ImageSource.gallery); - }, - child: Row( - children: const [ - Icon(Icons.image), - Text('From Gallery'), - ], - ), - ), - ElevatedButton( - //if user click this button. user can upload image from camera - onPressed: () { - Navigator.pop(context); - getImage(ImageSource.camera); - }, - child: Row( - children: const [ - Icon(Icons.camera), - Text('From Camera'), - ], - ), - ), - ], - ), - ), - ); - }); - } - - void editRouteDialog() { - showDialog( - context: context, - builder: (BuildContext context) { - return EditRoute(route: widget.route, onUpdate: widget.onUpdate); - }); - } - - @override - void initState(){ - super.initState(); - } - - @override - Widget build(BuildContext context) { - List elements = []; - MultiPitchRoute route = widget.route; - - // general info - elements.addAll([ - Text( - route.name, - style: const TextStyle( - fontSize: 22, - fontWeight: FontWeight.w600 - ), - ), - Text( - route.location, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400 - ), - )]); - // rating - List ratingRowElements = []; - - for (var i = 0; i < 5; i++){ - if (route.rating > i) { - ratingRowElements.add(const Icon(Icons.favorite, size: 30.0, color: Colors.pink)); - } else { - ratingRowElements.add(const Icon(Icons.favorite, size: 30.0, color: Colors.grey)); - } - } - - elements.add(Center(child: Padding( - padding: const EdgeInsets.only(top: 10), - child:Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: ratingRowElements, - ) - ))); - - if (route.comment.isNotEmpty) { - elements.add(Container( - margin: const EdgeInsets.all(15.0), - padding: const EdgeInsets.all(5.0), - decoration: BoxDecoration( - border: Border.all(color: Colors.blueAccent), - borderRadius: BorderRadius.circular(10), - ), - child: Text( - route.comment, - ) - )); - } - // images - if (route.mediaIds.isNotEmpty) { - List imageWidgets = []; - Future> futureMediaUrls = fetchURLs(); - - imageWidgets.add( - FutureBuilder>( - future: futureMediaUrls, - builder: (context, snapshot) { - Widget skeleton = const Padding( - padding: EdgeInsets.all(5), - child: SkeletonAvatar( - style: SkeletonAvatarStyle( - shape: BoxShape.rectangle, width: 150, height: 250 - ), - ) - ); - - if (snapshot.data != null){ - List urls = snapshot.data!; - List images = []; - for (var url in urls){ - images.add( - Padding( - padding: const EdgeInsets.all(5.0), - child: ClipRRect( - borderRadius: BorderRadius.circular(8.0), - child: Image.network( - url, - fit: BoxFit.fitHeight, - loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) { - if (loadingProgress == null) { - return child; - } - return skeleton; - }, - ) - ), - ) - ); - } - return Container( - padding: const EdgeInsets.all(10), - child: Row( - mainAxisSize: MainAxisSize.min, - children: images - ) - ); - } - List skeletons = []; - for (var i = 0; i < route.mediaIds.length; i++){ - skeletons.add(skeleton); - } - return Container( - padding: const EdgeInsets.all(10), - child: Row( - mainAxisSize: MainAxisSize.min, - children: skeletons - ) - ); - } - ) - ); - imageWidgets.add( - ElevatedButton.icon( - icon: const Icon(Icons.add, size: 30.0, color: Colors.pink), - label: const Text('Add image'), - onPressed: () => addImageDialog(), - style: MyButtonStyles.rounded - ), - ); - elements.add( - SizedBox( - height: 250, - child: ListView( - scrollDirection: Axis.horizontal, - children: imageWidgets - ) - ), - ); - } else { - elements.add( - ElevatedButton.icon( - icon: const Icon(Icons.add, size: 30.0, color: Colors.pink), - label: const Text('Add image'), - onPressed: () => addImageDialog(), - style: MyButtonStyles.rounded - ), - ); - } - // add pitch - elements.add( - ElevatedButton.icon( - icon: const Icon(Icons.add, size: 30.0, color: Colors.pink), - label: const Text('Add new pitch'), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => AddPitch(routes: [widget.route],), - ) - ); - }, - style: MyButtonStyles.rounded - ), - ); - elements.add( - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - // delete route button - IconButton( - onPressed: () { - Navigator.pop(context); - routeService.deleteMultiPitchRoute(route, widget.spotId); - widget.onDelete.call(route); - }, - icon: const Icon(Icons.delete), - ), - IconButton( - onPressed: () => editRouteDialog(), - icon: const Icon(Icons.edit), - ), - IconButton( - onPressed: () => Navigator.pop(context), - icon: const Icon(Icons.close), - ), - ], - ) - ); - return Stack( - children: [ - Padding( - padding: const EdgeInsets.all(20), - child: ListView( - children: elements - ) - ) - ] - ); - } -} \ No newline at end of file diff --git a/src/flutter_app/lib/components/detail/single_pitch_route_details.dart b/src/flutter_app/lib/components/detail/single_pitch_route_details.dart index 66c53c6..2ee859a 100644 --- a/src/flutter_app/lib/components/detail/single_pitch_route_details.dart +++ b/src/flutter_app/lib/components/detail/single_pitch_route_details.dart @@ -6,11 +6,11 @@ import 'package:skeletons/skeletons.dart'; import '../../interfaces/single_pitch_route/single_pitch_route.dart'; import '../../interfaces/spot/spot.dart'; import '../../interfaces/trip/trip.dart'; +import '../../pages/diary_page/timeline/ascent_timeline.dart'; import '../../services/media_service.dart'; import '../../services/pitch_service.dart'; import '../../services/route_service.dart'; import '../MyButtonStyles.dart'; -import '../diary_page/timeline/ascent_timeline.dart'; import '../edit/edit_single_pitch_route.dart'; import '../info/single_pitch_route_info.dart'; diff --git a/src/flutter_app/lib/components/detail/spot_details.dart b/src/flutter_app/lib/components/detail/spot_details.dart index ddd2232..c10c4fe 100644 --- a/src/flutter_app/lib/components/detail/spot_details.dart +++ b/src/flutter_app/lib/components/detail/spot_details.dart @@ -5,11 +5,11 @@ import 'package:skeletons/skeletons.dart'; import '../../interfaces/spot/spot.dart'; import '../../interfaces/trip/trip.dart'; +import '../../pages/diary_page/timeline/route_timeline.dart'; import '../../services/media_service.dart'; import '../../services/spot_service.dart'; import '../MyButtonStyles.dart'; import '../add/add_route.dart'; -import '../diary_page/timeline/route_timeline.dart'; import '../edit/edit_spot.dart'; class SpotDetails extends StatefulWidget { @@ -295,11 +295,15 @@ class _SpotDetailsState extends State{ context, MaterialPageRoute( builder: (context) => AddRoute( - spots: [widget.spot], - onAdd: (route) { + spot: widget.spot, + onAddMultiPitchRoute: (route) { widget.spot.multiPitchRouteIds.add(route.id); setState(() {}); }, + onAddSinglePitchRoute: (route) { + widget.spot.singlePitchRouteIds.add(route.id); + setState(() {}); + }, ), ) ); diff --git a/src/flutter_app/lib/components/detail/trip_details.dart b/src/flutter_app/lib/components/detail/trip_details.dart index 4c9d243..83b9d70 100644 --- a/src/flutter_app/lib/components/detail/trip_details.dart +++ b/src/flutter_app/lib/components/detail/trip_details.dart @@ -6,11 +6,10 @@ import 'package:skeletons/skeletons.dart'; import '../../interfaces/spot/spot.dart'; import '../../interfaces/trip/trip.dart'; -import '../../pages/navigation_screen_page.dart'; +import '../../pages/diary_page/timeline/spot_timeline.dart'; import '../../services/media_service.dart'; import '../../services/trip_service.dart'; import '../add/add_spot.dart'; -import '../diary_page/timeline/spot_timeline.dart'; import '../edit/edit_trip.dart'; class TripDetails extends StatefulWidget { diff --git a/src/flutter_app/lib/components/edit/edit_multi_pitch_route.dart b/src/flutter_app/lib/components/edit/edit_multi_pitch_route.dart index 04a5237..8943c37 100644 --- a/src/flutter_app/lib/components/edit/edit_multi_pitch_route.dart +++ b/src/flutter_app/lib/components/edit/edit_multi_pitch_route.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import '../../interfaces/multi_pitch_route/multi_pitch_route.dart'; -import '../../interfaces/multi_pitch_route/update_multi_pitch_route.dart'; import '../../interfaces/route/route.dart'; import '../../interfaces/route/update_route.dart'; import '../../services/route_service.dart'; @@ -103,7 +102,7 @@ class _EditMultiPitchRouteState extends State{ onPressed: () async { bool result = await InternetConnectionChecker().hasConnection; if (_formKey.currentState!.validate()) { - UpdateMultiPitchRoute route = UpdateMultiPitchRoute( + UpdateClimbingRoute route = UpdateClimbingRoute( id: widget.route.id, comment: controllerComment.text, location: controllerLocation.text, @@ -112,10 +111,13 @@ class _EditMultiPitchRouteState extends State{ ); Navigator.of(context).pop(); Navigator.of(context).pop(); - MultiPitchRoute? updatedRoute = await routeService.editMultiPitchRoute(route); + /* + TODO single/multi ? + ClimbingRoute? updatedRoute = await routeService.editRoute(route); if (updatedRoute != null) { widget.onUpdate.call(updatedRoute); } + */ } }, icon: const Icon(Icons.save) diff --git a/src/flutter_app/lib/components/edit/edit_route.dart b/src/flutter_app/lib/components/edit/edit_route.dart deleted file mode 100644 index 9f76bf3..0000000 --- a/src/flutter_app/lib/components/edit/edit_route.dart +++ /dev/null @@ -1,132 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; - -import '../../interfaces/multi_pitch_route/multi_pitch_route.dart'; -import '../../interfaces/route/route.dart'; -import '../../interfaces/route/update_route.dart'; -import '../../services/route_service.dart'; -import 'package:internet_connection_checker/internet_connection_checker.dart'; - -class EditRoute extends StatefulWidget { - const EditRoute({super.key, required this.route, required this.onUpdate}); - - final MultiPitchRoute route; - final ValueSetter onUpdate; - - @override - State createState() => _EditRouteState(); -} - -class _EditRouteState extends State{ - final GlobalKey _formKey = GlobalKey(); - final RouteService routeService = RouteService(); - final TextEditingController controllerName = TextEditingController(); - final TextEditingController controllerDate = TextEditingController(); - final TextEditingController controllerLocation = TextEditingController(); - final TextEditingController controllerComment = TextEditingController(); - - int currentSliderValue = 0; - - @override - void initState(){ - controllerName.text = widget.route.name; - controllerDate.text = DateFormat('yyyy-MM-dd').format(DateTime.now()); - controllerLocation.text = widget.route.location; - currentSliderValue = widget.route.rating; - controllerComment.text = widget.route.comment; - super.initState(); - } - - @override - Widget build(BuildContext context) { - return AlertDialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - title: const Text('Edit this route'), - content: SingleChildScrollView( - child: Form( - key: _formKey, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - TextFormField( - validator: (value) { - return value!.isNotEmpty - ? null - : "Please add a title"; - }, - controller: controllerName, - decoration: const InputDecoration( - hintText: "name", labelText: "name"), - ), - TextFormField( - controller: controllerLocation, - decoration: const InputDecoration(labelText: "location"), - ), - Align( - alignment: Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text( - "Rating", - style: TextStyle( - color: Colors.black.withOpacity(0.6), - fontSize: 16 - ) - ), - ), - ), - Slider( - value: currentSliderValue.toDouble(), - max: 5, - divisions: 5, - label: currentSliderValue.round().toString(), - onChanged: (value) { - setState(() { - currentSliderValue = value.toInt(); - }); - }, - ), - TextFormField( - controller: controllerComment, - decoration: const InputDecoration( - hintText: "Description", labelText: "Description"), - ), - ] - ), - ), - ), - actions: [ - IconButton( - onPressed: () async { - bool result = await InternetConnectionChecker().hasConnection; - if (_formKey.currentState!.validate()) { - UpdateClimbingRoute route = UpdateClimbingRoute( - id: widget.route.id, - comment: controllerComment.text, - location: controllerLocation.text, - name: controllerName.text, - rating: currentSliderValue.toInt(), - ); - Navigator.of(context).pop(); - Navigator.of(context).pop(); - /* - TODO single/multi ? - ClimbingRoute? updatedRoute = await routeService.editRoute(route); - if (updatedRoute != null) { - widget.onUpdate.call(updatedRoute); - } - */ - } - }, - icon: const Icon(Icons.save) - ), - IconButton( - onPressed: () => Navigator.pop(context), - icon: const Icon(Icons.close), - ), - ], - ); - } -} \ No newline at end of file diff --git a/src/flutter_app/lib/components/edit/edit_single_pitch_route.dart b/src/flutter_app/lib/components/edit/edit_single_pitch_route.dart index 43e915c..675c628 100644 --- a/src/flutter_app/lib/components/edit/edit_single_pitch_route.dart +++ b/src/flutter_app/lib/components/edit/edit_single_pitch_route.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:intl/intl.dart'; -import '../../interfaces/route/route.dart'; -import '../../interfaces/route/update_route.dart'; +import '../../interfaces/grading_system.dart'; import '../../interfaces/single_pitch_route/single_pitch_route.dart'; import '../../interfaces/single_pitch_route/update_single_pitch_route.dart'; import '../../services/route_service.dart'; @@ -21,20 +21,26 @@ class EditSinglePitchRoute extends StatefulWidget { class _EditSinglePitchRouteState extends State{ final GlobalKey _formKey = GlobalKey(); final RouteService routeService = RouteService(); - final TextEditingController controllerName = TextEditingController(); - final TextEditingController controllerDate = TextEditingController(); - final TextEditingController controllerLocation = TextEditingController(); final TextEditingController controllerComment = TextEditingController(); + final TextEditingController controllerGrade = TextEditingController(); + final TextEditingController controllerLength = TextEditingController(); + final TextEditingController controllerLocation = TextEditingController(); + final TextEditingController controllerName = TextEditingController(); + final TextEditingController controllerRating = TextEditingController(); int currentSliderValue = 0; + GradingSystem? gradingSystem; @override void initState(){ - controllerName.text = widget.route.name; - controllerDate.text = DateFormat('yyyy-MM-dd').format(DateTime.now()); + controllerComment.text = widget.route.comment; + controllerGrade.text = widget.route.grade.grade; + gradingSystem = widget.route.grade.system; + controllerLength.text = widget.route.length.toString(); controllerLocation.text = widget.route.location; + controllerName.text = widget.route.name; currentSliderValue = widget.route.rating; - controllerComment.text = widget.route.comment; + super.initState(); } @@ -55,26 +61,59 @@ class _EditSinglePitchRouteState extends State{ validator: (value) { return value!.isNotEmpty ? null - : "Please add a title"; + : "please add a name"; }, controller: controllerName, decoration: const InputDecoration( - hintText: "name", labelText: "name"), + hintText: "name of the route", labelText: "name"), + ), + TextFormField( + controller: controllerGrade, + decoration: const InputDecoration( + hintText: "grade of the route", labelText: "grade"), + ), + DropdownButton( + value: gradingSystem, + items: GradingSystem.values.map>((GradingSystem value) { + return DropdownMenuItem( + value: value, + child: Text(value.toShortString()) + ); + }).toList(), + onChanged: (GradingSystem? value) { + setState(() { + gradingSystem = value!; + }); + }, + ), + TextFormField( + controller: controllerLength, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], + decoration: const InputDecoration( + hintText: "length of the route", labelText: "length"), ), TextFormField( controller: controllerLocation, decoration: const InputDecoration(labelText: "location"), ), + TextFormField( + controller: controllerComment, + decoration: const InputDecoration( + hintText: "comment", labelText: "comment"), + ), Align( alignment: Alignment.centerLeft, child: Padding( padding: const EdgeInsets.only(top: 8.0), child: Text( - "Rating", - style: TextStyle( - color: Colors.black.withOpacity(0.6), - fontSize: 16 - ) + "Rating", + style: TextStyle( + color: Colors.black.withOpacity(0.6), + fontSize: 16 + ) ), ), ), @@ -89,11 +128,6 @@ class _EditSinglePitchRouteState extends State{ }); }, ), - TextFormField( - controller: controllerComment, - decoration: const InputDecoration( - hintText: "Description", labelText: "Description"), - ), ] ), ), diff --git a/src/flutter_app/lib/components/edit/edit_spot.dart b/src/flutter_app/lib/components/edit/edit_spot.dart index 0bfdb95..7307dbf 100644 --- a/src/flutter_app/lib/components/edit/edit_spot.dart +++ b/src/flutter_app/lib/components/edit/edit_spot.dart @@ -156,8 +156,7 @@ class _EditSpotState extends State{ distancePublicTransport: (valDistancePublicTransport != null) ? valDistancePublicTransport : 0, comment: controllerDescription.text, ); - Navigator.of(context).pop(); - Navigator.of(context).pop(); + Navigator.popUntil(context, ModalRoute.withName('/')); Spot? updatedSpot = await spotService.editSpot(spot); if (updatedSpot != null) { widget.onUpdate.call(updatedSpot); diff --git a/src/flutter_app/lib/main.dart b/src/flutter_app/lib/main.dart index 1ff0cbc..b7ca9e7 100644 --- a/src/flutter_app/lib/main.dart +++ b/src/flutter_app/lib/main.dart @@ -1,11 +1,11 @@ import 'package:auth0_flutter/auth0_flutter.dart'; -import 'package:climbing_diary/components/list_page/list_page.dart'; +import 'package:climbing_diary/pages/list_page/list_page.dart'; +import 'package:climbing_diary/pages/map_page/map_page.dart'; import 'package:internet_connection_checker/internet_connection_checker.dart'; import 'package:overlay_support/overlay_support.dart'; import 'config/environment.dart'; -import 'pages/diary_page.dart'; -import 'components/map_page/map_page.dart'; -import 'pages/statistic_page.dart'; +import 'pages/diary_page/diary_page.dart'; +import 'pages/statistic_page/statistic_page.dart'; import 'services/locator.dart'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; diff --git a/src/flutter_app/lib/pages/diary_page.dart b/src/flutter_app/lib/pages/diary_page/diary_page.dart similarity index 83% rename from src/flutter_app/lib/pages/diary_page.dart rename to src/flutter_app/lib/pages/diary_page/diary_page.dart index ac2a87e..9124da6 100644 --- a/src/flutter_app/lib/pages/diary_page.dart +++ b/src/flutter_app/lib/pages/diary_page/diary_page.dart @@ -1,5 +1,5 @@ +import 'package:climbing_diary/pages/diary_page/timeline/trip_timeline.dart'; import 'package:flutter/material.dart'; -import '../components/diary_page/timeline/trip_timeline.dart'; class DiaryPage extends StatefulWidget { const DiaryPage({super.key}); diff --git a/src/flutter_app/lib/components/diary_page/image_list_view.dart b/src/flutter_app/lib/pages/diary_page/image_list_view.dart similarity index 100% rename from src/flutter_app/lib/components/diary_page/image_list_view.dart rename to src/flutter_app/lib/pages/diary_page/image_list_view.dart diff --git a/src/flutter_app/lib/components/diary_page/rating_row.dart b/src/flutter_app/lib/pages/diary_page/rating_row.dart similarity index 100% rename from src/flutter_app/lib/components/diary_page/rating_row.dart rename to src/flutter_app/lib/pages/diary_page/rating_row.dart diff --git a/src/flutter_app/lib/components/diary_page/spot_details.dart b/src/flutter_app/lib/pages/diary_page/spot_details.dart similarity index 98% rename from src/flutter_app/lib/components/diary_page/spot_details.dart rename to src/flutter_app/lib/pages/diary_page/spot_details.dart index 9629d3c..e70c4a3 100644 --- a/src/flutter_app/lib/components/diary_page/spot_details.dart +++ b/src/flutter_app/lib/pages/diary_page/spot_details.dart @@ -3,13 +3,13 @@ import 'package:image_picker/image_picker.dart'; import 'package:latlong2/latlong.dart'; import 'package:skeletons/skeletons.dart'; +import '../../components/MyButtonStyles.dart'; +import '../../components/edit/edit_spot.dart'; import '../../interfaces/spot/spot.dart'; import '../../interfaces/trip/trip.dart'; import '../../services/media_service.dart'; import '../../services/spot_service.dart'; -import '../MyButtonStyles.dart'; -import '../diary_page/timeline/route_timeline.dart'; -import '../edit/edit_spot.dart'; +import 'timeline/route_timeline.dart'; class SpotDetails extends StatefulWidget { const SpotDetails({super.key, this.trip, required this.spot, required this.onDelete, required this.onUpdate }); diff --git a/src/flutter_app/lib/components/diary_page/timeline/ascent_timeline.dart b/src/flutter_app/lib/pages/diary_page/timeline/ascent_timeline.dart similarity index 97% rename from src/flutter_app/lib/components/diary_page/timeline/ascent_timeline.dart rename to src/flutter_app/lib/pages/diary_page/timeline/ascent_timeline.dart index 15dc10c..9554372 100644 --- a/src/flutter_app/lib/components/diary_page/timeline/ascent_timeline.dart +++ b/src/flutter_app/lib/pages/diary_page/timeline/ascent_timeline.dart @@ -1,16 +1,15 @@ -import 'package:climbing_diary/components/diary_page/image_list_view.dart'; import 'package:climbing_diary/interfaces/route/route.dart'; import 'package:flutter/material.dart'; import 'package:internet_connection_checker/internet_connection_checker.dart'; import 'package:timelines/timelines.dart'; +import '../../../components/detail/ascent_details.dart'; +import '../../../components/info/ascent_info.dart'; import '../../../interfaces/ascent/ascent.dart'; -import '../../../interfaces/pitch/pitch.dart'; import '../../../interfaces/trip/trip.dart'; import '../../../interfaces/spot/spot.dart'; import '../../../services/ascent_service.dart'; -import '../../detail/ascent_details.dart'; -import '../../info/ascent_info.dart'; +import '../image_list_view.dart'; class AscentTimeline extends StatefulWidget { const AscentTimeline({super.key, this.trip, required this.spot, required this.route, required this.pitchId, required this.ascentIds, required this.onDelete, required this.onUpdate, required this.startDate, required this.endDate, required this.ofMultiPitch}); diff --git a/src/flutter_app/lib/components/diary_page/timeline/pitch_timeline.dart b/src/flutter_app/lib/pages/diary_page/timeline/pitch_timeline.dart similarity index 96% rename from src/flutter_app/lib/components/diary_page/timeline/pitch_timeline.dart rename to src/flutter_app/lib/pages/diary_page/timeline/pitch_timeline.dart index 1753ae8..6b59c32 100644 --- a/src/flutter_app/lib/components/diary_page/timeline/pitch_timeline.dart +++ b/src/flutter_app/lib/pages/diary_page/timeline/pitch_timeline.dart @@ -1,16 +1,16 @@ -import 'package:climbing_diary/components/diary_page/image_list_view.dart'; -import 'package:climbing_diary/components/diary_page/rating_row.dart'; import 'package:flutter/material.dart'; import 'package:internet_connection_checker/internet_connection_checker.dart'; import 'package:timelines/timelines.dart'; +import '../../../components/detail/pitch_details.dart'; +import '../../../components/info/pitch_info.dart'; import '../../../interfaces/pitch/pitch.dart'; import '../../../interfaces/route/route.dart'; import '../../../interfaces/spot/spot.dart'; import '../../../interfaces/trip/trip.dart'; import '../../../services/pitch_service.dart'; -import '../../detail/pitch_details.dart'; -import '../../info/pitch_info.dart'; +import '../image_list_view.dart'; +import '../rating_row.dart'; class PitchTimeline extends StatefulWidget { const PitchTimeline({super.key, this.trip, required this.spot, required this.route, required this.pitchIds}); diff --git a/src/flutter_app/lib/components/diary_page/timeline/route_timeline.dart b/src/flutter_app/lib/pages/diary_page/timeline/route_timeline.dart similarity index 96% rename from src/flutter_app/lib/components/diary_page/timeline/route_timeline.dart rename to src/flutter_app/lib/pages/diary_page/timeline/route_timeline.dart index 1f77597..e3f68c8 100644 --- a/src/flutter_app/lib/components/diary_page/timeline/route_timeline.dart +++ b/src/flutter_app/lib/pages/diary_page/timeline/route_timeline.dart @@ -1,21 +1,21 @@ -import 'package:climbing_diary/components/diary_page/image_list_view.dart'; -import 'package:climbing_diary/components/diary_page/timeline/pitch_timeline.dart'; -import 'package:climbing_diary/components/diary_page/rating_row.dart'; import 'package:climbing_diary/interfaces/multi_pitch_route/multi_pitch_route.dart'; import 'package:climbing_diary/interfaces/single_pitch_route/single_pitch_route.dart'; +import 'package:climbing_diary/pages/diary_page/timeline/pitch_timeline.dart'; import 'package:flutter/material.dart'; import 'package:internet_connection_checker/internet_connection_checker.dart'; import 'package:timelines/timelines.dart'; +import '../../../components/detail/multi_pitch_route_details.dart'; +import '../../../components/detail/single_pitch_route_details.dart'; +import '../../../components/info/multi_pitch_route_info.dart'; +import '../../../components/info/route_info.dart'; +import '../../../components/info/single_pitch_route_info.dart'; import '../../../interfaces/spot/spot.dart'; import '../../../interfaces/trip/trip.dart'; import '../../../services/pitch_service.dart'; import '../../../services/route_service.dart'; -import '../../detail/multi_pitch_route_details.dart'; -import '../../detail/single_pitch_route_details.dart'; -import '../../info/multi_pitch_route_info.dart'; -import '../../info/route_info.dart'; -import '../../info/single_pitch_route_info.dart'; +import '../image_list_view.dart'; +import '../rating_row.dart'; class RouteTimeline extends StatefulWidget { const RouteTimeline({super.key, this.trip, required this.spot, required this.singlePitchRouteIds, required this.multiPitchRouteIds, required this.startDate, required this.endDate}); diff --git a/src/flutter_app/lib/components/diary_page/timeline/spot_timeline.dart b/src/flutter_app/lib/pages/diary_page/timeline/spot_timeline.dart similarity index 96% rename from src/flutter_app/lib/components/diary_page/timeline/spot_timeline.dart rename to src/flutter_app/lib/pages/diary_page/timeline/spot_timeline.dart index b76730c..04528c2 100644 --- a/src/flutter_app/lib/components/diary_page/timeline/spot_timeline.dart +++ b/src/flutter_app/lib/pages/diary_page/timeline/spot_timeline.dart @@ -1,17 +1,17 @@ -import 'package:climbing_diary/components/diary_page/image_list_view.dart'; -import 'package:climbing_diary/components/diary_page/rating_row.dart'; -import 'package:climbing_diary/components/diary_page/timeline/route_timeline.dart'; +import 'package:climbing_diary/pages/diary_page/timeline/route_timeline.dart'; import 'package:flutter/material.dart'; import 'package:internet_connection_checker/internet_connection_checker.dart'; import 'package:timelines/timelines.dart'; +import '../../../components/info/spot_info.dart'; import '../../../interfaces/spot/spot.dart'; import '../../../interfaces/trip/trip.dart'; import '../../../interfaces/trip/update_trip.dart'; import '../../../services/spot_service.dart'; import '../../../services/trip_service.dart'; +import '../image_list_view.dart'; +import '../rating_row.dart'; import '../spot_details.dart'; -import '../../info/spot_info.dart'; class SpotTimeline extends StatefulWidget { const SpotTimeline({super.key, required this.spotIds, required this.trip, required this.startDate, required this.endDate}); diff --git a/src/flutter_app/lib/components/diary_page/timeline/trip_timeline.dart b/src/flutter_app/lib/pages/diary_page/timeline/trip_timeline.dart similarity index 95% rename from src/flutter_app/lib/components/diary_page/timeline/trip_timeline.dart rename to src/flutter_app/lib/pages/diary_page/timeline/trip_timeline.dart index 68e328d..6ce0039 100644 --- a/src/flutter_app/lib/components/diary_page/timeline/trip_timeline.dart +++ b/src/flutter_app/lib/pages/diary_page/timeline/trip_timeline.dart @@ -1,15 +1,15 @@ -import 'package:climbing_diary/components/diary_page/image_list_view.dart'; -import 'package:climbing_diary/components/diary_page/rating_row.dart'; -import 'package:climbing_diary/components/diary_page/timeline/spot_timeline.dart'; +import 'package:climbing_diary/pages/diary_page/timeline/spot_timeline.dart'; import 'package:flutter/material.dart'; import 'package:internet_connection_checker/internet_connection_checker.dart'; import 'package:timelines/timelines.dart'; +import '../../../components/add/add_trip.dart'; +import '../../../components/detail/trip_details.dart'; +import '../../../components/info/trip_info.dart'; import '../../../interfaces/trip/trip.dart'; import '../../../services/trip_service.dart'; -import '../../add/add_trip.dart'; -import '../../detail/trip_details.dart'; -import '../../info/trip_info.dart'; +import '../image_list_view.dart'; +import '../rating_row.dart'; class TripTimeline extends StatefulWidget { const TripTimeline({super.key}); diff --git a/src/flutter_app/lib/components/list_page/list_page.dart b/src/flutter_app/lib/pages/list_page/list_page.dart similarity index 95% rename from src/flutter_app/lib/components/list_page/list_page.dart rename to src/flutter_app/lib/pages/list_page/list_page.dart index a63e280..0148444 100644 --- a/src/flutter_app/lib/components/list_page/list_page.dart +++ b/src/flutter_app/lib/pages/list_page/list_page.dart @@ -1,5 +1,4 @@ -import 'package:climbing_diary/components/diary_page/timeline/spot_timeline.dart'; -import 'package:climbing_diary/components/list_page/spot_list.dart'; +import 'package:climbing_diary/pages/list_page/spot_list.dart'; import 'package:flutter/material.dart'; import 'package:latlong2/latlong.dart'; import '../../interfaces/spot/spot.dart'; diff --git a/src/flutter_app/lib/components/list_page/spot_list.dart b/src/flutter_app/lib/pages/list_page/spot_list.dart similarity index 92% rename from src/flutter_app/lib/components/list_page/spot_list.dart rename to src/flutter_app/lib/pages/list_page/spot_list.dart index 43f95cb..bb525b0 100644 --- a/src/flutter_app/lib/components/list_page/spot_list.dart +++ b/src/flutter_app/lib/pages/list_page/spot_list.dart @@ -1,14 +1,13 @@ -import 'package:climbing_diary/components/diary_page/image_list_view.dart'; -import 'package:climbing_diary/components/diary_page/rating_row.dart'; -import 'package:climbing_diary/components/diary_page/timeline/route_timeline.dart'; import 'package:flutter/material.dart'; import 'package:internet_connection_checker/internet_connection_checker.dart'; import 'package:timelines/timelines.dart'; import '../../../interfaces/spot/spot.dart'; import '../../../services/spot_service.dart'; -import '../detail/spot_details.dart'; -import '../info/spot_info.dart'; +import '../../components/detail/spot_details.dart'; +import '../../components/info/spot_info.dart'; +import '../diary_page/image_list_view.dart'; +import '../diary_page/rating_row.dart'; class SpotList extends StatefulWidget { const SpotList({super.key, required this.spots}); diff --git a/src/flutter_app/lib/components/map_page/add_spot.dart b/src/flutter_app/lib/pages/map_page/add_spot.dart similarity index 98% rename from src/flutter_app/lib/components/map_page/add_spot.dart rename to src/flutter_app/lib/pages/map_page/add_spot.dart index 739376b..f049175 100644 --- a/src/flutter_app/lib/components/map_page/add_spot.dart +++ b/src/flutter_app/lib/pages/map_page/add_spot.dart @@ -1,9 +1,7 @@ import 'package:climbing_diary/components/MyTextStyles.dart'; -import 'package:climbing_diary/components/map_page/navigation_screen_page.dart'; import 'package:climbing_diary/interfaces/trip/update_trip.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; -import 'package:latlong2/latlong.dart'; import '../../interfaces/spot/create_spot.dart'; import '../../interfaces/spot/spot.dart'; @@ -12,6 +10,7 @@ import '../../services/spot_service.dart'; import 'package:internet_connection_checker/internet_connection_checker.dart'; import '../../services/trip_service.dart'; +import '../navigation_screen_page.dart'; class AddSpot extends StatefulWidget { const AddSpot({super.key, this.trip, required this.onAdd}); diff --git a/src/flutter_app/lib/components/map_page/map_page.dart b/src/flutter_app/lib/pages/map_page/map_page.dart similarity index 99% rename from src/flutter_app/lib/components/map_page/map_page.dart rename to src/flutter_app/lib/pages/map_page/map_page.dart index b4d5498..bab1eeb 100644 --- a/src/flutter_app/lib/components/map_page/map_page.dart +++ b/src/flutter_app/lib/pages/map_page/map_page.dart @@ -1,10 +1,8 @@ -import 'dart:ffi'; - import 'package:climbing_diary/pages/save_location_no_connection.dart'; import 'package:flutter/material.dart'; import 'add_spot.dart'; -import 'details/spot_details.dart'; +import 'spot_details.dart'; import '../../services/cache.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; diff --git a/src/flutter_app/lib/components/map_page/navigation_screen_page.dart b/src/flutter_app/lib/pages/map_page/navigation_screen_page.dart similarity index 100% rename from src/flutter_app/lib/components/map_page/navigation_screen_page.dart rename to src/flutter_app/lib/pages/map_page/navigation_screen_page.dart diff --git a/src/flutter_app/lib/components/map_page/route_list.dart b/src/flutter_app/lib/pages/map_page/route_list.dart similarity index 96% rename from src/flutter_app/lib/components/map_page/route_list.dart rename to src/flutter_app/lib/pages/map_page/route_list.dart index 10177e3..3485d6f 100644 --- a/src/flutter_app/lib/components/map_page/route_list.dart +++ b/src/flutter_app/lib/pages/map_page/route_list.dart @@ -1,6 +1,3 @@ -import 'package:climbing_diary/components/diary_page/image_list_view.dart'; -import 'package:climbing_diary/components/diary_page/timeline/pitch_timeline.dart'; -import 'package:climbing_diary/components/diary_page/rating_row.dart'; import 'package:climbing_diary/interfaces/multi_pitch_route/multi_pitch_route.dart'; import 'package:climbing_diary/interfaces/single_pitch_route/single_pitch_route.dart'; import 'package:flutter/material.dart'; @@ -11,11 +8,14 @@ import '../../../interfaces/spot/spot.dart'; import '../../../interfaces/trip/trip.dart'; import '../../../services/pitch_service.dart'; import '../../../services/route_service.dart'; -import '../detail/multi_pitch_route_details.dart'; -import '../detail/single_pitch_route_details.dart'; -import '../info/multi_pitch_route_info.dart'; -import '../info/route_info.dart'; -import '../info/single_pitch_route_info.dart'; +import '../../components/detail/multi_pitch_route_details.dart'; +import '../../components/detail/single_pitch_route_details.dart'; +import '../../components/info/multi_pitch_route_info.dart'; +import '../../components/info/route_info.dart'; +import '../../components/info/single_pitch_route_info.dart'; +import '../diary_page/image_list_view.dart'; +import '../diary_page/rating_row.dart'; +import '../diary_page/timeline/pitch_timeline.dart'; class RouteList extends StatefulWidget { const RouteList({super.key, this.trip, required this.spot, required this.singlePitchRouteIds, required this.multiPitchRouteIds}); diff --git a/src/flutter_app/lib/components/map_page/save_location_no_connection.dart b/src/flutter_app/lib/pages/map_page/save_location_no_connection.dart similarity index 100% rename from src/flutter_app/lib/components/map_page/save_location_no_connection.dart rename to src/flutter_app/lib/pages/map_page/save_location_no_connection.dart diff --git a/src/flutter_app/lib/components/map_page/details/spot_details.dart b/src/flutter_app/lib/pages/map_page/spot_details.dart similarity index 94% rename from src/flutter_app/lib/components/map_page/details/spot_details.dart rename to src/flutter_app/lib/pages/map_page/spot_details.dart index 9a0b71b..0f95e8c 100644 --- a/src/flutter_app/lib/components/map_page/details/spot_details.dart +++ b/src/flutter_app/lib/pages/map_page/spot_details.dart @@ -3,14 +3,14 @@ import 'package:image_picker/image_picker.dart'; import 'package:latlong2/latlong.dart'; import 'package:skeletons/skeletons.dart'; -import '../../../interfaces/spot/spot.dart'; -import '../../../interfaces/trip/trip.dart'; -import '../../../services/media_service.dart'; -import '../../../services/spot_service.dart'; -import '../../MyButtonStyles.dart'; -import '../../add/add_route.dart'; -import '../../edit/edit_spot.dart'; -import '../route_list.dart'; +import '../../components/MyButtonStyles.dart'; +import '../../components/add/add_route.dart'; +import '../../components/edit/edit_spot.dart'; +import '../../interfaces/spot/spot.dart'; +import '../../interfaces/trip/trip.dart'; +import '../../services/media_service.dart'; +import '../../services/spot_service.dart'; +import 'route_list.dart'; class SpotDetails extends StatefulWidget { const SpotDetails({super.key, this.trip, required this.spot, required this.onDelete, required this.onUpdate }); @@ -295,11 +295,15 @@ class _SpotDetailsState extends State{ context, MaterialPageRoute( builder: (context) => AddRoute( - spots: [widget.spot], - onAdd: (route) { + spot: widget.spot, + onAddMultiPitchRoute: (route) { widget.spot.multiPitchRouteIds.add(route.id); setState(() {}); }, + onAddSinglePitchRoute: (route) { + widget.spot.singlePitchRouteIds.add(route.id); + setState(() {}); + }, ), ) ); diff --git a/src/flutter_app/lib/pages/statistic_page.dart b/src/flutter_app/lib/pages/statistic_page/statistic_page.dart similarity index 95% rename from src/flutter_app/lib/pages/statistic_page.dart rename to src/flutter_app/lib/pages/statistic_page/statistic_page.dart index a6e6c8a..1308eba 100644 --- a/src/flutter_app/lib/pages/statistic_page.dart +++ b/src/flutter_app/lib/pages/statistic_page/statistic_page.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:overlay_support/overlay_support.dart'; import 'package:flutter_heatmap_calendar/flutter_heatmap_calendar.dart'; -import '../interfaces/ascent/ascent.dart'; -import '../services/ascent_service.dart'; +import '../../interfaces/ascent/ascent.dart'; +import '../../services/ascent_service.dart'; class StatisticPage extends StatefulWidget { const StatisticPage({super.key}); From 7dab376825ee4d17f29f2782bbcc3a708e3aaf1c Mon Sep 17 00:00:00 2001 From: florianpix Date: Fri, 30 Jun 2023 10:43:55 +0200 Subject: [PATCH 07/13] fixed edit multi pitch route; --- .../lib/components/edit/edit_multi_pitch_route.dart | 11 ++++------- .../lib/components/edit/edit_single_pitch_route.dart | 3 +-- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/flutter_app/lib/components/edit/edit_multi_pitch_route.dart b/src/flutter_app/lib/components/edit/edit_multi_pitch_route.dart index 8943c37..585cc5b 100644 --- a/src/flutter_app/lib/components/edit/edit_multi_pitch_route.dart +++ b/src/flutter_app/lib/components/edit/edit_multi_pitch_route.dart @@ -1,3 +1,4 @@ +import 'package:climbing_diary/interfaces/multi_pitch_route/update_multi_pitch_route.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -102,22 +103,18 @@ class _EditMultiPitchRouteState extends State{ onPressed: () async { bool result = await InternetConnectionChecker().hasConnection; if (_formKey.currentState!.validate()) { - UpdateClimbingRoute route = UpdateClimbingRoute( + UpdateMultiPitchRoute route = UpdateMultiPitchRoute( id: widget.route.id, comment: controllerComment.text, location: controllerLocation.text, name: controllerName.text, rating: currentSliderValue.toInt(), ); - Navigator.of(context).pop(); - Navigator.of(context).pop(); - /* - TODO single/multi ? - ClimbingRoute? updatedRoute = await routeService.editRoute(route); + Navigator.popUntil(context, ModalRoute.withName('/')); + MultiPitchRoute? updatedRoute = await routeService.editMultiPitchRoute(route); if (updatedRoute != null) { widget.onUpdate.call(updatedRoute); } - */ } }, icon: const Icon(Icons.save) diff --git a/src/flutter_app/lib/components/edit/edit_single_pitch_route.dart b/src/flutter_app/lib/components/edit/edit_single_pitch_route.dart index 675c628..1c5a044 100644 --- a/src/flutter_app/lib/components/edit/edit_single_pitch_route.dart +++ b/src/flutter_app/lib/components/edit/edit_single_pitch_route.dart @@ -144,8 +144,7 @@ class _EditSinglePitchRouteState extends State{ name: controllerName.text, rating: currentSliderValue.toInt(), ); - Navigator.of(context).pop(); - Navigator.of(context).pop(); + Navigator.popUntil(context, ModalRoute.withName('/')); SinglePitchRoute? updatedRoute = await routeService.editSinglePitchRoute(route); if (updatedRoute != null) { widget.onUpdate.call(updatedRoute); From df03d09222952473ba807a348fee9d7409dfcadd Mon Sep 17 00:00:00 2001 From: florianpix Date: Fri, 30 Jun 2023 10:58:45 +0200 Subject: [PATCH 08/13] add existing spot callback; --- .../lib/components/detail/trip_details.dart | 5 ++++- src/flutter_app/lib/components/edit/edit_trip.dart | 3 +-- .../lib/components/select/select_spot.dart | 11 +++++++---- src/flutter_app/lib/pages/map_page/add_spot.dart | 2 +- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/flutter_app/lib/components/detail/trip_details.dart b/src/flutter_app/lib/components/detail/trip_details.dart index 83b9d70..30e177a 100644 --- a/src/flutter_app/lib/components/detail/trip_details.dart +++ b/src/flutter_app/lib/components/detail/trip_details.dart @@ -295,7 +295,10 @@ class _TripDetailsState extends State{ Navigator.push( context, MaterialPageRoute( - builder: (context) => SelectSpot(trip: widget.trip), + builder: (context) => SelectSpot( + trip: widget.trip, + onAdd: widget.onSpotAdd + ), ) ); }, diff --git a/src/flutter_app/lib/components/edit/edit_trip.dart b/src/flutter_app/lib/components/edit/edit_trip.dart index 372af3a..d95c655 100644 --- a/src/flutter_app/lib/components/edit/edit_trip.dart +++ b/src/flutter_app/lib/components/edit/edit_trip.dart @@ -129,8 +129,7 @@ class _EditTripState extends State{ rating: currentSliderValue.toInt(), comment: controllerComment.text, ); - Navigator.of(context).pop(); - Navigator.of(context).pop(); + Navigator.popUntil(context, ModalRoute.withName('/')); Trip? updatedTrip = await tripService.editTrip(trip); if (updatedTrip != null) { widget.onUpdate.call(updatedTrip); diff --git a/src/flutter_app/lib/components/select/select_spot.dart b/src/flutter_app/lib/components/select/select_spot.dart index 01f8b10..ec0a8f6 100644 --- a/src/flutter_app/lib/components/select/select_spot.dart +++ b/src/flutter_app/lib/components/select/select_spot.dart @@ -8,9 +8,11 @@ import '../../services/trip_service.dart'; import '../MyButtonStyles.dart'; class SelectSpot extends StatefulWidget { - const SelectSpot({super.key, required this.trip}); + const SelectSpot({super.key, required this.trip, required this.onAdd}); final Trip trip; + final ValueSetter onAdd; + @override State createState() => _SelectSpotState(); } @@ -42,12 +44,13 @@ class _SelectSpotState extends State{ elements.add(ElevatedButton.icon( icon: const Icon(Icons.arrow_forward, size: 30.0, color: Colors.pink), label: Text(spots[i].name), - onPressed: () { + onPressed: () async { if (!widget.trip.spotIds.contains(spots[i].id)){ widget.trip.spotIds.add(spots[i].id); - tripService.editTrip(widget.trip.toUpdateTrip()); + Trip? updatedTrip = await tripService.editTrip(widget.trip.toUpdateTrip()); + widget.onAdd.call(spots[i]); } - Navigator.of(context).pop(); + Navigator.popUntil(context, ModalRoute.withName('/')); }, style: MyButtonStyles.rounded )); diff --git a/src/flutter_app/lib/pages/map_page/add_spot.dart b/src/flutter_app/lib/pages/map_page/add_spot.dart index f049175..58c5d73 100644 --- a/src/flutter_app/lib/pages/map_page/add_spot.dart +++ b/src/flutter_app/lib/pages/map_page/add_spot.dart @@ -10,7 +10,7 @@ import '../../services/spot_service.dart'; import 'package:internet_connection_checker/internet_connection_checker.dart'; import '../../services/trip_service.dart'; -import '../navigation_screen_page.dart'; +import 'navigation_screen_page.dart'; class AddSpot extends StatefulWidget { const AddSpot({super.key, this.trip, required this.onAdd}); From f6222e3e6d2a0996a5ec5a95b7c24dc4c20404a8 Mon Sep 17 00:00:00 2001 From: florianpix Date: Fri, 30 Jun 2023 13:35:42 +0200 Subject: [PATCH 09/13] fixed spot search; added route and pitch search; --- .../lib/components/add/add_pitch.dart | 28 +- .../detail/multi_pitch_route_details.dart | 2 +- .../detail/single_pitch_route_details.dart | 1 - .../lib/components/edit/edit_spot.dart | 24 +- .../info/single_pitch_route_info.dart | 5 +- .../diary_page/timeline/route_timeline.dart | 1 - .../lib/pages/list_page/list_page.dart | 188 ++++++++----- .../list_page/multi_pitch_route_details.dart | 240 ++++++++++++++++ .../lib/pages/list_page/pitch_details.dart | 217 +++++++++++++++ .../lib/pages/list_page/pitch_list.dart | 48 ++++ .../lib/pages/list_page/route_list.dart | 71 +++++ .../list_page/single_pitch_route_details.dart | 220 +++++++++++++++ .../lib/pages/list_page/spot_details.dart | 261 ++++++++++++++++++ .../lib/pages/list_page/spot_list.dart | 101 +------ .../lib/pages/map_page/route_list.dart | 1 - .../lib/services/pitch_service.dart | 31 +++ .../lib/services/route_service.dart | 38 +++ 17 files changed, 1284 insertions(+), 193 deletions(-) create mode 100644 src/flutter_app/lib/pages/list_page/multi_pitch_route_details.dart create mode 100644 src/flutter_app/lib/pages/list_page/pitch_details.dart create mode 100644 src/flutter_app/lib/pages/list_page/pitch_list.dart create mode 100644 src/flutter_app/lib/pages/list_page/route_list.dart create mode 100644 src/flutter_app/lib/pages/list_page/single_pitch_route_details.dart create mode 100644 src/flutter_app/lib/pages/list_page/spot_details.dart diff --git a/src/flutter_app/lib/components/add/add_pitch.dart b/src/flutter_app/lib/components/add/add_pitch.dart index 755b092..076ae5c 100644 --- a/src/flutter_app/lib/components/add/add_pitch.dart +++ b/src/flutter_app/lib/components/add/add_pitch.dart @@ -8,10 +8,12 @@ import '../../interfaces/route/route.dart'; import '../../services/pitch_service.dart'; import 'package:internet_connection_checker/internet_connection_checker.dart'; +import '../MyTextStyles.dart'; + class AddPitch extends StatefulWidget { - const AddPitch({super.key, required this.routes}); + const AddPitch({super.key, required this.route}); - final List routes; + final ClimbingRoute route; @override State createState() => _AddPitchState(); @@ -28,7 +30,6 @@ class _AddPitchState extends State{ final TextEditingController controllerRating = TextEditingController(); double currentSliderValue = 0; - ClimbingRoute? dropdownValue; GradingSystem? gradingSystem; @override @@ -49,19 +50,9 @@ class _AddPitchState extends State{ child: Column( mainAxisSize: MainAxisSize.min, children: [ - DropdownButton( - value: dropdownValue, - items: widget.routes.map>((ClimbingRoute route) { - return DropdownMenuItem( - value: route, - child: Text(route.name), - ); - }).toList(), - onChanged: (ClimbingRoute? route) { - setState(() { - dropdownValue = route!; - }); - } + Text( + widget.route.name, + style: MyTextStyles.title, ), TextFormField( validator: (value) { @@ -148,10 +139,7 @@ class _AddPitchState extends State{ num: int.parse(controllerNum.text), rating: currentSliderValue.toInt(), ); - final dropdownValue = this.dropdownValue; - if (dropdownValue != null) { - Pitch? createdPitch = await pitchService.createPitch(pitch, dropdownValue.id, result); - } + Pitch? createdPitch = await pitchService.createPitch(pitch, widget.route.id, result); Navigator.popUntil(context, ModalRoute.withName('/')); } }, diff --git a/src/flutter_app/lib/components/detail/multi_pitch_route_details.dart b/src/flutter_app/lib/components/detail/multi_pitch_route_details.dart index 5c0e5f6..8be172c 100644 --- a/src/flutter_app/lib/components/detail/multi_pitch_route_details.dart +++ b/src/flutter_app/lib/components/detail/multi_pitch_route_details.dart @@ -273,7 +273,7 @@ class _MultiPitchRouteDetailsState extends State{ Navigator.push( context, MaterialPageRoute( - builder: (context) => AddPitch(routes: [widget.route],), + builder: (context) => AddPitch(route: widget.route,), ) ); }, diff --git a/src/flutter_app/lib/components/detail/single_pitch_route_details.dart b/src/flutter_app/lib/components/detail/single_pitch_route_details.dart index 2ee859a..56afb16 100644 --- a/src/flutter_app/lib/components/detail/single_pitch_route_details.dart +++ b/src/flutter_app/lib/components/detail/single_pitch_route_details.dart @@ -126,7 +126,6 @@ class _SinglePitchRouteDetailsState extends State{ // general info elements.addAll([ SinglePitchRouteInfo( - spot: widget.spot, route: route ), Text( diff --git a/src/flutter_app/lib/components/edit/edit_spot.dart b/src/flutter_app/lib/components/edit/edit_spot.dart index 7307dbf..2158dfe 100644 --- a/src/flutter_app/lib/components/edit/edit_spot.dart +++ b/src/flutter_app/lib/components/edit/edit_spot.dart @@ -47,7 +47,7 @@ class _EditSpotState extends State{ shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), - title: const Text('Edit this spot'), + title: const Text('edit this spot'), content: SingleChildScrollView( child: Form( key: _formKey, @@ -58,30 +58,30 @@ class _EditSpotState extends State{ validator: (value) { return value!.isNotEmpty ? null - : "Please add a title"; + : "please add a title"; }, controller: controllerTitle, decoration: const InputDecoration( - hintText: "Name of the spot", labelText: "Title"), + hintText: "name of the spot", labelText: "name"), ), TextFormField( controller: controllerAddress, - decoration: const InputDecoration(labelText: "Address"), + decoration: const InputDecoration(labelText: "address"), ), TextFormField( controller: controllerLat, - decoration: const InputDecoration(labelText: "Latitude"), + decoration: const InputDecoration(labelText: "latitude"), ), TextFormField( controller: controllerLong, - decoration: const InputDecoration(labelText: "Longitude"), + decoration: const InputDecoration(labelText: "longitude"), ), Align( alignment: Alignment.centerLeft, child: Padding( padding: const EdgeInsets.only(top: 8.0), child: Text( - "Rating", + "rating", style: TextStyle( color: Colors.black.withOpacity(0.6), fontSize: 16 @@ -103,14 +103,14 @@ class _EditSpotState extends State{ TextFormField( controller: controllerDescription, decoration: const InputDecoration( - hintText: "Description", labelText: "Description"), + hintText: "comment", labelText: "comment"), ), TextFormField( validator: (value) { if (value != null && value != ""){ var i = int.tryParse(value); if (i == null) { - return "Must be a number"; + return "must be a number"; } } return null; @@ -118,14 +118,14 @@ class _EditSpotState extends State{ controller: controllerBus, decoration: const InputDecoration( hintText: "in minutes", - labelText: "Distance to public transport station"), + labelText: "distance to public transport station"), ), TextFormField( validator: (value) { if (value != null && value != ""){ var i = int.tryParse(value); if (i == null) { - return "Must be a number"; + return "must be a number"; } } return null; @@ -133,7 +133,7 @@ class _EditSpotState extends State{ controller: controllerCar, decoration: const InputDecoration( hintText: "in minutes", - labelText: "Distance to parking"), + labelText: "distance to parking"), ) ], ), diff --git a/src/flutter_app/lib/components/info/single_pitch_route_info.dart b/src/flutter_app/lib/components/info/single_pitch_route_info.dart index dfbc984..28a9f31 100644 --- a/src/flutter_app/lib/components/info/single_pitch_route_info.dart +++ b/src/flutter_app/lib/components/info/single_pitch_route_info.dart @@ -1,4 +1,3 @@ -import 'package:climbing_diary/components/detail/pitch_details.dart'; import 'package:climbing_diary/interfaces/grading_system.dart'; import 'package:climbing_diary/services/ascent_service.dart'; import 'package:flutter/material.dart'; @@ -7,16 +6,14 @@ import '../../interfaces/ascent/ascent.dart'; import '../../interfaces/ascent/ascent_style.dart'; import '../../interfaces/ascent/ascent_type.dart'; import '../../interfaces/single_pitch_route/single_pitch_route.dart'; -import '../../interfaces/spot/spot.dart'; import '../../interfaces/trip/trip.dart'; import '../MyTextStyles.dart'; class SinglePitchRouteInfo extends StatefulWidget { const SinglePitchRouteInfo( - {super.key, this.trip, required this.spot, required this.route}); + {super.key, this.trip, required this.route}); final Trip? trip; - final Spot spot; final SinglePitchRoute route; @override diff --git a/src/flutter_app/lib/pages/diary_page/timeline/route_timeline.dart b/src/flutter_app/lib/pages/diary_page/timeline/route_timeline.dart index e3f68c8..82daf77 100644 --- a/src/flutter_app/lib/pages/diary_page/timeline/route_timeline.dart +++ b/src/flutter_app/lib/pages/diary_page/timeline/route_timeline.dart @@ -190,7 +190,6 @@ class RouteTimelineState extends State { // route info SinglePitchRoute singlePitchRoute = singlePitchRoutes[index]; elements.add(SinglePitchRouteInfo( - spot: widget.spot, route: singlePitchRoute )); // rating as hearts in a row diff --git a/src/flutter_app/lib/pages/list_page/list_page.dart b/src/flutter_app/lib/pages/list_page/list_page.dart index 0148444..726c9b9 100644 --- a/src/flutter_app/lib/pages/list_page/list_page.dart +++ b/src/flutter_app/lib/pages/list_page/list_page.dart @@ -1,7 +1,13 @@ +import 'package:climbing_diary/interfaces/multi_pitch_route/multi_pitch_route.dart'; +import 'package:climbing_diary/pages/list_page/pitch_list.dart'; import 'package:climbing_diary/pages/list_page/spot_list.dart'; +import 'package:climbing_diary/pages/list_page/route_list.dart'; +import 'package:climbing_diary/services/pitch_service.dart'; import 'package:flutter/material.dart'; -import 'package:latlong2/latlong.dart'; +import '../../interfaces/pitch/pitch.dart'; +import '../../interfaces/single_pitch_route/single_pitch_route.dart'; import '../../interfaces/spot/spot.dart'; +import '../../services/route_service.dart'; import '../../services/spot_service.dart'; class ListPage extends StatefulWidget { @@ -16,6 +22,9 @@ class ListPageState extends State { final TextEditingController controllerSearch = TextEditingController(); final SpotService spotService = SpotService(); + final RouteService routeService = RouteService(); + final PitchService pitchService = PitchService(); + bool searchSpots = true; bool searchRoutes = false; bool searchPitches = false; @@ -30,80 +39,129 @@ class ListPageState extends State { @override Widget build(BuildContext context) { - return Scaffold( - body: FutureBuilder>( + Widget search = Form( + key: _formKey, + child: + Padding( + padding: const EdgeInsets.all(20), + child: TextFormField( + controller: controllerSearch, + decoration: const InputDecoration( + icon: Icon(Icons.search), + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(25.0)), + borderSide: BorderSide(color: Colors.blue), + ), + hintText: "name", + labelText: "name" + ), + onChanged: (String s) { + setState(() {}); + } + ), + ), + ); + + Widget switches = Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + const Text("spots"), + Switch( + value: searchSpots, + onChanged: (bool value) { + setState(() { + searchSpots = value; + }); + } + ), + const Text("routes"), + Switch( + value: searchRoutes, + onChanged: (bool value) { + setState(() { + searchRoutes = value; + }); + } + ), + const Text("pitches"), + Switch( + value: searchPitches, + onChanged: (bool value) { + setState(() { + searchPitches = value; + }); + } + ), + ] + ); + + Widget spotList = FutureBuilder>( future: spotService.getSpotsByName(controllerSearch.text), builder: (context, snapshot) { if (snapshot.hasData) { List spots = snapshot.data!; - List elements = []; - elements.add(Form( - key: _formKey, - child: - Padding( - padding: const EdgeInsets.all(20), - child: TextFormField( - controller: controllerSearch, - decoration: const InputDecoration( - icon: Icon(Icons.search), - border: OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(25.0)), - borderSide: BorderSide(color: Colors.blue), - ), - hintText: "name", - labelText: "name" - ), - onChanged: (String s) { - setState(() {}); + return SpotList( + spots: spots + ); + } else { + return const CircularProgressIndicator(); + } + } + ); + + Widget routeList = FutureBuilder>( + future: routeService.getMultiPitchRoutesByName(controllerSearch.text), + builder: (context, snapshot) { + if (snapshot.hasData) { + List multiPitchRoutes = snapshot.data!; + return FutureBuilder>( + future: routeService.getSinglePitchRoutesByName(controllerSearch.text), + builder: (context, snapshot) { + if (snapshot.hasData) { + List singlePitchRoutes = snapshot.data!; + return RouteList( + singlePitchRoutes: singlePitchRoutes, + multiPitchRoutes: multiPitchRoutes, + ); + } else { + return const CircularProgressIndicator(); } - ), - ), - )); - elements.add(Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - const Text("spots"), - Switch( - value: searchSpots, - onChanged: (bool value) { - setState(() { - searchSpots = value; - }); - } - ), - const Text("routes"), - Switch( - value: searchRoutes, - onChanged: (bool value) { - setState(() { - searchRoutes = value; - }); - } - ), - const Text("pitches"), - Switch( - value: searchPitches, - onChanged: (bool value) { - setState(() { - searchPitches = value; - }); - } - ), - ] - )); - elements.add( Padding( - padding: const EdgeInsets.all(10), - child: SpotList( - spots: spots - ))); - return ListView( - children: elements, + } ); } else { return const CircularProgressIndicator(); } } - ) + ); + + Widget pitchList = FutureBuilder>( + future: pitchService.getPitchesByName(controllerSearch.text), + builder: (context, snapshot) { + if (snapshot.hasData) { + List pitches = snapshot.data!; + return PitchList( + pitches: pitches + ); + } else { + return const CircularProgressIndicator(); + } + } + ); + + List elements = []; + + if (searchSpots){ + elements.add(spotList); + } + if(searchRoutes){ + elements.add(routeList); + } + if(searchPitches){ + elements.add(pitchList); + } + + return Scaffold( + body: Column(children: [search, switches, Expanded(child: ListView(children: elements))]) ); } } \ No newline at end of file diff --git a/src/flutter_app/lib/pages/list_page/multi_pitch_route_details.dart b/src/flutter_app/lib/pages/list_page/multi_pitch_route_details.dart new file mode 100644 index 0000000..af3f02c --- /dev/null +++ b/src/flutter_app/lib/pages/list_page/multi_pitch_route_details.dart @@ -0,0 +1,240 @@ +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:skeletons/skeletons.dart'; + +import '../../components/info/multi_pitch_route_info.dart'; +import '../../interfaces/multi_pitch_route/multi_pitch_route.dart'; +import '../../services/media_service.dart'; +import '../../services/pitch_service.dart'; +import '../../services/route_service.dart'; + +class MultiPitchRouteDetails extends StatefulWidget { + const MultiPitchRouteDetails({super.key, required this.route}); + + final MultiPitchRoute route; + + @override + State createState() => _MultiPitchRouteDetailsState(); +} + +class _MultiPitchRouteDetailsState extends State{ + final MediaService mediaService = MediaService(); + final RouteService routeService = RouteService(); + final PitchService pitchService = PitchService(); + + Future> fetchURLs() { + List> futures = []; + for (var mediaId in widget.route.mediaIds) { + futures.add(mediaService.getMediumUrl(mediaId)); + } + return Future.wait(futures); + } + + XFile? image; + final ImagePicker picker = ImagePicker(); + + Future getImage(ImageSource media) async { + var img = await picker.pickImage(source: media); + if (img != null){ + var mediaId = await mediaService.uploadMedia(img); + MultiPitchRoute route = widget.route; + route.mediaIds.add(mediaId); + routeService.editMultiPitchRoute(route.toUpdateMultiPitchRoute()); + } + + setState(() { + image = img; + }); + } + + void addImageDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + title: const Text('Please choose media to select'), + content: SizedBox( + height: MediaQuery.of(context).size.height / 6, + child: Column( + children: [ + ElevatedButton( + //if user click this button, user can upload image from gallery + onPressed: () { + Navigator.pop(context); + getImage(ImageSource.gallery); + }, + child: Row( + children: const [ + Icon(Icons.image), + Text('From Gallery'), + ], + ), + ), + ElevatedButton( + //if user click this button. user can upload image from camera + onPressed: () { + Navigator.pop(context); + getImage(ImageSource.camera); + }, + child: Row( + children: const [ + Icon(Icons.camera), + Text('From Camera'), + ], + ), + ), + ], + ), + ), + ); + }); + } + + @override + void initState(){ + super.initState(); + } + + @override + Widget build(BuildContext context) { + List elements = []; + MultiPitchRoute route = widget.route; + + // general info + elements.addAll([ + Text( + route.name, + style: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.w600 + ), + ), + MultiPitchInfo( + pitchIds: route.pitchIds + ), + Text( + route.location, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400 + ), + )]); + // rating + List ratingRowElements = []; + + for (var i = 0; i < 5; i++){ + if (route.rating > i) { + ratingRowElements.add(const Icon(Icons.favorite, size: 30.0, color: Colors.pink)); + } else { + ratingRowElements.add(const Icon(Icons.favorite, size: 30.0, color: Colors.grey)); + } + } + + elements.add(Center(child: Padding( + padding: const EdgeInsets.only(top: 10), + child:Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: ratingRowElements, + ) + ))); + + if (route.comment.isNotEmpty) { + elements.add(Container( + margin: const EdgeInsets.all(15.0), + padding: const EdgeInsets.all(5.0), + decoration: BoxDecoration( + border: Border.all(color: Colors.blueAccent), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + route.comment, + ) + )); + } + // images + if (route.mediaIds.isNotEmpty) { + List imageWidgets = []; + Future> futureMediaUrls = fetchURLs(); + + imageWidgets.add( + FutureBuilder>( + future: futureMediaUrls, + builder: (context, snapshot) { + Widget skeleton = const Padding( + padding: EdgeInsets.all(5), + child: SkeletonAvatar( + style: SkeletonAvatarStyle( + shape: BoxShape.rectangle, width: 150, height: 250 + ), + ) + ); + + if (snapshot.data != null){ + List urls = snapshot.data!; + List images = []; + for (var url in urls){ + images.add( + Padding( + padding: const EdgeInsets.all(5.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.0), + child: Image.network( + url, + fit: BoxFit.fitHeight, + loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) { + if (loadingProgress == null) { + return child; + } + return skeleton; + }, + ) + ), + ) + ); + } + return Container( + padding: const EdgeInsets.all(10), + child: Row( + mainAxisSize: MainAxisSize.min, + children: images + ) + ); + } + List skeletons = []; + for (var i = 0; i < route.mediaIds.length; i++){ + skeletons.add(skeleton); + } + return Container( + padding: const EdgeInsets.all(10), + child: Row( + mainAxisSize: MainAxisSize.min, + children: skeletons + ) + ); + } + ) + ); + elements.add( + SizedBox( + height: 250, + child: ListView( + scrollDirection: Axis.horizontal, + children: imageWidgets + ) + ), + ); + } + return Stack( + children: [ + Padding( + padding: const EdgeInsets.all(20), + child: ListView( + children: elements + ) + ) + ] + ); + } +} \ No newline at end of file diff --git a/src/flutter_app/lib/pages/list_page/pitch_details.dart b/src/flutter_app/lib/pages/list_page/pitch_details.dart new file mode 100644 index 0000000..0cb1162 --- /dev/null +++ b/src/flutter_app/lib/pages/list_page/pitch_details.dart @@ -0,0 +1,217 @@ +import 'package:climbing_diary/interfaces/route/route.dart'; +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:skeletons/skeletons.dart'; + +import '../../components/info/pitch_info.dart'; +import '../../interfaces/pitch/pitch.dart'; +import '../../pages/diary_page/timeline/ascent_timeline.dart'; +import '../../services/media_service.dart'; +import '../../services/pitch_service.dart'; + +class PitchDetails extends StatefulWidget { + const PitchDetails({super.key, required this.pitch}); + + final Pitch pitch; + + @override + State createState() => _PitchDetailsState(); +} + +class _PitchDetailsState extends State{ + final MediaService mediaService = MediaService(); + final PitchService pitchService = PitchService(); + + Future> fetchURLs() { + List> futures = []; + for (var mediaId in widget.pitch.mediaIds) { + futures.add(mediaService.getMediumUrl(mediaId)); + } + return Future.wait(futures); + } + + XFile? image; + final ImagePicker picker = ImagePicker(); + + Future getImage(ImageSource media) async { + var img = await picker.pickImage(source: media); + if (img != null){ + var mediaId = await mediaService.uploadMedia(img); + Pitch pitch = widget.pitch; + pitch.mediaIds.add(mediaId); + pitchService.editPitch(pitch.toUpdatePitch()); + } + + setState(() { + image = img; + }); + } + + void addImageDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + title: const Text('Please choose media to select'), + content: SizedBox( + height: MediaQuery.of(context).size.height / 6, + child: Column( + children: [ + ElevatedButton( + //if user click this button, user can upload image from gallery + onPressed: () { + Navigator.pop(context); + getImage(ImageSource.gallery); + }, + child: Row( + children: const [ + Icon(Icons.image), + Text('From Gallery'), + ], + ), + ), + ElevatedButton( + //if user click this button. user can upload image from camera + onPressed: () { + Navigator.pop(context); + getImage(ImageSource.camera); + }, + child: Row( + children: const [ + Icon(Icons.camera), + Text('From Camera'), + ], + ), + ), + ], + ), + ), + ); + }); + } + + @override + void initState(){ + super.initState(); + } + + @override + Widget build(BuildContext context) { + Pitch pitch = widget.pitch; + List elements = []; + + // general info + elements.add(PitchInfo(pitch: pitch)); + // rating + List ratingRowElements = []; + + for (var i = 0; i < 5; i++){ + if (pitch.rating > i) { + ratingRowElements.add(const Icon(Icons.favorite, size: 30.0, color: Colors.pink)); + } else { + ratingRowElements.add(const Icon(Icons.favorite, size: 30.0, color: Colors.grey)); + } + } + + elements.add(Center(child: Padding( + padding: const EdgeInsets.all(10), + child:Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: ratingRowElements, + ) + ))); + + if (pitch.comment.isNotEmpty) { + elements.add(Container( + margin: const EdgeInsets.all(15.0), + padding: const EdgeInsets.all(5.0), + decoration: BoxDecoration( + border: Border.all(color: Colors.blueAccent), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + pitch.comment, + ) + )); + } + // images + if (pitch.mediaIds.isNotEmpty) { + List imageWidgets = []; + Future> futureMediaUrls = fetchURLs(); + + imageWidgets.add( + FutureBuilder>( + future: futureMediaUrls, + builder: (context, snapshot) { + Widget skeleton = const Padding( + padding: EdgeInsets.all(5), + child: SkeletonAvatar( + style: SkeletonAvatarStyle( + shape: BoxShape.rectangle, width: 150, height: 250 + ), + ) + ); + + if (snapshot.data != null){ + List urls = snapshot.data!; + List images = []; + for (var url in urls){ + images.add( + Padding( + padding: const EdgeInsets.all(5.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.0), + child: Image.network( + url, + fit: BoxFit.fitHeight, + loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) { + if (loadingProgress == null) { + return child; + } + return skeleton; + }, + ) + ), + ) + ); + } + return Container( + padding: const EdgeInsets.all(10), + child: Row( + mainAxisSize: MainAxisSize.min, + children: images + ) + ); + } + List skeletons = []; + for (var i = 0; i < pitch.mediaIds.length; i++){ + skeletons.add(skeleton); + } + return Container( + padding: const EdgeInsets.all(10), + child: Row( + mainAxisSize: MainAxisSize.min, + children: skeletons + ) + ); + } + ) + ); + elements.add( + SizedBox( + height: 250, + child: ListView( + scrollDirection: Axis.horizontal, + children: imageWidgets + ) + ), + ); + } + + return Column( + children: elements + ); + } +} \ No newline at end of file diff --git a/src/flutter_app/lib/pages/list_page/pitch_list.dart b/src/flutter_app/lib/pages/list_page/pitch_list.dart new file mode 100644 index 0000000..09f8a3d --- /dev/null +++ b/src/flutter_app/lib/pages/list_page/pitch_list.dart @@ -0,0 +1,48 @@ +import 'package:climbing_diary/interfaces/multi_pitch_route/multi_pitch_route.dart'; +import 'package:climbing_diary/interfaces/single_pitch_route/single_pitch_route.dart'; +import 'package:flutter/material.dart'; +import '../../../services/pitch_service.dart'; +import '../../../services/route_service.dart'; +import '../../interfaces/pitch/pitch.dart'; +import 'pitch_details.dart'; + +class PitchList extends StatefulWidget { + const PitchList({super.key, required this.pitches}); + + final List pitches; + + @override + State createState() => PitchListState(); +} + +class PitchListState extends State { + final RouteService routeService = RouteService(); + final PitchService pitchService = PitchService(); + + @override + void initState(){ + super.initState(); + } + + @override + Widget build(BuildContext context) { + List pitches = widget.pitches; + pitches.sort((a, b) => a.name.compareTo(b.name)); + return Column( + children: pitches.map((pitch) => buildList(pitch)).toList(), + ); + } + + Widget buildList(Pitch pitch){ + return ExpansionTile( + title: Text( + "${pitch.name} (${pitch.num})", + ), + children: [Padding( + padding: const EdgeInsets.only(left: 20, right: 20), + child: PitchDetails( + pitch: pitch, + ))] + ); + } +} \ No newline at end of file diff --git a/src/flutter_app/lib/pages/list_page/route_list.dart b/src/flutter_app/lib/pages/list_page/route_list.dart new file mode 100644 index 0000000..33e2bdb --- /dev/null +++ b/src/flutter_app/lib/pages/list_page/route_list.dart @@ -0,0 +1,71 @@ +import 'package:climbing_diary/interfaces/multi_pitch_route/multi_pitch_route.dart'; +import 'package:climbing_diary/interfaces/single_pitch_route/single_pitch_route.dart'; +import 'package:flutter/material.dart'; +import '../../../services/pitch_service.dart'; +import '../../../services/route_service.dart'; +import 'multi_pitch_route_details.dart'; +import 'single_pitch_route_details.dart'; + +class RouteList extends StatefulWidget { + const RouteList({super.key, required this.singlePitchRoutes, required this.multiPitchRoutes}); + + final List singlePitchRoutes; + final List multiPitchRoutes; + + @override + State createState() => RouteListState(); +} + +class RouteListState extends State { + final RouteService routeService = RouteService(); + final PitchService pitchService = PitchService(); + + @override + void initState(){ + super.initState(); + } + + @override + Widget build(BuildContext context) { + List singlePitchRoutes = widget.singlePitchRoutes; + List multiPitchRoutes = widget.multiPitchRoutes; + + singlePitchRoutes.sort((a, b) => a.name.compareTo(b.name)); + multiPitchRoutes.sort((a, b) => a.name.compareTo(b.name)); + + List elements = singlePitchRoutes.map((route) => buildSinglePitchRouteList(route)).toList(); + elements += multiPitchRoutes.map((route) => buildMultiPitchRouteList(route)).toList(); + + return Column( + children: elements, + ); + } + + Widget buildSinglePitchRouteList(SinglePitchRoute route){ + return ExpansionTile( + title: Text( + route.name, + ), + children: [Padding( + padding: const EdgeInsets.only(left: 20, right: 20), + child: SinglePitchRouteDetails( + route: route, + ) + )] + ); + } + + Widget buildMultiPitchRouteList(MultiPitchRoute route){ + return ExpansionTile( + title: Text( + route.name, + ), + children: [Padding( + padding: const EdgeInsets.only(left: 20, right: 20), + child: MultiPitchRouteDetails( + route: route, + ) + )] + ); + } +} \ No newline at end of file diff --git a/src/flutter_app/lib/pages/list_page/single_pitch_route_details.dart b/src/flutter_app/lib/pages/list_page/single_pitch_route_details.dart new file mode 100644 index 0000000..bdd5a6e --- /dev/null +++ b/src/flutter_app/lib/pages/list_page/single_pitch_route_details.dart @@ -0,0 +1,220 @@ +import 'package:climbing_diary/components/add/add_ascent_to_single_pitch_route.dart'; +import 'package:climbing_diary/components/info/single_pitch_route_info.dart'; +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:skeletons/skeletons.dart'; + +import '../../components/MyButtonStyles.dart'; +import '../../interfaces/single_pitch_route/single_pitch_route.dart'; +import '../../services/media_service.dart'; +import '../../services/pitch_service.dart'; +import '../../services/route_service.dart'; + +class SinglePitchRouteDetails extends StatefulWidget { + const SinglePitchRouteDetails({super.key, required this.route}); + + final SinglePitchRoute route; + @override + State createState() => _SinglePitchRouteDetailsState(); +} + +class _SinglePitchRouteDetailsState extends State{ + final MediaService mediaService = MediaService(); + final RouteService routeService = RouteService(); + final PitchService pitchService = PitchService(); + + Future> fetchURLs() { + List> futures = []; + for (var mediaId in widget.route.mediaIds) { + futures.add(mediaService.getMediumUrl(mediaId)); + } + return Future.wait(futures); + } + + XFile? image; + final ImagePicker picker = ImagePicker(); + + Future getImage(ImageSource media) async { + var img = await picker.pickImage(source: media); + if (img != null){ + var mediaId = await mediaService.uploadMedia(img); + SinglePitchRoute route = widget.route; + route.mediaIds.add(mediaId); + routeService.editSinglePitchRoute(route.toUpdateSinglePitchRoute()); + } + + setState(() { + image = img; + }); + } + + void addImageDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + title: const Text('Please choose media to select'), + content: SizedBox( + height: MediaQuery.of(context).size.height / 6, + child: Column( + children: [ + ElevatedButton( + //if user click this button, user can upload image from gallery + onPressed: () { + Navigator.pop(context); + getImage(ImageSource.gallery); + }, + child: Row( + children: const [ + Icon(Icons.image), + Text('From Gallery'), + ], + ), + ), + ElevatedButton( + //if user click this button. user can upload image from camera + onPressed: () { + Navigator.pop(context); + getImage(ImageSource.camera); + }, + child: Row( + children: const [ + Icon(Icons.camera), + Text('From Camera'), + ], + ), + ), + ], + ), + ), + ); + }); + } + + @override + void initState(){ + super.initState(); + } + + @override + Widget build(BuildContext context) { + List elements = []; + SinglePitchRoute route = widget.route; + + // general info + elements.add(SinglePitchRouteInfo( + route: route, + )); + // rating + List ratingRowElements = []; + + for (var i = 0; i < 5; i++){ + if (route.rating > i) { + ratingRowElements.add(const Icon(Icons.favorite, size: 30.0, color: Colors.pink)); + } else { + ratingRowElements.add(const Icon(Icons.favorite, size: 30.0, color: Colors.grey)); + } + } + + elements.add(Center(child: Padding( + padding: const EdgeInsets.only(top: 10), + child:Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: ratingRowElements, + ) + ))); + + if (route.comment.isNotEmpty) { + elements.add(Container( + margin: const EdgeInsets.all(15.0), + padding: const EdgeInsets.all(5.0), + decoration: BoxDecoration( + border: Border.all(color: Colors.blueAccent), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + route.comment, + ) + )); + } + // images + if (route.mediaIds.isNotEmpty) { + List imageWidgets = []; + Future> futureMediaUrls = fetchURLs(); + + imageWidgets.add( + FutureBuilder>( + future: futureMediaUrls, + builder: (context, snapshot) { + Widget skeleton = const Padding( + padding: EdgeInsets.all(5), + child: SkeletonAvatar( + style: SkeletonAvatarStyle( + shape: BoxShape.rectangle, width: 150, height: 250 + ), + ) + ); + + if (snapshot.data != null){ + List urls = snapshot.data!; + List images = []; + for (var url in urls){ + images.add( + Padding( + padding: const EdgeInsets.all(5.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.0), + child: Image.network( + url, + fit: BoxFit.fitHeight, + loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) { + if (loadingProgress == null) { + return child; + } + return skeleton; + }, + ) + ), + ) + ); + } + return Container( + padding: const EdgeInsets.all(10), + child: Row( + mainAxisSize: MainAxisSize.min, + children: images + ) + ); + } + List skeletons = []; + for (var i = 0; i < route.mediaIds.length; i++){ + skeletons.add(skeleton); + } + return Container( + padding: const EdgeInsets.all(10), + child: Row( + mainAxisSize: MainAxisSize.min, + children: skeletons + ) + ); + } + ) + ); + elements.add( + SizedBox( + height: 250, + child: ListView( + scrollDirection: Axis.horizontal, + children: imageWidgets + ) + ), + ); + } + + return Column( + children: elements + ); + } +} \ No newline at end of file diff --git a/src/flutter_app/lib/pages/list_page/spot_details.dart b/src/flutter_app/lib/pages/list_page/spot_details.dart new file mode 100644 index 0000000..5c7f3b2 --- /dev/null +++ b/src/flutter_app/lib/pages/list_page/spot_details.dart @@ -0,0 +1,261 @@ +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:latlong2/latlong.dart'; +import 'package:skeletons/skeletons.dart'; + +import '../../components/MyButtonStyles.dart'; +import '../../components/edit/edit_spot.dart'; +import '../../interfaces/spot/spot.dart'; +import '../../interfaces/trip/trip.dart'; +import '../../services/media_service.dart'; +import '../../services/spot_service.dart'; + +class SpotDetails extends StatefulWidget { + const SpotDetails({super.key, required this.spot}); + + final Spot spot; + + @override + State createState() => _SpotDetailsState(); +} + +class _SpotDetailsState extends State{ + final MediaService mediaService = MediaService(); + final SpotService spotService = SpotService(); + + Future> fetchURLs() { + List> futures = []; + for (var mediaId in widget.spot.mediaIds) { + futures.add(mediaService.getMediumUrl(mediaId)); + } + return Future.wait(futures); + } + + XFile? image; + final ImagePicker picker = ImagePicker(); + + Future getImage(ImageSource media) async { + var img = await picker.pickImage(source: media); + if (img != null){ + var mediaId = await mediaService.uploadMedia(img); + Spot spot = widget.spot; + spot.mediaIds.add(mediaId); + spotService.editSpot(spot.toUpdateSpot()); + } + + setState(() { + image = img; + }); + } + + void addImageDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + title: const Text('Please choose media to select'), + content: SizedBox( + height: MediaQuery.of(context).size.height / 6, + child: Column( + children: [ + ElevatedButton( + //if user click this button, user can upload image from gallery + onPressed: () { + Navigator.pop(context); + getImage(ImageSource.gallery); + }, + child: Row( + children: const [ + Icon(Icons.image), + Text('From Gallery'), + ], + ), + ), + ElevatedButton( + //if user click this button. user can upload image from camera + onPressed: () { + Navigator.pop(context); + getImage(ImageSource.camera); + }, + child: Row( + children: const [ + Icon(Icons.camera), + Text('From Camera'), + ], + ), + ), + ], + ), + ), + ); + }); + } + + @override + void initState(){ + super.initState(); + } + + @override + Widget build(BuildContext context) { + List elements = []; + + // general info + elements.addAll([ + Text( + widget.spot.name, + style: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.w600 + ), + ), + Text( + '${round(widget.spot.coordinates[0], decimals: 8)}, ${round(widget.spot.coordinates[1], decimals: 8)}', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400 + ), + ), + Text( + widget.spot.location, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400 + ), + )]); + // rating + List ratingRowElements = []; + + for (var i = 0; i < 5; i++){ + if (widget.spot.rating > i) { + ratingRowElements.add(const Icon(Icons.favorite, size: 30.0, color: Colors.pink)); + } else { + ratingRowElements.add(const Icon(Icons.favorite, size: 30.0, color: Colors.grey)); + } + } + + elements.add(Center(child: Padding( + padding: const EdgeInsets.all(10), + child:Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: ratingRowElements, + ) + ))); + + // time to walk transport + elements.add(Center(child: Padding( + padding: const EdgeInsets.all(5), + child:Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + const Icon(Icons.train, size: 30.0, color: Colors.green), + Text( + '${widget.spot.distancePublicTransport} min', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400 + ), + ), + const Icon(Icons.directions_car, size: 30.0, color: Colors.red), + Text( + '${widget.spot.distanceParking} min', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400 + ), + ) + ], + ) + ))); + + if (widget.spot.comment.isNotEmpty) { + elements.add(Container( + margin: const EdgeInsets.all(15.0), + padding: const EdgeInsets.all(5.0), + decoration: BoxDecoration( + border: Border.all(color: Colors.blueAccent), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + widget.spot.comment, + ) + )); + } + // images + if (widget.spot.mediaIds.isNotEmpty) { + List imageWidgets = []; + Future> futureMediaUrls = fetchURLs(); + + imageWidgets.add( + FutureBuilder>( + future: futureMediaUrls, + builder: (context, snapshot) { + Widget skeleton = const Padding( + padding: EdgeInsets.all(5), + child: SkeletonAvatar( + style: SkeletonAvatarStyle( + shape: BoxShape.rectangle, width: 150, height: 250 + ), + ) + ); + + if (snapshot.data != null){ + List urls = snapshot.data!; + List images = []; + for (var url in urls){ + images.add( + Padding( + padding: const EdgeInsets.all(5.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.0), + child: Image.network( + url, + fit: BoxFit.fitHeight, + loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) { + if (loadingProgress == null) { + return child; + } + return skeleton; + }, + ) + ), + ) + ); + } + return Container( + padding: const EdgeInsets.all(10), + child: Row( + mainAxisSize: MainAxisSize.min, + children: images + ) + ); + } + List skeletons = []; + for (var i = 0; i < widget.spot.mediaIds.length; i++){ + skeletons.add(skeleton); + } + return Container( + padding: const EdgeInsets.all(10), + child: Row( + mainAxisSize: MainAxisSize.min, + children: skeletons + ) + ); + } + ) + ); + elements.add( + SizedBox( + height: 250, + child: ListView( + scrollDirection: Axis.horizontal, + children: imageWidgets + ) + ), + ); + } + return Column(children: elements); + } +} \ No newline at end of file diff --git a/src/flutter_app/lib/pages/list_page/spot_list.dart b/src/flutter_app/lib/pages/list_page/spot_list.dart index bb525b0..212cabf 100644 --- a/src/flutter_app/lib/pages/list_page/spot_list.dart +++ b/src/flutter_app/lib/pages/list_page/spot_list.dart @@ -1,13 +1,9 @@ +import 'spot_details.dart'; import 'package:flutter/material.dart'; import 'package:internet_connection_checker/internet_connection_checker.dart'; -import 'package:timelines/timelines.dart'; import '../../../interfaces/spot/spot.dart'; import '../../../services/spot_service.dart'; -import '../../components/detail/spot_details.dart'; -import '../../components/info/spot_info.dart'; -import '../diary_page/image_list_view.dart'; -import '../diary_page/rating_row.dart'; class SpotList extends StatefulWidget { const SpotList({super.key, required this.spots}); @@ -24,98 +20,27 @@ class SpotListState extends State { @override void initState(){ super.initState(); - checkConnection(); } @override Widget build(BuildContext context) { List spots = widget.spots; spots.sort((a, b) => a.name.compareTo(b.name)); - - updateSpotCallback(Spot spot) { - var index = -1; - for (int i = 0; i < spots.length; i++) { - if (spots[i].id == spot.id) { - index = i; - } - } - spots.removeAt(index); - spots.add(spot); - setState(() {}); - } - return Column( - children: [ - FixedTimeline.tileBuilder( - theme: TimelineThemeData( - nodePosition: 0, - color: const Color(0xff989898), - indicatorTheme: const IndicatorThemeData( - position: 0, - size: 20.0, - ), - connectorTheme: const ConnectorThemeData( - thickness: 2.5, - ), - ), - builder: TimelineTileBuilder.connected( - connectionDirection: ConnectionDirection.before, - itemCount: spots.length, - contentsBuilder: (_, index) { - List elements = []; - // spot info - elements.add(SpotInfo(spot: spots[index])); - // rating as hearts in a row - elements.add(RatingRow(rating: spots[index].rating)); - // images list view - if (spots[index].mediaIds.isNotEmpty) { - elements.add( - ImageListView(mediaIds: spots[index].mediaIds) - ); - } - return InkWell( - onTap: () => - showDialog( - context: context, - builder: (BuildContext context) => - Dialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - child: SpotDetails( - spot: spots[index], - onDelete: (Spot value) { }, - onUpdate: (Spot value) { }, - ) - ), - ), - child: Ink( - child: Padding( - padding: const EdgeInsets.only(left: 8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: elements, - ), - ) - ), - ); - }, - indicatorBuilder: (_, index) { - return const OutlinedDotIndicator( - borderWidth: 2.5, - color: Color(0xff66c97f), - ); - }, - connectorBuilder: (_, index, ___) => - const SolidLineConnector(color: Color(0xff66c97f)), - ), - ) - ], + children: spots.map((spot) => buildList(spot)).toList(), ); } - Future checkConnection() async { - return await InternetConnectionChecker().hasConnection; + Widget buildList(Spot spot){ + return ExpansionTile( + title: Text( + spot.name, + ), + children: [Padding( + padding: const EdgeInsets.only(left: 20, right: 20), + child: SpotDetails( + spot: spot, + ))] + ); } } \ No newline at end of file diff --git a/src/flutter_app/lib/pages/map_page/route_list.dart b/src/flutter_app/lib/pages/map_page/route_list.dart index 3485d6f..a10b934 100644 --- a/src/flutter_app/lib/pages/map_page/route_list.dart +++ b/src/flutter_app/lib/pages/map_page/route_list.dart @@ -189,7 +189,6 @@ class RouteListState extends State { // route info SinglePitchRoute singlePitchRoute = singlePitchRoutes[index]; elements.add(SinglePitchRouteInfo( - spot: widget.spot, route: singlePitchRoute )); // rating as hearts in a row diff --git a/src/flutter_app/lib/services/pitch_service.dart b/src/flutter_app/lib/services/pitch_service.dart index 5727dca..3557380 100644 --- a/src/flutter_app/lib/services/pitch_service.dart +++ b/src/flutter_app/lib/services/pitch_service.dart @@ -60,6 +60,37 @@ class PitchService { } } + Future> getPitchesByName(String name) async { + if (name == ""){ + return []; + } + try { + final Response response = await netWorkLocator.dio.get('$climbingApiHost/pitch'); + + if (response.statusCode == 200) { + // If the server did return a 200 OK response, then parse the JSON. + List pitches = []; + response.data.forEach((s) { + Pitch pitch = Pitch.fromJson(s); + if (pitch.name.contains(name)) { + pitches.add(pitch); + } + }); + return pitches; + } + } catch (e) { + if (e is DioError) { + if (e.error.toString().contains("OS Error: Connection refused, errno = 111")){ + showSimpleNotification( + const Text('Couldn\'t connect to API'), + background: Colors.red, + ); + } + } + } + return []; + } + Future createPitch(CreatePitch createPitch, String routeId, bool hasConnection) async { CreatePitch pitch = CreatePitch( comment: (createPitch.comment != null) ? createPitch.comment! : "", diff --git a/src/flutter_app/lib/services/route_service.dart b/src/flutter_app/lib/services/route_service.dart index 5d69ed0..9050ef7 100644 --- a/src/flutter_app/lib/services/route_service.dart +++ b/src/flutter_app/lib/services/route_service.dart @@ -70,6 +70,25 @@ class RouteService { } } + Future> getMultiPitchRoutesByName(String name) async { + if (name == ""){ + return []; + } + final Response response = await netWorkLocator.dio.get('$climbingApiHost/multi_pitch_route'); + if (response.statusCode == 200) { + List routes = []; + response.data.forEach((r) { + MultiPitchRoute route = MultiPitchRoute.fromJson(r); + if (route.name.contains(name)) { + routes.add(route); + } + }); + return routes; + } else { + throw Exception('Failed to load route'); + } + } + Future getMultiPitchRouteIfWithinDateRange(String routeId, DateTime startDate, DateTime endDate) async { final Response response = await netWorkLocator.dio.get('$climbingApiHost/multi_pitch_route/$routeId'); if (response.statusCode == 200) { @@ -291,6 +310,25 @@ class RouteService { } } + Future> getSinglePitchRoutesByName(String name) async { + if (name == ""){ + return []; + } + final Response response = await netWorkLocator.dio.get('$climbingApiHost/single_pitch_route'); + if (response.statusCode == 200) { + List routes = []; + response.data.forEach((r) { + SinglePitchRoute route = SinglePitchRoute.fromJson(r); + if (route.name.contains(name)) { + routes.add(route); + } + }); + return routes; + } else { + throw Exception('Failed to load route'); + } + } + Future getSinglePitchRouteIfWithinDateRange(String routeId, DateTime startDate, DateTime endDate) async { final Response response = await netWorkLocator.dio.get('$climbingApiHost/single_pitch_route/$routeId'); if (response.statusCode == 200) { From 95909040fc214c0c2bfa4edcabfd03f1e4471e7a Mon Sep 17 00:00:00 2001 From: florianpix Date: Fri, 30 Jun 2023 13:42:03 +0200 Subject: [PATCH 10/13] fixed multi pitch route search; --- .../pages/list_page/multi_pitch_route_details.dart | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/flutter_app/lib/pages/list_page/multi_pitch_route_details.dart b/src/flutter_app/lib/pages/list_page/multi_pitch_route_details.dart index af3f02c..774309b 100644 --- a/src/flutter_app/lib/pages/list_page/multi_pitch_route_details.dart +++ b/src/flutter_app/lib/pages/list_page/multi_pitch_route_details.dart @@ -226,15 +226,8 @@ class _MultiPitchRouteDetailsState extends State{ ), ); } - return Stack( - children: [ - Padding( - padding: const EdgeInsets.all(20), - child: ListView( - children: elements - ) - ) - ] + return Column( + children: elements ); } } \ No newline at end of file From b025f68dbef4a884d415850b78f33c4e2c8e7fef Mon Sep 17 00:00:00 2001 From: florianpix Date: Fri, 30 Jun 2023 13:47:54 +0200 Subject: [PATCH 11/13] remove gap in trip info in case that no comment was provided; --- src/flutter_app/lib/components/info/trip_info.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/flutter_app/lib/components/info/trip_info.dart b/src/flutter_app/lib/components/info/trip_info.dart index ec8803c..bc46cb3 100644 --- a/src/flutter_app/lib/components/info/trip_info.dart +++ b/src/flutter_app/lib/components/info/trip_info.dart @@ -25,10 +25,12 @@ class TripInfo extends StatelessWidget { style: MyTextStyles.description, )); - listInfo.add(Text( - trip.comment, - style: MyTextStyles.description, - )); + if (trip.comment != ""){ + listInfo.add(Text( + trip.comment, + style: MyTextStyles.description, + )); + } return Column( crossAxisAlignment: CrossAxisAlignment.start, From 3fe04538e15089fb56daddc3d38211e00be336b1 Mon Sep 17 00:00:00 2001 From: florianpix Date: Fri, 30 Jun 2023 14:21:41 +0200 Subject: [PATCH 12/13] fixed error in grade; expansion tile for timelines; --- src/flutter_app/lib/interfaces/grade.dart | 14 +++++++------- .../pages/diary_page/timeline/pitch_timeline.dart | 6 ++++-- .../pages/diary_page/timeline/route_timeline.dart | 6 +++++- .../pages/diary_page/timeline/spot_timeline.dart | 9 +++++---- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/flutter_app/lib/interfaces/grade.dart b/src/flutter_app/lib/interfaces/grade.dart index ee74f80..3d0c98c 100644 --- a/src/flutter_app/lib/interfaces/grade.dart +++ b/src/flutter_app/lib/interfaces/grade.dart @@ -14,13 +14,13 @@ class Grade{ ['5.1', '5.1', '5.1', '5.1', '5.2', '5.3', '5.4', '5.6', '5.7', '5.7', '5.8', '5.9', '5.10a', '5.10b', '5.10c', '5.10d', '5.11a', '5.11b', '5.11c', '5.11d', '5.12a', '5.12b', '5.12c', '5.12d', '5.13a', '5.13b', '5.13c', '5.13c', '5.13d', '5.14a', '5.14b', '5.14c', '5.14d', '5.14d', '5.15a', '5.15a', '5.15b', '5.15c', '5.15d'], // USA-YDS ['3', '3', '3', '3', '4a', '4a', '4a', '4a', '4b', '4c', '4c', '5a', '5a', '5b', '5b', '5c', '5c', '5c', '6a', '6a', '6a', '6b', '6b', '6c', '6c', '6c', '7a', '7a', '7a', '7a', '7b', '7b', '7c', '7c', '7c', '7c', '7c', '7c', '7c'], // UK-Tech ['VD', 'VD', 'VD', 'VD', 'VD', 'S', 'S', 'S', 'HS', 'HS', 'VS', 'HVS', 'E1', 'E1', 'E2', 'E2', 'E3', 'E3', 'E4', 'E4', 'E5', 'E5', 'E6', 'E6', 'E7', 'E7', 'E8', 'E8', 'E9', 'E9', 'E10', 'E11', 'E12', 'E12', 'E12', 'E12', 'E12', 'E12', 'E12'], // UK-Adj - ['1', '2', '3', '3', '4a', '4b', '4c', '5a', '5a', '5b', '5b', '5c', '6a', '6a+', '6b''6b+', '6c', '6c+', '7a', '7a+', '7b', '7b+', '7c', '7c+', '8a', '8a', '8a+', '8a+''8b', '8b+', '8c', '8c+', '9a', '9a', '9a+', '9a+', '9b', '9b+', '9c'], // French - ['I', 'II', 'III', 'III+', 'IV-', 'IV', 'IV+', 'Vāˆ’', 'V', 'V+', 'VI-', 'VI', 'VI+''VII-', 'VII', 'VII+', 'VII+', 'VIII-', 'VIII', 'VIII+', 'VIII+', 'IX-', 'IX''IX+', 'IX+', 'X-', 'X-', 'X-', 'X', 'X+', 'XI-', 'XI-', 'XI', 'XI', 'XI+', 'XI+''XII-', 'XII-', 'XII'], // UIAA - ['11', '11', '12', '12', '12', '12', '12', '13', '14', '15', '16', '17', '18', '19''20', '21', '22', '23', '24', '25', '26', '26', '27', '28', '29', '29', '30', '30''31', '32', '33', '34', '35', '35', '35', '35', '35', '35', '35'], // Australien - ['I', 'II', 'III', '', 'IV', 'IV', 'V', 'V', 'VI', 'VIIa', 'VIIa', 'VIIb', 'VIIc''VIIIa', 'VIIIb', 'VIIIc', 'VIIIc', 'IXa', 'IXb', 'IXc', 'IXc', 'Xa', 'Xb', 'Xc''Xc', 'Xc', 'XIa', 'XIa', 'XIb', 'XIc', 'XIIa', 'XIIa', 'XIIb', 'XIIb', 'XIIb''XIIb', 'XIIb', 'XIIb', 'XIIb'], // Sachsen - ['1', '2', '3', '3+', '4-', '4', '4+', '5āˆ’', '5', '5', '5+', '5+', '6-', '6-', '6', '6''6+', '6+', '7-', '7', '7+', '8-', '8', '8+', '9-', '9', '9+', '9+', '10-', '10''10+', '11-', '11', '11', '11+', '11+', '11+', '11+', '11+'], // Skandinavien - ['Isup', 'II', 'IIsup', '', 'III', 'IIIsup', 'III', 'IIIsup', 'IV', 'IV', 'IVsup''V', 'Vsup', 'VI', 'VI', 'VIsup', 'VIIa', 'VIIa', 'VIIb', 'VIIc', 'VIIIa''VIIIb', 'VIIIc', 'IXa', 'IXb', 'IXc', 'Xa', 'Xa', 'Xb', 'Xc', 'XIa', 'XIa', 'XIa''XIa', 'XIa', 'XIa', 'XIa', 'XIa', 'XIa'], // Brasilien - ['1', '1', '2', '2', '3', '3', '3', '3', '4a', '4a', '4a', '4b', '4b', '4b', '4b', '4c''5a', '5a', '5b', '5c', '6a', '6b', '6c', '7a', '7a+', '7b', '7b+', '7c', '7c+''8a', '8a+', '8b', '8b+', '8c', '8c+', '8c+', '8c+', '9a', '9a'], //Fb + ['1', '2', '3', '3', '4a', '4b', '4c', '5a', '5a', '5b', '5b', '5c', '6a', '6a+', '6b', '6b+', '6c', '6c+', '7a', '7a+', '7b', '7b+', '7c', '7c+', '8a', '8a', '8a+', '8a+''8b', '8b+', '8c', '8c+', '9a', '9a', '9a+', '9a+', '9b', '9b+', '9c'], // French + ['I', 'II', 'III', 'III+', 'IV-', 'IV', 'IV+', 'Vāˆ’', 'V', 'V+', 'VI-', 'VI', 'VI+', 'VII-', 'VII', 'VII+', 'VII+', 'VIII-', 'VIII', 'VIII+', 'VIII+', 'IX-', 'IX''IX+', 'IX+', 'X-', 'X-', 'X-', 'X', 'X+', 'XI-', 'XI-', 'XI', 'XI', 'XI+', 'XI+''XII-', 'XII-', 'XII'], // UIAA + ['11', '11', '12', '12', '12', '12', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '26', '27', '28', '29', '29', '30', '30''31', '32', '33', '34', '35', '35', '35', '35', '35', '35', '35'], // Australien + ['I', 'II', 'III', '', 'IV', 'IV', 'V', 'V', 'VI', 'VIIa', 'VIIa', 'VIIb', 'VIIc', 'VIIIa', 'VIIIb', 'VIIIc', 'VIIIc', 'IXa', 'IXb', 'IXc', 'IXc', 'Xa', 'Xb', 'Xc''Xc', 'Xc', 'XIa', 'XIa', 'XIb', 'XIc', 'XIIa', 'XIIa', 'XIIb', 'XIIb', 'XIIb''XIIb', 'XIIb', 'XIIb', 'XIIb'], // Sachsen + ['1', '2', '3', '3+', '4-', '4', '4+', '5āˆ’', '5', '5', '5+', '5+', '6-', '6-', '6', '6', '6+', '6+', '7-', '7', '7+', '8-', '8', '8+', '9-', '9', '9+', '9+', '10-', '10''10+', '11-', '11', '11', '11+', '11+', '11+', '11+', '11+'], // Skandinavien + ['Isup', 'II', 'IIsup', '', 'III', 'IIIsup', 'III', 'IIIsup', 'IV', 'IV', 'IVsup', 'V', 'Vsup', 'VI', 'VI', 'VIsup', 'VIIa', 'VIIa', 'VIIb', 'VIIc', 'VIIIa''VIIIb', 'VIIIc', 'IXa', 'IXb', 'IXc', 'Xa', 'Xa', 'Xb', 'Xc', 'XIa', 'XIa', 'XIa''XIa', 'XIa', 'XIa', 'XIa', 'XIa', 'XIa'], // Brasilien + ['1', '1', '2', '2', '3', '3', '3', '3', '4a', '4a', '4a', '4b', '4b', '4b', '4b', '4c', '5a', '5a', '5b', '5c', '6a', '6b', '6c', '7a', '7a+', '7b', '7b+', '7c', '7c+''8a', '8a+', '8b', '8b+', '8c', '8c+', '8c+', '8c+', '9a', '9a'], //Fb ]; Grade translate(GradingSystem gs){ diff --git a/src/flutter_app/lib/pages/diary_page/timeline/pitch_timeline.dart b/src/flutter_app/lib/pages/diary_page/timeline/pitch_timeline.dart index 6b59c32..a461b70 100644 --- a/src/flutter_app/lib/pages/diary_page/timeline/pitch_timeline.dart +++ b/src/flutter_app/lib/pages/diary_page/timeline/pitch_timeline.dart @@ -72,7 +72,9 @@ class PitchTimelineState extends State { setState(() {}); } - return Column( + return Column(children: [ExpansionTile( + leading: const Icon(Icons.commit), + title: const Text("pitches"), children: [ FixedTimeline.tileBuilder( theme: TimelineThemeData( @@ -137,7 +139,7 @@ class PitchTimelineState extends State { const SolidLineConnector(color: Color(0xff66c97f)), ), ) - ], + ])], ); } else { return const CircularProgressIndicator(); diff --git a/src/flutter_app/lib/pages/diary_page/timeline/route_timeline.dart b/src/flutter_app/lib/pages/diary_page/timeline/route_timeline.dart index 82daf77..1d77e6d 100644 --- a/src/flutter_app/lib/pages/diary_page/timeline/route_timeline.dart +++ b/src/flutter_app/lib/pages/diary_page/timeline/route_timeline.dart @@ -242,7 +242,11 @@ class RouteTimelineState extends State { ); } return Column( - children: elements, + children: [ExpansionTile( + leading: const Icon(Icons.route), + title: const Text("routes"), + children: elements, + )], ); } else { return const CircularProgressIndicator(); diff --git a/src/flutter_app/lib/pages/diary_page/timeline/spot_timeline.dart b/src/flutter_app/lib/pages/diary_page/timeline/spot_timeline.dart index 04528c2..94be511 100644 --- a/src/flutter_app/lib/pages/diary_page/timeline/spot_timeline.dart +++ b/src/flutter_app/lib/pages/diary_page/timeline/spot_timeline.dart @@ -6,7 +6,6 @@ import 'package:timelines/timelines.dart'; import '../../../components/info/spot_info.dart'; import '../../../interfaces/spot/spot.dart'; import '../../../interfaces/trip/trip.dart'; -import '../../../interfaces/trip/update_trip.dart'; import '../../../services/spot_service.dart'; import '../../../services/trip_service.dart'; import '../image_list_view.dart'; @@ -69,7 +68,9 @@ class SpotTimelineState extends State { setState(() {}); } - return Column( + return Column(children: [ExpansionTile( + leading: const Icon(Icons.place), + title: const Text("spots"), children: [ FixedTimeline.tileBuilder( theme: TimelineThemeData( @@ -148,8 +149,8 @@ class SpotTimelineState extends State { connectorBuilder: (_, index, ___) => const SolidLineConnector(color: Color(0xff66c97f)), ), - ) - ], + )] + )], ); } else { return const CircularProgressIndicator(); From 3afa79f9d30074220f277b02d03d01d7cc17ec7c Mon Sep 17 00:00:00 2001 From: florianpix Date: Fri, 30 Jun 2023 14:30:59 +0200 Subject: [PATCH 13/13] minor; --- .../lib/components/add/add_ascent.dart | 31 ++++++------------- .../lib/components/detail/pitch_details.dart | 2 +- .../lib/components/info/ascent_info.dart | 2 +- .../lib/components/info/pitch_info.dart | 2 +- .../lib/components/info/route_info.dart | 2 +- .../info/single_pitch_route_info.dart | 2 +- 6 files changed, 14 insertions(+), 27 deletions(-) diff --git a/src/flutter_app/lib/components/add/add_ascent.dart b/src/flutter_app/lib/components/add/add_ascent.dart index cc2e461..6ebcd71 100644 --- a/src/flutter_app/lib/components/add/add_ascent.dart +++ b/src/flutter_app/lib/components/add/add_ascent.dart @@ -9,11 +9,13 @@ import '../../interfaces/pitch/pitch.dart'; import '../../services/ascent_service.dart'; import 'package:internet_connection_checker/internet_connection_checker.dart'; +import '../MyTextStyles.dart'; + class AddAscent extends StatefulWidget { - const AddAscent({super.key, required this.pitches, this.onAdd}); + const AddAscent({super.key, required this.pitch, this.onAdd}); final ValueSetter? onAdd; - final List pitches; + final Pitch pitch; @override State createState() => _AddAscentState(); @@ -26,13 +28,11 @@ class _AddAscentState extends State{ final TextEditingController controllerComment = TextEditingController(); final TextEditingController controllerDate = TextEditingController(); - Pitch? pitchValue; AscentStyle? ascentStyleValue; AscentType? ascentTypeValue; @override void initState(){ - pitchValue = widget.pitches[0]; ascentStyleValue = AscentStyle.lead; ascentTypeValue = AscentType.redPoint; controllerDate.text = DateFormat('yyyy-MM-dd').format(DateTime.now()); @@ -52,19 +52,9 @@ class _AddAscentState extends State{ child: Column( mainAxisSize: MainAxisSize.min, children: [ - DropdownButton( - value: pitchValue, - items: widget.pitches.map>((Pitch pitch) { - return DropdownMenuItem( - value: pitch, - child: Text(pitch.name), - ); - }).toList(), - onChanged: (Pitch? pitch) { - setState(() { - pitchValue = pitch!; - }); - } + Text( + widget.pitch.name, + style: MyTextStyles.title, ), TextFormField( controller: controllerComment, @@ -141,11 +131,8 @@ class _AddAscentState extends State{ type: ascentTypeIndex, ); Navigator.popUntil(context, ModalRoute.withName('/')); - final pitchValue = this.pitchValue; - if (pitchValue != null) { - Ascent? createdAscent = await ascentService.createAscent(pitchValue.id, ascent, result); - widget.onAdd?.call(createdAscent!); - } + Ascent? createdAscent = await ascentService.createAscent(widget.pitch.id, ascent, result); + widget.onAdd?.call(createdAscent!); } } }, diff --git a/src/flutter_app/lib/components/detail/pitch_details.dart b/src/flutter_app/lib/components/detail/pitch_details.dart index ac91158..2f7aed4 100644 --- a/src/flutter_app/lib/components/detail/pitch_details.dart +++ b/src/flutter_app/lib/components/detail/pitch_details.dart @@ -254,7 +254,7 @@ class _PitchDetailsState extends State{ context, MaterialPageRoute( builder: (context) => AddAscent( - pitches: [widget.pitch], + pitch: widget.pitch, onAdd: (ascent) { widget.pitch.ascentIds.add(ascent.id); setState(() {}); diff --git a/src/flutter_app/lib/components/info/ascent_info.dart b/src/flutter_app/lib/components/info/ascent_info.dart index c0e5338..28ad6c3 100644 --- a/src/flutter_app/lib/components/info/ascent_info.dart +++ b/src/flutter_app/lib/components/info/ascent_info.dart @@ -18,7 +18,7 @@ class AscentInfo extends StatelessWidget { // date, style and type listInfo.add(Text( - "${ascent.date} ${AscentStyle.values[ascent.style].toEmoji()} ${AscentType.values[ascent.type].toEmoji()}", + "${ascent.date} ${AscentStyle.values[ascent.style].toEmoji()}${AscentType.values[ascent.type].toEmoji()}", style: MyTextStyles.title, )); diff --git a/src/flutter_app/lib/components/info/pitch_info.dart b/src/flutter_app/lib/components/info/pitch_info.dart index 7fa7c81..6a65151 100644 --- a/src/flutter_app/lib/components/info/pitch_info.dart +++ b/src/flutter_app/lib/components/info/pitch_info.dart @@ -51,7 +51,7 @@ class _PitchInfoState extends State{ List listInfo = []; String title = widget.pitch.name; if (displayedAscent != null) { - title += " ${AscentStyle.values[displayedAscent.style].toEmoji()} ${AscentType.values[displayedAscent.type].toEmoji()}"; + title += " ${AscentStyle.values[displayedAscent.style].toEmoji()}${AscentType.values[displayedAscent.type].toEmoji()}"; } // name diff --git a/src/flutter_app/lib/components/info/route_info.dart b/src/flutter_app/lib/components/info/route_info.dart index b68895a..f870827 100644 --- a/src/flutter_app/lib/components/info/route_info.dart +++ b/src/flutter_app/lib/components/info/route_info.dart @@ -37,7 +37,7 @@ class _RouteInfoState extends State{ List listInfo = []; String title = widget.route.name; if (ascent != null) { - title += " ${AscentStyle.values[ascent.style].toEmoji()} ${AscentType.values[ascent.type].toEmoji()}"; + title += " ${AscentStyle.values[ascent.style].toEmoji()}${AscentType.values[ascent.type].toEmoji()}"; } listInfo.add(Text( diff --git a/src/flutter_app/lib/components/info/single_pitch_route_info.dart b/src/flutter_app/lib/components/info/single_pitch_route_info.dart index 28a9f31..ba55613 100644 --- a/src/flutter_app/lib/components/info/single_pitch_route_info.dart +++ b/src/flutter_app/lib/components/info/single_pitch_route_info.dart @@ -54,7 +54,7 @@ class _SinglePitchRouteInfoState extends State{ List listInfo = []; String title = widget.route.name; if (displayedAscent != null) { - title += " ${AscentStyle.values[displayedAscent.style].toEmoji()} ${AscentType.values[displayedAscent.type].toEmoji()}"; + title += " ${AscentStyle.values[displayedAscent.style].toEmoji()}${AscentType.values[displayedAscent.type].toEmoji()}"; } listInfo.add(Text(