Created
October 7, 2020 22:01
-
-
Save maks/731e4fa984de707e129062fc5e494ebf to your computer and use it in GitHub Desktop.
fruit-dartpad-demo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file | |
// for details. All rights reserved. Use of this source code is governed by a | |
// BSD-style license that can be found in the LICENSE file. | |
import 'package:flutter/material.dart'; | |
void main() => runApp(MyApp()); | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'Flutter Demo', | |
debugShowCheckedModeBanner: false, | |
theme: ThemeData( | |
primarySwatch: Colors.blue, | |
), | |
home: MyHomePage(title: 'Flutter Demo Home Page'), | |
); | |
} | |
} | |
class MyHomePage extends StatefulWidget { | |
MyHomePage({Key key, this.title}) : super(key: key); | |
final String title; | |
@override | |
_MyHomePageState createState() => _MyHomePageState(); | |
} | |
class _MyHomePageState extends State<MyHomePage> { | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
title: Text(widget.title), | |
), | |
body: Center( | |
child: FruitCardListView(), | |
), | |
// floatingActionButton: FloatingActionButton( | |
// onPressed: _incrementCounter, | |
// tooltip: 'Increment', | |
// child: Icon(Icons.add), | |
// ), | |
); | |
} | |
} | |
Color grey = const Color(0xFFDDDDDD); | |
Color darkGrey = const Color(0xFFBBBBBB); | |
const String imgUrlPrefix = 'https://craiglabenz.sfo2.digitaloceanspaces.com/stock-photos/'; | |
// Finally, we re-structure our widget to be pluggable elsewhere | |
// in our application. Internally, we continue to lean on reusable | |
// widgets by including the `RatingButton` and `FruitInfo` widgets | |
// we created earlier. | |
class FruitCard extends StatelessWidget { | |
final String assetUrl; | |
final String title; | |
final String healthInfo; | |
final int numLikes; | |
final int numDislikes; | |
final RatingStatus ratingStatus; | |
final String family; | |
final bool isSelected; | |
const FruitCard({ | |
@required this.assetUrl, | |
@required this.title, | |
@required this.healthInfo, | |
@required this.family, | |
this.numLikes = 0, | |
this.numDislikes = 0, | |
this.ratingStatus = RatingStatus.none, | |
this.isSelected = false, | |
Key key, | |
}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
double horizontalMargin = 5; | |
double cardPadding = 12; | |
double pictureAndRatings = 164; | |
return Card( | |
margin: EdgeInsets.all(horizontalMargin), | |
child: Container( | |
height: 120, | |
child: Stack( | |
children: <Widget>[ | |
Positioned( | |
left: cardPadding, | |
top: cardPadding, | |
height: 96, | |
child: Container( | |
width: MediaQuery.of(context).size.width - | |
(cardPadding * 2) - | |
(horizontalMargin * 2) - | |
pictureAndRatings, | |
// Pass necessary variables through to child widgets | |
child: FruitInfo( | |
name: title, | |
healthInfo: healthInfo, | |
family: family, | |
), | |
), | |
), | |
Positioned( | |
right: pictureAndRatings - 30, | |
top: 20, | |
// Pass necessary variables through to child widgets | |
child: RatingButton( | |
numRatings: numLikes, | |
icon: Icons.thumb_up, | |
ratingStatus: ratingStatus == RatingStatus.liked | |
? RatingStatus.liked | |
: RatingStatus.none, | |
), | |
), | |
Positioned( | |
right: pictureAndRatings - 30, | |
bottom: 20, | |
// Pass necessary variables through to child widgets | |
child: RatingButton( | |
numRatings: numDislikes, | |
icon: Icons.thumb_down, | |
ratingStatus: ratingStatus == RatingStatus.disliked | |
? RatingStatus.disliked | |
: RatingStatus.none, | |
), | |
), | |
Positioned( | |
right: 0, | |
child: Image.network(imgUrlPrefix+assetUrl, height: 120), | |
), | |
], | |
), | |
), | |
); | |
} | |
} | |
class FruitCardListView extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return ListView( | |
physics: BouncingScrollPhysics(), | |
children: <Widget>[ | |
FruitCard( | |
assetUrl: 'lime.jpg', | |
family: 'Citrus', | |
healthInfo: '20 calories', | |
numLikes: 7, | |
numDislikes: 2, | |
ratingStatus: RatingStatus.liked, | |
title: 'Lime', | |
), | |
FruitCard( | |
assetUrl: 'tomato.jpg', | |
family: 'Nightshade', | |
healthInfo: '24 calories', | |
numLikes: 10, | |
numDislikes: 3, | |
ratingStatus: RatingStatus.liked, | |
title: 'Tomato', | |
), | |
FruitCard( | |
assetUrl: 'kiwi.jpg', | |
family: 'Actinidiaceae', | |
healthInfo: '42 calories', | |
numLikes: 5, | |
numDislikes: 6, | |
ratingStatus: RatingStatus.disliked, | |
title: 'Kiwi', | |
), | |
FruitCard( | |
assetUrl: 'lemon.jpg', | |
family: 'Citrus', | |
healthInfo: '17 calories', | |
numLikes: 12, | |
numDislikes: 11, | |
title: 'Lemon', | |
), | |
FruitCard( | |
assetUrl: 'orange.jpg', | |
family: 'Citrus', | |
healthInfo: '45 calories', | |
numLikes: 10, | |
numDislikes: 1, | |
title: 'Orange', | |
), | |
FruitCard( | |
assetUrl: 'strawberry.jpg', | |
family: 'Citrus', | |
healthInfo: '4 calories', | |
numLikes: 21, | |
numDislikes: 1, | |
ratingStatus: RatingStatus.liked, | |
title: 'Strawberry', | |
), | |
FruitCard( | |
assetUrl: 'avocado.jpg', | |
family: 'Guacamole ingredients', | |
healthInfo: '234 calories', | |
numLikes: 12, | |
numDislikes: 4, | |
ratingStatus: RatingStatus.liked, | |
title: 'Avocado', | |
), | |
], | |
); | |
} | |
} | |
enum RatingStatus { liked, disliked, none } | |
// Next, let's give these rating buttons active states | |
class RatingButton extends StatelessWidget { | |
// Add two variables to control the customizable aspects | |
// of our widget | |
final int numRatings; | |
final IconData icon; | |
final RatingStatus ratingStatus; | |
const RatingButton({ | |
this.numRatings, | |
this.icon, | |
this.ratingStatus = RatingStatus.none, | |
Key key, | |
}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
// Assign a border color to each state | |
Map<RatingStatus, Color> borderColors = { | |
RatingStatus.none: grey, | |
RatingStatus.liked: Colors.green[400], | |
RatingStatus.disliked: Colors.red[400], | |
}; | |
// Assign a fill color to each state | |
Map<RatingStatus, Color> buttonColors = { | |
RatingStatus.none: grey, | |
RatingStatus.liked: Colors.green[700], | |
RatingStatus.disliked: Colors.red[700], | |
}; | |
return Container( | |
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 10), | |
// Replace the hard-coded color with our dynamic color | |
decoration: BoxDecoration( | |
border: Border.all(width: 1, color: borderColors[ratingStatus]), | |
borderRadius: BorderRadius.all(Radius.circular(100)), | |
), | |
child: Row( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: <Widget>[ | |
// Replace the hard-coded color with our dynamic color | |
Text(numRatings.toString(), style: TextStyle( | |
color: buttonColors[ratingStatus], | |
)), | |
SizedBox(width: 4), | |
// Replace the hard-coded color with our dynamic color | |
Icon(icon, size: 16, color: buttonColors[ratingStatus]), | |
], | |
), | |
); | |
} | |
} | |
// Next, let's turn our attention to the Like/Dislike buttons. | |
// First, we'll style just the Like button in its inactive state. | |
class ThumbsUpButton extends StatelessWidget { | |
const ThumbsUpButton({Key key}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return Container( | |
// Set border and padding in ways very familiar to web developers | |
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 10), | |
decoration: BoxDecoration( | |
border: Border.all(width: 1, color: grey), | |
borderRadius: BorderRadius.all(Radius.circular(100)), | |
), | |
child: Row( | |
// Flexbox-inspired layout rules evenly position elements | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: <Widget>[ | |
Text('6', style: TextStyle(color: darkGrey)), | |
SizedBox(width: 4), | |
Icon(Icons.thumb_up, size: 16, color: darkGrey), | |
], | |
), | |
); | |
} | |
} | |
// Zoom in on just the text from our previous code, now with | |
// updated style rules | |
class FruitInfo extends StatelessWidget { | |
final String name; | |
final String healthInfo; | |
final String family; | |
const FruitInfo({ | |
@required this.name, | |
@required this.healthInfo, | |
@required this.family, | |
Key key, | |
}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return Column( | |
mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: <Widget>[ | |
Text( | |
name, | |
// Text-styling is streamlined by provided themes that | |
// capture commonly needed text styles | |
style: Theme.of(context).textTheme.headline5, | |
), | |
Text( | |
healthInfo, | |
// Common text styles can be customized at the root | |
// of the app, or, as shown here, quickly altered | |
// on the fly for specific needs | |
style: Theme.of(context) | |
.textTheme | |
.subtitle2 | |
.copyWith(fontWeight: FontWeight.w200), | |
), | |
Text( | |
family, | |
style: TextStyle( | |
color: Colors.pink, fontWeight: FontWeight.bold), | |
), | |
], | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment