mirror of https://github.com/flutter/samples.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
177 lines
4.7 KiB
177 lines
4.7 KiB
// 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' as ui;
|
|
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:veggieseasons/data/veggie.dart';
|
|
import 'package:veggieseasons/styles.dart';
|
|
|
|
class FrostyBackground extends StatelessWidget {
|
|
const FrostyBackground({
|
|
this.color,
|
|
this.intensity = 25,
|
|
this.child,
|
|
super.key,
|
|
});
|
|
|
|
final Color? color;
|
|
final double intensity;
|
|
final Widget? child;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ClipRect(
|
|
child: BackdropFilter(
|
|
filter: ui.ImageFilter.blur(sigmaX: intensity, sigmaY: intensity),
|
|
child: DecoratedBox(
|
|
decoration: BoxDecoration(
|
|
color: color,
|
|
),
|
|
child: child,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// A Card-like Widget that responds to tap events by animating changes to its
|
|
/// elevation and invoking an optional [onPressed] callback.
|
|
class PressableCard extends StatefulWidget {
|
|
const PressableCard({
|
|
required this.child,
|
|
this.borderRadius = const BorderRadius.all(Radius.circular(5)),
|
|
this.upElevation = 2,
|
|
this.downElevation = 0,
|
|
this.shadowColor = CupertinoColors.black,
|
|
this.duration = const Duration(milliseconds: 100),
|
|
this.onPressed,
|
|
super.key,
|
|
});
|
|
|
|
final VoidCallback? onPressed;
|
|
|
|
final Widget child;
|
|
|
|
final BorderRadius borderRadius;
|
|
|
|
final double upElevation;
|
|
|
|
final double downElevation;
|
|
|
|
final Color shadowColor;
|
|
|
|
final Duration duration;
|
|
|
|
@override
|
|
State<PressableCard> createState() => _PressableCardState();
|
|
}
|
|
|
|
class _PressableCardState extends State<PressableCard> {
|
|
bool cardIsDown = false;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return GestureDetector(
|
|
onTap: () {
|
|
setState(() => cardIsDown = false);
|
|
if (widget.onPressed != null) {
|
|
widget.onPressed!();
|
|
}
|
|
},
|
|
onTapDown: (details) => setState(() => cardIsDown = true),
|
|
onTapCancel: () => setState(() => cardIsDown = false),
|
|
child: AnimatedPhysicalModel(
|
|
elevation: cardIsDown ? widget.downElevation : widget.upElevation,
|
|
borderRadius: widget.borderRadius,
|
|
shape: BoxShape.rectangle,
|
|
shadowColor: widget.shadowColor,
|
|
duration: widget.duration,
|
|
color: CupertinoColors.lightBackgroundGray,
|
|
child: ClipRRect(
|
|
borderRadius: widget.borderRadius,
|
|
child: widget.child,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class VeggieCard extends StatelessWidget {
|
|
const VeggieCard(this.veggie, this.isInSeason, this.isPreferredCategory,
|
|
{super.key});
|
|
|
|
/// Veggie to be displayed by the card.
|
|
final Veggie veggie;
|
|
|
|
/// If the veggie is in season, it's displayed more prominently and the
|
|
/// image is fully saturated. Otherwise, it's reduced and de-saturated.
|
|
final bool isInSeason;
|
|
|
|
/// Whether [veggie] falls into one of user's preferred [VeggieCategory]s
|
|
final bool isPreferredCategory;
|
|
|
|
Widget _buildDetails(BuildContext context) {
|
|
final themeData = CupertinoTheme.of(context);
|
|
return FrostyBackground(
|
|
color: const Color(0x90ffffff),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
veggie.name,
|
|
style: Styles.cardTitleText(themeData),
|
|
),
|
|
Text(
|
|
veggie.shortDescription,
|
|
style: Styles.cardDescriptionText(themeData),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return PressableCard(
|
|
onPressed: () {
|
|
// GoRouter does not support relative routes,
|
|
// so navigate to the absolute route.
|
|
// see https://github.com/flutter/flutter/issues/108177
|
|
context.go('/list/details/${veggie.id}');
|
|
},
|
|
child: Stack(
|
|
children: [
|
|
Semantics(
|
|
label: 'A card background featuring ${veggie.name}',
|
|
child: Container(
|
|
height: isInSeason ? 300 : 150,
|
|
decoration: BoxDecoration(
|
|
image: DecorationImage(
|
|
fit: BoxFit.cover,
|
|
colorFilter:
|
|
isInSeason ? null : Styles.desaturatedColorFilter,
|
|
image: AssetImage(
|
|
veggie.imageAssetPath,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
Positioned(
|
|
bottom: 0,
|
|
left: 0,
|
|
right: 0,
|
|
child: _buildDetails(context),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|