Updates detail screen to new, modal design. (#40)

pull/45/head
Andrew Brogdon 6 years ago committed by GitHub
parent bfb8b8aea9
commit 6ce0d22e9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -32,9 +32,9 @@ class AppState extends Model {
.where((v) => v.name.toLowerCase().contains(terms.toLowerCase()))
.toList();
void toggleFavorite(int id) {
void setFavorite(int id, bool isFavorite) {
Veggie veggie = getVeggie(id);
veggie.isFavorite = !veggie.isFavorite;
veggie.isFavorite = isFavorite;
notifyListeners();
}

@ -8,14 +8,15 @@ import 'package:scoped_model/scoped_model.dart';
import 'package:veggieseasons/data/app_state.dart';
import 'package:veggieseasons/data/veggie.dart';
import 'package:veggieseasons/styles.dart';
import 'package:veggieseasons/widgets/close_button.dart';
/// A circular widget that represents a season of the year.
///
/// The season can be displayed as a valid harvest season or one during which a
/// The season can be displayed as a valid harvest time or one during which a
/// particular veggie cannot be harvested. Bright colors are used in the first
/// case, and grays in the latter.
class SeasonCircle extends StatelessWidget {
SeasonCircle(this.season, this.isHarvestTime);
const SeasonCircle(this.season, this.isHarvestTime);
/// Season to be displayed by this widget.
final Season season;
@ -62,25 +63,7 @@ class DetailsScreen extends StatelessWidget {
DetailsScreen(this.id);
Widget _createFavoriteButton(bool isFav, VoidCallback onPressed) {
return CupertinoButton(
color: Styles.buttonColor,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
isFav ? Styles.checkedIcon : Styles.uncheckedIcon,
color: Styles.buttonIconColor,
),
SizedBox(width: 4.0),
Text(isFav ? 'Saved to Garden' : 'Save to Garden'),
],
),
onPressed: onPressed,
);
}
Widget _createHeader(AppState model) {
Widget _createHeader(BuildContext context, AppState model) {
final veggie = model.getVeggie(id);
return SizedBox(
@ -90,9 +73,21 @@ class DetailsScreen extends StatelessWidget {
Positioned(
right: 0.0,
left: 0.0,
child: Image.asset(
veggie.imageAssetPath,
fit: BoxFit.cover,
child: Hero(
tag: veggie.id,
child: Image.asset(
veggie.imageAssetPath,
fit: BoxFit.cover,
),
),
),
Positioned(
top: 0.0,
right: 16.0,
child: SafeArea(
child: CloseButton(() {
Navigator.of(context).pop();
}),
),
),
Positioned(
@ -125,33 +120,38 @@ class DetailsScreen extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Wrap(
children: Season.values.map((s) {
return SeasonCircle(s, veggie.seasons.contains(s));
}).toList(),
),
SizedBox(height: 8.0),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisSize: MainAxisSize.min,
children: [
Wrap(
children: Season.values.map((s) {
return SeasonCircle(s, veggie.seasons.contains(s));
}).toList(),
CupertinoSwitch(
value: veggie.isFavorite,
onChanged: (value) {
model.setFavorite(id, value);
},
),
SizedBox(width: 8.0),
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: Text(
veggieCategoryNames[veggie.category].toUpperCase(),
style: Styles.minorText,
),
),
),
Text('Save to Garden'),
],
),
SizedBox(height: 24.0),
Align(
alignment: Alignment.centerRight,
child: Text(
veggieCategoryNames[veggie.category].toUpperCase(),
style: Styles.minorText,
),
),
SizedBox(width: 8.0),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: Text(veggie.shortDescription),
),
_createFavoriteButton(veggie.isFavorite, () {
model.toggleFavorite(veggie.id);
}),
],
),
);
@ -162,13 +162,10 @@ class DetailsScreen extends StatelessWidget {
final model = ScopedModel.of<AppState>(context, rebuildOnChange: true);
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Details'),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_createHeader(model),
_createHeader(context, model),
_createDetails(model),
],
),

@ -13,7 +13,7 @@ import 'package:veggieseasons/widgets/veggie_headline.dart';
class FavoritesScreen extends StatelessWidget {
/// Builds the "content" of the favorites screen: either a list of favorite
/// veggies or a note that says the user hasn't favorited any yet.
Widget _buildScaffoldBody(BuildContext context) {
Widget _buildTabViewBody(BuildContext context) {
final model = ScopedModel.of<AppState>(context, rebuildOnChange: true);
if (model.favoriteVeggies.length == 0) {
@ -46,14 +46,17 @@ class FavoritesScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Your Garden'),
),
backgroundColor: Styles.scaffoldBackground,
child: Center(
child: _buildScaffoldBody(context),
),
return CupertinoTabView(
builder: (context) {
return DecoratedBox(
decoration: BoxDecoration(
color: Styles.scaffoldBackground,
),
child: Center(
child: _buildTabViewBody(context),
),
);
},
);
}
}

@ -91,11 +91,13 @@ abstract class Styles {
static const scaffoldBackground = Color(0xfff0f0f0);
static const buttonColor = Color(0xff007aff);
static const searchBackground = Color(0xffe0e0e0);
static const buttonIconColor = Color(0xffffffff);
static const frostedBackground = Color(0xccf8f8f8);
static const searchBackground = Color(0xffe0e0e0);
static const closeButtonUnpressed = Color(0xff101010);
static const closeButtonPressed = Color(0xff808080);
static const TextStyle searchText = TextStyle(
color: Color.fromRGBO(0, 0, 0, 1.0),

@ -0,0 +1,131 @@
// Copyright 2018 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/widgets.dart';
import 'package:veggieseasons/styles.dart';
/// Partially overlays and then blurs its child.
class FrostedBox extends StatelessWidget {
const FrostedBox({
this.child,
Key key,
}) : super(key: key);
final Widget child;
@override
Widget build(BuildContext context) {
return BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
child: DecoratedBox(
decoration: BoxDecoration(
color: Styles.frostedBackground,
),
child: child,
),
);
}
}
/// An Icon that implicitly animates changes to its color.
class ColorChangingIcon extends ImplicitlyAnimatedWidget {
const ColorChangingIcon(
this.icon, {
this.color = CupertinoColors.black,
this.size,
@required Duration duration,
Key key,
}) : assert(icon != null),
assert(color != null),
assert(duration != null),
super(key: key, duration: duration);
final Color color;
final IconData icon;
final double size;
@override
_ColorChangingIconState createState() => _ColorChangingIconState();
}
class _ColorChangingIconState
extends AnimatedWidgetBaseState<ColorChangingIcon> {
ColorTween _colorTween;
@override
Widget build(BuildContext context) {
return Icon(
widget.icon,
size: widget.size,
color: _colorTween?.evaluate(animation),
);
}
@override
void forEachTween(visitor) {
_colorTween = visitor(
_colorTween,
widget.color,
(dynamic value) => ColorTween(begin: value),
);
}
}
/// A simple "close this modal" button that invokes a callback when pressed.
class CloseButton extends StatefulWidget {
const CloseButton(this.onPressed);
final VoidCallback onPressed;
@override
CloseButtonState createState() {
return new CloseButtonState();
}
}
class CloseButtonState extends State<CloseButton> {
bool tapInProgress = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: (details) {
setState(() => tapInProgress = true);
},
onTapUp: (details) {
setState(() => tapInProgress = false);
widget.onPressed();
},
onTapCancel: () {
setState(() => tapInProgress = false);
},
child: ClipOval(
child: FrostedBox(
child: Container(
width: 30,
height: 30,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
),
child: Center(
child: ColorChangingIcon(
CupertinoIcons.clear_thick,
duration: Duration(milliseconds: 300),
color: tapInProgress
? Styles.closeButtonPressed
: Styles.closeButtonUnpressed,
size: 20,
),
),
),
),
),
);
}
}

@ -34,7 +34,7 @@ class SearchBar extends StatelessWidget {
color: Styles.searchIconColor,
),
Expanded(
child: EditableText(
child: CupertinoTextField(
controller: controller,
focusNode: focusNode,
style: Styles.searchText,

@ -35,8 +35,10 @@ class VeggieHeadline extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => Navigator.of(context).push(
CupertinoPageRoute(builder: (context) => DetailsScreen(veggie.id))),
onTap: () => Navigator.of(context).push(CupertinoPageRoute(
builder: (context) => DetailsScreen(veggie.id),
fullscreenDialog: true,
)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [

Loading…
Cancel
Save