Frontend UX improvements #37
| @@ -5,7 +5,6 @@ import 'package:flutter/material.dart'; | ||||
| import 'package:anyway/modules/landmark_card.dart'; | ||||
| import 'package:anyway/structs/landmark.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) { | ||||
|     children.add( | ||||
|       Dismissible( | ||||
|         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, | ||||
|         ), | ||||
|       ) | ||||
|       LandmarkCard(landmark, trip), | ||||
|     ); | ||||
|  | ||||
|     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:cached_network_image/cached_network_image.dart'; | ||||
| import 'package:url_launcher/url_launcher.dart'; | ||||
| @@ -6,8 +8,12 @@ import 'package:anyway/structs/landmark.dart'; | ||||
|  | ||||
| class LandmarkCard extends StatefulWidget { | ||||
|   final Landmark landmark; | ||||
|   final Trip parentTrip; | ||||
|    | ||||
|   LandmarkCard(this.landmark); | ||||
|   LandmarkCard( | ||||
|     this.landmark, | ||||
|     this.parentTrip, | ||||
|     ); | ||||
|    | ||||
|   @override | ||||
|   _LandmarkCardState createState() => _LandmarkCardState(); | ||||
| @@ -18,106 +24,150 @@ class _LandmarkCardState extends State<LandmarkCard> { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Container( | ||||
|       height: 160, | ||||
|       child: Card( | ||||
|         shape: RoundedRectangleBorder( | ||||
|           borderRadius: BorderRadius.circular(15.0), | ||||
|         ), | ||||
|         elevation: 5, | ||||
|         clipBehavior: Clip.antiAliasWithSaveLayer, | ||||
|         child: Row( | ||||
|           crossAxisAlignment: CrossAxisAlignment.start, | ||||
|           children: [ | ||||
|             Container( // the image on the left | ||||
|               // inherit the height of the parent container | ||||
|               height: double.infinity, | ||||
|               // force a fixed width | ||||
|               width: 160, | ||||
|               child: CachedNetworkImage( | ||||
|                 imageUrl: widget.landmark.imageURL ?? '', | ||||
|                 placeholder: (context, url) => Center(child: CircularProgressIndicator()), | ||||
|                 errorWidget: (context, error, stackTrace) => Icon(Icons.question_mark_outlined), | ||||
|                 fit: BoxFit.cover, | ||||
|               ), | ||||
|             ), | ||||
|             Flexible( | ||||
|               child: 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'), | ||||
|                           // ), | ||||
|                         ], | ||||
|                       ), | ||||
|                     ), | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|           ], | ||||
|         // if the image is available, display it on the left side of the card, otherwise only display the text | ||||
|         child: widget.landmark.imageURL != null ? splitLayout() : textLayout(), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Widget splitLayout() { | ||||
|     // If an image is available, display it on the left side of the card | ||||
|     return Row( | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|       children: [ | ||||
|         Container( | ||||
|           // the image on the left | ||||
|           width: 160, | ||||
|           height: 160, | ||||
|  | ||||
|           child: CachedNetworkImage( | ||||
|             imageUrl: widget.landmark.imageURL ?? '', | ||||
|             placeholder: (context, url) => Center(child: CircularProgressIndicator()), | ||||
|             errorWidget: (context, error, stackTrace) => Icon(Icons.question_mark_outlined), | ||||
|             fit: BoxFit.cover, | ||||
|           ), | ||||
|         ), | ||||
|         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