Frontend UX improvements #37
| @@ -5,7 +5,6 @@ import 'package:flutter/material.dart'; | |||||||
| import 'package:anyway/modules/landmark_card.dart'; | import 'package:anyway/modules/landmark_card.dart'; | ||||||
| import 'package:anyway/structs/landmark.dart'; | import 'package:anyway/structs/landmark.dart'; | ||||||
| import 'package:anyway/structs/trip.dart'; | import 'package:anyway/structs/trip.dart'; | ||||||
| import 'package:anyway/main.dart'; |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -25,30 +24,7 @@ List<Widget> landmarksList(Trip trip) { | |||||||
|  |  | ||||||
|   for (Landmark landmark in trip.landmarks) { |   for (Landmark landmark in trip.landmarks) { | ||||||
|     children.add( |     children.add( | ||||||
|       Dismissible( |       LandmarkCard(landmark, trip), | ||||||
|         key: ValueKey<int>(landmark.hashCode), |  | ||||||
|         child: LandmarkCard(landmark), |  | ||||||
|         dismissThresholds: {DismissDirection.endToStart: 0.95, DismissDirection.startToEnd: 0.95}, |  | ||||||
|         onDismissed: (direction) { |  | ||||||
|           log('Removing ${landmark.name}'); |  | ||||||
|           trip.removeLandmark(landmark); |  | ||||||
|  |  | ||||||
|           rootScaffoldMessengerKey.currentState!.showSnackBar( |  | ||||||
|             SnackBar(content: Text("We won't show ${landmark.name} again")) |  | ||||||
|           ); |  | ||||||
|         }, |  | ||||||
|  |  | ||||||
|         background: Container(color: Colors.red), |  | ||||||
|         secondaryBackground: Container( |  | ||||||
|           color: Colors.red, |  | ||||||
|           child: Icon( |  | ||||||
|             Icons.delete, |  | ||||||
|             color: Colors.white, |  | ||||||
|           ), |  | ||||||
|           padding: EdgeInsets.all(15), |  | ||||||
|           alignment: Alignment.centerRight, |  | ||||||
|         ), |  | ||||||
|       ) |  | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     if (landmark.next != null) { |     if (landmark.next != null) { | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | import 'package:anyway/main.dart'; | ||||||
|  | import 'package:anyway/structs/trip.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:cached_network_image/cached_network_image.dart'; | import 'package:cached_network_image/cached_network_image.dart'; | ||||||
| import 'package:url_launcher/url_launcher.dart'; | import 'package:url_launcher/url_launcher.dart'; | ||||||
| @@ -6,8 +8,12 @@ import 'package:anyway/structs/landmark.dart'; | |||||||
|  |  | ||||||
| class LandmarkCard extends StatefulWidget { | class LandmarkCard extends StatefulWidget { | ||||||
|   final Landmark landmark; |   final Landmark landmark; | ||||||
|  |   final Trip parentTrip; | ||||||
|    |    | ||||||
|   LandmarkCard(this.landmark); |   LandmarkCard( | ||||||
|  |     this.landmark, | ||||||
|  |     this.parentTrip, | ||||||
|  |     ); | ||||||
|    |    | ||||||
|   @override |   @override | ||||||
|   _LandmarkCardState createState() => _LandmarkCardState(); |   _LandmarkCardState createState() => _LandmarkCardState(); | ||||||
| @@ -18,106 +24,150 @@ class _LandmarkCardState extends State<LandmarkCard> { | |||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return Container( |     return Container( | ||||||
|       height: 160, |  | ||||||
|       child: Card( |       child: Card( | ||||||
|         shape: RoundedRectangleBorder( |         shape: RoundedRectangleBorder( | ||||||
|           borderRadius: BorderRadius.circular(15.0), |           borderRadius: BorderRadius.circular(15.0), | ||||||
|         ), |         ), | ||||||
|         elevation: 5, |         elevation: 5, | ||||||
|         clipBehavior: Clip.antiAliasWithSaveLayer, |         clipBehavior: Clip.antiAliasWithSaveLayer, | ||||||
|         child: Row( |         // if the image is available, display it on the left side of the card, otherwise only display the text | ||||||
|           crossAxisAlignment: CrossAxisAlignment.start, |         child: widget.landmark.imageURL != null ? splitLayout() : textLayout(), | ||||||
|           children: [ |       ), | ||||||
|             Container( // the image on the left |     ); | ||||||
|               // inherit the height of the parent container |   } | ||||||
|               height: double.infinity, |  | ||||||
|               // force a fixed width |   Widget splitLayout() { | ||||||
|               width: 160, |     // If an image is available, display it on the left side of the card | ||||||
|               child: CachedNetworkImage( |     return Row( | ||||||
|                 imageUrl: widget.landmark.imageURL ?? '', |       crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|                 placeholder: (context, url) => Center(child: CircularProgressIndicator()), |       children: [ | ||||||
|                 errorWidget: (context, error, stackTrace) => Icon(Icons.question_mark_outlined), |         Container( | ||||||
|                 fit: BoxFit.cover, |           // the image on the left | ||||||
|               ), |           width: 160, | ||||||
|             ), |           height: 160, | ||||||
|             Flexible( |  | ||||||
|               child: Padding( |           child: CachedNetworkImage( | ||||||
|                 padding: EdgeInsets.all(10), |             imageUrl: widget.landmark.imageURL ?? '', | ||||||
|                 child: Column( |             placeholder: (context, url) => Center(child: CircularProgressIndicator()), | ||||||
|                   children: [ |             errorWidget: (context, error, stackTrace) => Icon(Icons.question_mark_outlined), | ||||||
|                     Row( |             fit: BoxFit.cover, | ||||||
|                       children: [ |           ), | ||||||
|                         Flexible( |  | ||||||
|                           child: Text( |  | ||||||
|                             widget.landmark.name, |  | ||||||
|                             style: const TextStyle( |  | ||||||
|                               fontSize: 18, |  | ||||||
|                               fontWeight: FontWeight.bold, |  | ||||||
|                             ), |  | ||||||
|                             maxLines: 2, |  | ||||||
|                           ), |  | ||||||
|                         ) |  | ||||||
|                       ], |  | ||||||
|                     ), |  | ||||||
|                     if (widget.landmark.nameEN != null) |  | ||||||
|                       Row( |  | ||||||
|                         children: [ |  | ||||||
|                           Flexible( |  | ||||||
|                             child: Text( |  | ||||||
|                               widget.landmark.nameEN!, |  | ||||||
|                               style: const TextStyle( |  | ||||||
|                                 fontSize: 16, |  | ||||||
|                               ), |  | ||||||
|                               maxLines: 1, |  | ||||||
|                             ), |  | ||||||
|                           ) |  | ||||||
|                         ], |  | ||||||
|                       ), |  | ||||||
|                     SingleChildScrollView( |  | ||||||
|                       // allows the buttons to be scrolled |  | ||||||
|                       scrollDirection: Axis.horizontal, |  | ||||||
|                       child: Wrap( |  | ||||||
|                         spacing: 10, |  | ||||||
|                         // show the type, the website, and the wikipedia link as buttons/labels in a row |  | ||||||
|                         children: [ |  | ||||||
|                           TextButton.icon( |  | ||||||
|                             onPressed: () {}, |  | ||||||
|                             icon: widget.landmark.type.icon, |  | ||||||
|                             label: Text(widget.landmark.type.name), |  | ||||||
|                           ), |  | ||||||
|                           if (widget.landmark.duration != null && widget.landmark.duration!.inMinutes > 0) |  | ||||||
|                             TextButton.icon( |  | ||||||
|                               onPressed: () {}, |  | ||||||
|                               icon: Icon(Icons.hourglass_bottom), |  | ||||||
|                               label: Text('${widget.landmark.duration!.inMinutes} minutes'), |  | ||||||
|                             ), |  | ||||||
|                           if (widget.landmark.websiteURL != null) |  | ||||||
|                             TextButton.icon( |  | ||||||
|                               onPressed: () async { |  | ||||||
|                                 // open a browser with the website link |  | ||||||
|                                 await launchUrl(Uri.parse(widget.landmark.websiteURL!)); |  | ||||||
|                               }, |  | ||||||
|                               icon: Icon(Icons.link), |  | ||||||
|                               label: Text('Website'), |  | ||||||
|                             ), |  | ||||||
|                           // if (widget.landmark.wikipediaURL != null) |  | ||||||
|                           //   TextButton.icon( |  | ||||||
|                           //     onPressed: () async { |  | ||||||
|                           //       // open a browser with the wikipedia link |  | ||||||
|                           //       await launchUrl(Uri.parse(widget.landmark.wikipediaURL!)); |  | ||||||
|                           //     }, |  | ||||||
|                           //     icon: Icon(Icons.book), |  | ||||||
|                           //   label: Text('Wikipedia'), |  | ||||||
|                           // ), |  | ||||||
|                         ], |  | ||||||
|                       ), |  | ||||||
|                     ), |  | ||||||
|                   ], |  | ||||||
|                 ), |  | ||||||
|               ), |  | ||||||
|             ), |  | ||||||
|           ], |  | ||||||
|         ), |         ), | ||||||
|  |         Flexible( | ||||||
|  |           child: textLayout(), | ||||||
|  |         ), | ||||||
|  |       ], | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Widget textLayout() { | ||||||
|  |     return Padding( | ||||||
|  |       padding: EdgeInsets.all(10), | ||||||
|  |       child: Column( | ||||||
|  |         children: [ | ||||||
|  |           Row( | ||||||
|  |             children: [ | ||||||
|  |               Flexible( | ||||||
|  |                 child: Text( | ||||||
|  |                   widget.landmark.name, | ||||||
|  |                   style: const TextStyle( | ||||||
|  |                     fontSize: 18, | ||||||
|  |                     fontWeight: FontWeight.bold, | ||||||
|  |                   ), | ||||||
|  |                   maxLines: 2, | ||||||
|  |                 ), | ||||||
|  |               ) | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|  |           if (widget.landmark.nameEN != null) | ||||||
|  |             Row( | ||||||
|  |               children: [ | ||||||
|  |                 Flexible( | ||||||
|  |                   child: Text( | ||||||
|  |                     widget.landmark.nameEN!, | ||||||
|  |                     style: const TextStyle( | ||||||
|  |                       fontSize: 16, | ||||||
|  |                     ), | ||||||
|  |                     maxLines: 1, | ||||||
|  |                   ), | ||||||
|  |                 ) | ||||||
|  |               ], | ||||||
|  |             ), | ||||||
|  |           SingleChildScrollView( | ||||||
|  |             // allows the buttons to be scrolled | ||||||
|  |             scrollDirection: Axis.horizontal, | ||||||
|  |             child: Wrap( | ||||||
|  |               spacing: 10, | ||||||
|  |               // show the type, the website, and the wikipedia link as buttons/labels in a row | ||||||
|  |               children: [ | ||||||
|  |                 TextButton.icon( | ||||||
|  |                   onPressed: () {}, | ||||||
|  |                   icon: widget.landmark.type.icon, | ||||||
|  |                   label: Text(widget.landmark.type.name), | ||||||
|  |                 ), | ||||||
|  |                 if (widget.landmark.duration != null && widget.landmark.duration!.inMinutes > 0) | ||||||
|  |                   TextButton.icon( | ||||||
|  |                     onPressed: () {}, | ||||||
|  |                     icon: Icon(Icons.hourglass_bottom), | ||||||
|  |                     label: Text('${widget.landmark.duration!.inMinutes} minutes'), | ||||||
|  |                   ), | ||||||
|  |                 if (widget.landmark.websiteURL != null) | ||||||
|  |                   TextButton.icon( | ||||||
|  |                     onPressed: () async { | ||||||
|  |                       // open a browser with the website link | ||||||
|  |                       await launchUrl(Uri.parse(widget.landmark.websiteURL!)); | ||||||
|  |                     }, | ||||||
|  |                     icon: Icon(Icons.link), | ||||||
|  |                     label: Text('Website'), | ||||||
|  |                   ), | ||||||
|  |                 // if (widget.landmark.wikipediaURL != null) | ||||||
|  |                 //   TextButton.icon( | ||||||
|  |                 //     onPressed: () async { | ||||||
|  |                 //       // open a browser with the wikipedia link | ||||||
|  |                 //       await launchUrl(Uri.parse(widget.landmark.wikipediaURL!)); | ||||||
|  |                 //     }, | ||||||
|  |                 //     icon: Icon(Icons.book), | ||||||
|  |                 //   label: Text('Wikipedia'), | ||||||
|  |                 // ), | ||||||
|  |                 PopupMenuButton( | ||||||
|  |                   icon: Icon(Icons.settings), | ||||||
|  |                   style: TextButtonTheme.of(context).style, | ||||||
|  |                   itemBuilder: (context) => [ | ||||||
|  |                     PopupMenuItem( | ||||||
|  |                       child: ListTile( | ||||||
|  |                         leading: Icon(Icons.delete), | ||||||
|  |                         title: Text('Delete'), | ||||||
|  |                         onTap: () async { | ||||||
|  |                           setState(() { | ||||||
|  |                             widget.parentTrip.removeLandmark(widget.landmark); | ||||||
|  |                           }); | ||||||
|  |                           rootScaffoldMessengerKey.currentState!.showSnackBar( | ||||||
|  |                             SnackBar(content: Text("We won't show ${widget.landmark.name} again")) | ||||||
|  |                           ); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |                         }, | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                     PopupMenuItem( | ||||||
|  |                       child: ListTile( | ||||||
|  |                         leading: Icon(Icons.star), | ||||||
|  |                         title: Text('Favorite'), | ||||||
|  |                         onTap: () async { | ||||||
|  |                           // delete the landmark | ||||||
|  |                           // await deleteLandmark(widget.landmark); | ||||||
|  |  | ||||||
|  |                         }, | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |  | ||||||
|  |                   ], | ||||||
|  |                 ) | ||||||
|  |                    | ||||||
|  |               ], | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user