Skip to content

Instantly share code, notes, and snippets.

@maks
Created October 7, 2020 22:01
Show Gist options
  • Save maks/731e4fa984de707e129062fc5e494ebf to your computer and use it in GitHub Desktop.
Save maks/731e4fa984de707e129062fc5e494ebf to your computer and use it in GitHub Desktop.
fruit-dartpad-demo
// 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