import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:anyway/constants.dart'; import 'package:anyway/main.dart'; import 'package:anyway/structs/trip.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:anyway/structs/landmark.dart'; class LandmarkCard extends StatefulWidget { final Landmark landmark; final Trip parentTrip; LandmarkCard( this.landmark, this.parentTrip, ); @override _LandmarkCardState createState() => _LandmarkCardState(); } class _LandmarkCardState extends State { @override Widget build(BuildContext context) { return Container( constraints: BoxConstraints( // express the max height in terms text lines maxHeight: 7 * (Theme.of(context).textTheme.titleMedium!.fontSize! + 10), ), child: Card( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15.0), ), elevation: 5, clipBehavior: Clip.antiAliasWithSaveLayer, // if the image is available, display it on the left side of the card, otherwise only display the text child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Image and landmark "type" on the left AspectRatio( aspectRatio: 3 / 4, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (widget.landmark.imageURL != null && widget.landmark.imageURL!.isNotEmpty) Expanded( child: CachedNetworkImage( imageUrl: widget.landmark.imageURL!, placeholder: (context, url) => const Center(child: CircularProgressIndicator()), errorWidget: (context, url, error) => imagePlaceholder(widget.landmark), fit: BoxFit.cover ) ) else imagePlaceholder(widget.landmark), if (widget.landmark.type != typeStart && widget.landmark.type != typeFinish) Container( color: PRIMARY_COLOR, child: Center( child: Padding( padding: EdgeInsets.all(5), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.timer_outlined, size: 16), Text("${widget.landmark.duration?.inMinutes} minutes"), ], ) ) ), ) ], ) ), // Main information, useful buttons on the right Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.all(10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.landmark.name, style: Theme.of(context).textTheme.titleMedium, overflow: TextOverflow.ellipsis, maxLines: 2, ), if (widget.landmark.nameEN != null) Text( widget.landmark.nameEN!, style: Theme.of(context).textTheme.bodyMedium, maxLines: 1, overflow: TextOverflow.ellipsis, ), ] ), ), // fill the vspace const Spacer(), SingleChildScrollView( scrollDirection: Axis.horizontal, padding: EdgeInsets.only(left: 5, right: 5, bottom: 10), // the scroll view should be flush once the buttons are scrolled to the left // but initially there should be some padding child: Wrap( spacing: 10, // show the type, the website, and the wikipedia link as buttons/labels in a row children: [ doneToggleButton(), if (widget.landmark.websiteURL != null) websiteButton(), optionsButton() ], ), ), ], ) ) ], ) ) ); } Widget doneToggleButton() { return TextButton.icon( onPressed: () async { widget.landmark.visited = !widget.landmark.visited; widget.parentTrip.notifyUpdate(); }, icon: Icon(widget.landmark.visited ? Icons.undo_sharp : Icons.check), label: Text(widget.landmark.visited ? "Add to overview" : "Done!"), ); } Widget websiteButton () => TextButton.icon( onPressed: () async { // open a browser with the website link await launchUrl(Uri.parse(widget.landmark.websiteURL!)); }, icon: const Icon(Icons.link), label: const Text('Website'), ); Widget optionsButton () => PopupMenuButton( icon: const Icon(Icons.settings), style: TextButtonTheme.of(context).style, itemBuilder: (context) => [ PopupMenuItem( child: ListTile( leading: const Icon(Icons.delete), title: const Text('Delete'), onTap: () async { widget.parentTrip.removeLandmark(widget.landmark); rootScaffoldMessengerKey.currentState!.showSnackBar( SnackBar(content: Text("${widget.landmark.name} won't be shown again")) ); }, ), ), PopupMenuItem( child: ListTile( leading: const Icon(Icons.star), title: const Text('Favorite'), onTap: () async { rootScaffoldMessengerKey.currentState!.showSnackBar( SnackBar(content: Text("Not implemented yet")) ); }, ), ), ], ); } Widget imagePlaceholder (Landmark landmark) => Expanded( child: Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [GRADIENT_START, GRADIENT_END], ), ), child: Center( child: Icon(landmark.type.icon.icon, size: 50), ), ), );