mirror of
https://github.com/flutter/samples.git
synced 2025-11-08 13:58:47 +00:00
Cleaning up Veggie Seasons (#2416)
## Pre-launch Checklist - [x] I read the [Flutter Style Guide] _recently_, and have followed its advice. - [x] I signed the [CLA]. - [x] I read the [Contributors Guide]. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-devrel channel on [Discord]. <!-- Links --> [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [CLA]: https://cla.developers.google.com/ [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md [Contributors Guide]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md
This commit is contained in:
@@ -1 +1,5 @@
|
|||||||
include: package:analysis_defaults/flutter.yaml
|
include: package:analysis_defaults/flutter.yaml
|
||||||
|
|
||||||
|
linter:
|
||||||
|
rules:
|
||||||
|
- prefer_relative_imports
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:veggieseasons/data/local_veggie_provider.dart';
|
import 'local_veggie_provider.dart';
|
||||||
import 'package:veggieseasons/data/veggie.dart';
|
import 'veggie.dart';
|
||||||
|
|
||||||
class AppState extends ChangeNotifier {
|
class AppState extends ChangeNotifier {
|
||||||
final List<Veggie> _veggies;
|
final List<Veggie> _veggies;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:veggieseasons/data/veggie.dart';
|
import 'veggie.dart';
|
||||||
|
|
||||||
class LocalVeggieProvider {
|
class LocalVeggieProvider {
|
||||||
static List<Veggie> veggies = [
|
static List<Veggie> veggies = [
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:veggieseasons/data/veggie.dart';
|
import 'veggie.dart';
|
||||||
|
|
||||||
/// A model class that mirrors the options in [SettingsScreen] and stores data
|
/// A model class that mirrors the options in [SettingsScreen] and stores data
|
||||||
/// in shared preferences.
|
/// in shared preferences.
|
||||||
|
|||||||
@@ -9,18 +9,18 @@ import 'package:flutter/foundation.dart' show kIsWeb;
|
|||||||
import 'package:flutter/services.dart' show DeviceOrientation, SystemChrome;
|
import 'package:flutter/services.dart' show DeviceOrientation, SystemChrome;
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:veggieseasons/data/app_state.dart';
|
|
||||||
import 'package:veggieseasons/data/preferences.dart';
|
|
||||||
import 'package:veggieseasons/screens/home.dart';
|
|
||||||
import 'package:veggieseasons/styles.dart';
|
|
||||||
import 'package:veggieseasons/widgets/fade_transition_page.dart';
|
|
||||||
import 'package:window_size/window_size.dart';
|
import 'package:window_size/window_size.dart';
|
||||||
|
|
||||||
|
import 'data/app_state.dart';
|
||||||
|
import 'data/preferences.dart';
|
||||||
import 'screens/details.dart';
|
import 'screens/details.dart';
|
||||||
import 'screens/favorites.dart';
|
import 'screens/favorites.dart';
|
||||||
|
import 'screens/home.dart';
|
||||||
import 'screens/list.dart';
|
import 'screens/list.dart';
|
||||||
import 'screens/search.dart';
|
import 'screens/search.dart';
|
||||||
import 'screens/settings.dart';
|
import 'screens/settings.dart';
|
||||||
|
import 'styles.dart';
|
||||||
|
import 'widgets/veggie_seasons_page.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
@@ -136,7 +136,7 @@ class _VeggieAppState extends State<VeggieApp> with RestorationMixin {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/list',
|
path: '/list',
|
||||||
pageBuilder: (context, state) {
|
pageBuilder: (context, state) {
|
||||||
return FadeTransitionPage(
|
return VeggieSeasonsPage(
|
||||||
key: state.pageKey,
|
key: state.pageKey,
|
||||||
restorationId: 'route.list',
|
restorationId: 'route.list',
|
||||||
child: const ListScreen(restorationId: 'list'),
|
child: const ListScreen(restorationId: 'list'),
|
||||||
@@ -149,7 +149,7 @@ class _VeggieAppState extends State<VeggieApp> with RestorationMixin {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/favorites',
|
path: '/favorites',
|
||||||
pageBuilder: (context, state) {
|
pageBuilder: (context, state) {
|
||||||
return FadeTransitionPage(
|
return VeggieSeasonsPage(
|
||||||
key: state.pageKey,
|
key: state.pageKey,
|
||||||
restorationId: 'route.favorites',
|
restorationId: 'route.favorites',
|
||||||
child: const FavoritesScreen(restorationId: 'favorites'),
|
child: const FavoritesScreen(restorationId: 'favorites'),
|
||||||
@@ -162,7 +162,7 @@ class _VeggieAppState extends State<VeggieApp> with RestorationMixin {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/search',
|
path: '/search',
|
||||||
pageBuilder: (context, state) {
|
pageBuilder: (context, state) {
|
||||||
return FadeTransitionPage(
|
return VeggieSeasonsPage(
|
||||||
key: state.pageKey,
|
key: state.pageKey,
|
||||||
restorationId: 'route.search',
|
restorationId: 'route.search',
|
||||||
child: const SearchScreen(restorationId: 'search'),
|
child: const SearchScreen(restorationId: 'search'),
|
||||||
@@ -175,7 +175,7 @@ class _VeggieAppState extends State<VeggieApp> with RestorationMixin {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
pageBuilder: (context, state) {
|
pageBuilder: (context, state) {
|
||||||
return FadeTransitionPage(
|
return VeggieSeasonsPage(
|
||||||
key: state.pageKey,
|
key: state.pageKey,
|
||||||
restorationId: 'route.settings',
|
restorationId: 'route.settings',
|
||||||
child: const SettingsScreen(restorationId: 'settings'),
|
child: const SettingsScreen(restorationId: 'settings'),
|
||||||
@@ -217,7 +217,6 @@ class _VeggieAppState extends State<VeggieApp> with RestorationMixin {
|
|||||||
final veggieId = int.parse(state.pathParameters['id']!);
|
final veggieId = int.parse(state.pathParameters['id']!);
|
||||||
return CupertinoPage(
|
return CupertinoPage(
|
||||||
restorationId: 'route.details',
|
restorationId: 'route.details',
|
||||||
fullscreenDialog: true,
|
|
||||||
child: DetailsScreen(
|
child: DetailsScreen(
|
||||||
id: veggieId,
|
id: veggieId,
|
||||||
restorationId: 'details',
|
restorationId: 'details',
|
||||||
|
|||||||
@@ -5,12 +5,11 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:veggieseasons/data/app_state.dart';
|
import '../data/app_state.dart';
|
||||||
import 'package:veggieseasons/data/preferences.dart';
|
import '../data/preferences.dart';
|
||||||
import 'package:veggieseasons/data/veggie.dart';
|
import '../data/veggie.dart';
|
||||||
import 'package:veggieseasons/styles.dart';
|
import '../styles.dart';
|
||||||
import 'package:veggieseasons/widgets/close_button.dart';
|
import '../widgets/detail_buttons.dart';
|
||||||
import 'package:veggieseasons/widgets/trivia.dart';
|
|
||||||
|
|
||||||
class ServingInfoChart extends StatelessWidget {
|
class ServingInfoChart extends StatelessWidget {
|
||||||
const ServingInfoChart(this.veggie, this.prefs, {super.key});
|
const ServingInfoChart(this.veggie, this.prefs, {super.key});
|
||||||
@@ -44,23 +43,7 @@ class ServingInfoChart extends StatelessWidget {
|
|||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Align(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
left: 9,
|
|
||||||
bottom: 4,
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
'Serving info',
|
|
||||||
style: CupertinoTheme.of(context).textTheme.textStyle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
Container(
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border.all(color: Styles.servingInfoBorderColor),
|
|
||||||
),
|
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -212,61 +195,23 @@ class InfoView extends StatelessWidget {
|
|||||||
style: CupertinoTheme.of(context).textTheme.textStyle,
|
style: CupertinoTheme.of(context).textTheme.textStyle,
|
||||||
),
|
),
|
||||||
ServingInfoChart(veggie, prefs),
|
ServingInfoChart(veggie, prefs),
|
||||||
const SizedBox(height: 24),
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
CupertinoSwitch(
|
|
||||||
value: veggie.isFavorite,
|
|
||||||
onChanged: (value) {
|
|
||||||
appState.setFavorite(id, value);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
'Save to Garden',
|
|
||||||
style: CupertinoTheme.of(context).textTheme.textStyle,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DetailsScreen extends StatefulWidget {
|
class DetailsScreen extends StatelessWidget {
|
||||||
final int? id;
|
final int? id;
|
||||||
final String? restorationId;
|
final String? restorationId;
|
||||||
|
|
||||||
const DetailsScreen({this.id, this.restorationId, super.key});
|
const DetailsScreen({this.id, this.restorationId, super.key});
|
||||||
|
|
||||||
@override
|
|
||||||
State<DetailsScreen> createState() => _DetailsScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _DetailsScreenState extends State<DetailsScreen> with RestorationMixin {
|
|
||||||
final RestorableInt _selectedViewIndex = RestorableInt(0);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String? get restorationId => widget.restorationId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
|
|
||||||
registerForRestoration(_selectedViewIndex, 'tab');
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_selectedViewIndex.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildHeader(BuildContext context, AppState model) {
|
Widget _buildHeader(BuildContext context, AppState model) {
|
||||||
final veggie = model.getVeggie(widget.id);
|
final veggie = model.getVeggie(id);
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 150,
|
height: 240,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned(
|
Positioned(
|
||||||
@@ -287,6 +232,48 @@ class _DetailsScreenState extends State<DetailsScreen> with RestorationMixin {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Positioned(
|
||||||
|
top: 16,
|
||||||
|
right: 16,
|
||||||
|
child: SafeArea(
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
ShareButton(
|
||||||
|
() {
|
||||||
|
showCupertinoModalPopup<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return CupertinoActionSheet(
|
||||||
|
title: Text('Share ${veggie.name}'),
|
||||||
|
message: Text(veggie.shortDescription),
|
||||||
|
actions: [
|
||||||
|
CupertinoActionSheetAction(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: const Text('OK'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Builder(builder: (context) {
|
||||||
|
final appState = Provider.of<AppState>(context);
|
||||||
|
final veggie = appState.getVeggie(id);
|
||||||
|
|
||||||
|
return FavoriteButton(
|
||||||
|
() => appState.setFavorite(id, !veggie.isFavorite),
|
||||||
|
veggie.isFavorite,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -296,9 +283,7 @@ class _DetailsScreenState extends State<DetailsScreen> with RestorationMixin {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final appState = Provider.of<AppState>(context);
|
final appState = Provider.of<AppState>(context);
|
||||||
|
|
||||||
return UnmanagedRestorationScope(
|
return CupertinoPageScaffold(
|
||||||
bucket: bucket,
|
|
||||||
child: CupertinoPageScaffold(
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@@ -309,29 +294,12 @@ class _DetailsScreenState extends State<DetailsScreen> with RestorationMixin {
|
|||||||
children: [
|
children: [
|
||||||
_buildHeader(context, appState),
|
_buildHeader(context, appState),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
CupertinoSegmentedControl<int>(
|
InfoView(id),
|
||||||
children: const {
|
|
||||||
0: Text(
|
|
||||||
'Facts & Info',
|
|
||||||
),
|
|
||||||
1: Text(
|
|
||||||
'Trivia',
|
|
||||||
)
|
|
||||||
},
|
|
||||||
groupValue: _selectedViewIndex.value,
|
|
||||||
onValueChanged: (value) {
|
|
||||||
setState(() => _selectedViewIndex.value = value);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
_selectedViewIndex.value == 0
|
|
||||||
? InfoView(widget.id)
|
|
||||||
: TriviaView(id: widget.id, restorationId: 'trivia'),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:veggieseasons/data/app_state.dart';
|
import '../data/app_state.dart';
|
||||||
import 'package:veggieseasons/data/veggie.dart';
|
import '../data/veggie.dart';
|
||||||
import 'package:veggieseasons/widgets/veggie_headline.dart';
|
import '../widgets/veggie_headline.dart';
|
||||||
|
|
||||||
class FavoritesScreen extends StatelessWidget {
|
class FavoritesScreen extends StatelessWidget {
|
||||||
const FavoritesScreen({this.restorationId, super.key});
|
const FavoritesScreen({this.restorationId, super.key});
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
const _bottomNavigationBarItemIconPadding = EdgeInsets.only(top: 4.0);
|
||||||
|
|
||||||
class HomeScreen extends StatelessWidget {
|
class HomeScreen extends StatelessWidget {
|
||||||
const HomeScreen({
|
const HomeScreen({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -30,19 +32,31 @@ class HomeScreen extends StatelessWidget {
|
|||||||
currentIndex: index,
|
currentIndex: index,
|
||||||
items: const [
|
items: const [
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: Icon(CupertinoIcons.home),
|
icon: Padding(
|
||||||
|
padding: _bottomNavigationBarItemIconPadding,
|
||||||
|
child: Icon(CupertinoIcons.home),
|
||||||
|
),
|
||||||
label: 'Home',
|
label: 'Home',
|
||||||
),
|
),
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: Icon(CupertinoIcons.book),
|
icon: Padding(
|
||||||
|
padding: _bottomNavigationBarItemIconPadding,
|
||||||
|
child: Icon(CupertinoIcons.book),
|
||||||
|
),
|
||||||
label: 'My Garden',
|
label: 'My Garden',
|
||||||
),
|
),
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: Icon(CupertinoIcons.search),
|
icon: Padding(
|
||||||
|
padding: _bottomNavigationBarItemIconPadding,
|
||||||
|
child: Icon(CupertinoIcons.search),
|
||||||
|
),
|
||||||
label: 'Search',
|
label: 'Search',
|
||||||
),
|
),
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: Icon(CupertinoIcons.settings),
|
icon: Padding(
|
||||||
|
padding: _bottomNavigationBarItemIconPadding,
|
||||||
|
child: Icon(CupertinoIcons.settings),
|
||||||
|
),
|
||||||
label: 'Settings',
|
label: 'Settings',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -4,20 +4,19 @@
|
|||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:veggieseasons/data/app_state.dart';
|
import '../data/app_state.dart';
|
||||||
import 'package:veggieseasons/data/preferences.dart';
|
import '../data/preferences.dart';
|
||||||
import 'package:veggieseasons/data/veggie.dart';
|
import '../data/veggie.dart';
|
||||||
import 'package:veggieseasons/styles.dart';
|
import '../styles.dart';
|
||||||
import 'package:veggieseasons/widgets/veggie_card.dart';
|
import '../widgets/veggie_card.dart';
|
||||||
|
|
||||||
class ListScreen extends StatelessWidget {
|
class ListScreen extends StatelessWidget {
|
||||||
const ListScreen({this.restorationId, super.key});
|
const ListScreen({this.restorationId, super.key});
|
||||||
|
|
||||||
final String? restorationId;
|
final String? restorationId;
|
||||||
|
|
||||||
Widget _generateVeggieRow(Veggie veggie, Preferences prefs,
|
Widget _generateVeggieCard(Veggie veggie, Preferences prefs,
|
||||||
{bool inSeason = true}) {
|
{bool inSeason = true}) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 24),
|
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 24),
|
||||||
@@ -35,8 +34,6 @@ class ListScreen extends StatelessWidget {
|
|||||||
return CupertinoTabView(
|
return CupertinoTabView(
|
||||||
restorationScopeId: restorationId,
|
restorationScopeId: restorationId,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
var dateString = DateFormat('MMMM y').format(DateTime.now());
|
|
||||||
|
|
||||||
final appState = Provider.of<AppState>(context);
|
final appState = Provider.of<AppState>(context);
|
||||||
final prefs = Provider.of<Preferences>(context);
|
final prefs = Provider.of<Preferences>(context);
|
||||||
final themeData = CupertinoTheme.of(context);
|
final themeData = CupertinoTheme.of(context);
|
||||||
@@ -52,18 +49,11 @@ class ListScreen extends StatelessWidget {
|
|||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(16, 24, 16, 16),
|
padding: const EdgeInsets.fromLTRB(16, 24, 16, 16),
|
||||||
child: Column(
|
child: Text('In season today',
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(dateString.toUpperCase(),
|
|
||||||
style: Styles.minorText(themeData)),
|
|
||||||
Text('In season today',
|
|
||||||
style: Styles.headlineText(themeData)),
|
style: Styles.headlineText(themeData)),
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
} else if (index <= appState.availableVeggies.length) {
|
} else if (index <= appState.availableVeggies.length) {
|
||||||
return _generateVeggieRow(
|
return _generateVeggieCard(
|
||||||
appState.availableVeggies[index - 1],
|
appState.availableVeggies[index - 1],
|
||||||
prefs,
|
prefs,
|
||||||
);
|
);
|
||||||
@@ -76,7 +66,7 @@ class ListScreen extends StatelessWidget {
|
|||||||
} else {
|
} else {
|
||||||
var relativeIndex =
|
var relativeIndex =
|
||||||
index - (appState.availableVeggies.length + 2);
|
index - (appState.availableVeggies.length + 2);
|
||||||
return _generateVeggieRow(
|
return _generateVeggieCard(
|
||||||
appState.unavailableVeggies[relativeIndex], prefs,
|
appState.unavailableVeggies[relativeIndex], prefs,
|
||||||
inSeason: false);
|
inSeason: false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:veggieseasons/data/app_state.dart';
|
import '../data/app_state.dart';
|
||||||
import 'package:veggieseasons/data/veggie.dart';
|
import '../data/veggie.dart';
|
||||||
import 'package:veggieseasons/widgets/veggie_headline.dart';
|
import '../widgets/veggie_headline.dart';
|
||||||
|
|
||||||
class SearchScreen extends StatefulWidget {
|
class SearchScreen extends StatefulWidget {
|
||||||
const SearchScreen({this.restorationId, super.key});
|
const SearchScreen({this.restorationId, super.key});
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:veggieseasons/data/preferences.dart';
|
import '../data/preferences.dart';
|
||||||
import 'package:veggieseasons/data/veggie.dart';
|
import '../data/veggie.dart';
|
||||||
import 'package:veggieseasons/styles.dart';
|
import '../styles.dart';
|
||||||
import 'package:veggieseasons/widgets/settings_group.dart';
|
import '../widgets/settings_group.dart';
|
||||||
import 'package:veggieseasons/widgets/settings_item.dart';
|
import '../widgets/settings_item.dart';
|
||||||
|
|
||||||
class VeggieCategorySettingsScreen extends StatelessWidget {
|
class VeggieCategorySettingsScreen extends StatelessWidget {
|
||||||
const VeggieCategorySettingsScreen({super.key, this.restorationId});
|
const VeggieCategorySettingsScreen({super.key, this.restorationId});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:veggieseasons/data/veggie.dart';
|
import 'data/veggie.dart';
|
||||||
|
|
||||||
abstract class Styles {
|
abstract class Styles {
|
||||||
static CupertinoThemeData veggieThemeData = const CupertinoThemeData(
|
static CupertinoThemeData veggieThemeData = const CupertinoThemeData(
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:veggieseasons/styles.dart';
|
import '../styles.dart';
|
||||||
|
|
||||||
/// Partially overlays and then blurs its child's background.
|
/// Partially overlays and then blurs its child's background.
|
||||||
class FrostedBox extends StatelessWidget {
|
class FrostedBox extends StatelessWidget {
|
||||||
@@ -75,17 +75,38 @@ class _ColorChangingIconState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A simple "close this modal" button that invokes a callback when pressed.
|
/// A close button that invokes a callback when pressed.
|
||||||
class CloseButton extends StatefulWidget {
|
class CloseButton extends _DetailPageButton {
|
||||||
const CloseButton(this.onPressed, {super.key});
|
const CloseButton(VoidCallback onPressed, {super.key})
|
||||||
|
: super(onPressed, CupertinoIcons.chevron_back);
|
||||||
final VoidCallback onPressed;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<CloseButton> createState() => _CloseButtonState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CloseButtonState extends State<CloseButton> {
|
/// A share button that invokes a callback when pressed.
|
||||||
|
class ShareButton extends _DetailPageButton {
|
||||||
|
const ShareButton(VoidCallback onPressed, {super.key})
|
||||||
|
: super(onPressed, CupertinoIcons.share);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A favorite button that invokes a callback when pressed.
|
||||||
|
class FavoriteButton extends _DetailPageButton {
|
||||||
|
const FavoriteButton(VoidCallback onPressed, bool isFavorite, {super.key})
|
||||||
|
: super(
|
||||||
|
onPressed,
|
||||||
|
isFavorite ? CupertinoIcons.heart_fill : CupertinoIcons.heart,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DetailPageButton extends StatefulWidget {
|
||||||
|
const _DetailPageButton(this.onPressed, this.icon, {super.key});
|
||||||
|
|
||||||
|
final VoidCallback onPressed;
|
||||||
|
final IconData icon;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_DetailPageButton> createState() => _DetailPageButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DetailPageButtonState extends State<_DetailPageButton> {
|
||||||
bool tapInProgress = false;
|
bool tapInProgress = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -111,7 +132,7 @@ class _CloseButtonState extends State<CloseButton> {
|
|||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: ColorChangingIcon(
|
child: ColorChangingIcon(
|
||||||
CupertinoIcons.clear_thick,
|
widget.icon,
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
color: tapInProgress
|
color: tapInProgress
|
||||||
? Styles.closeButtonPressed
|
? Styles.closeButtonPressed
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
// Copyright 2021, the Flutter 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/cupertino.dart';
|
|
||||||
|
|
||||||
class FadeTransitionPage<T> extends Page<T> {
|
|
||||||
final Widget child;
|
|
||||||
final Duration duration;
|
|
||||||
|
|
||||||
const FadeTransitionPage({
|
|
||||||
super.key,
|
|
||||||
required this.child,
|
|
||||||
this.duration = const Duration(milliseconds: 300),
|
|
||||||
super.restorationId,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Route<T> createRoute(BuildContext context) =>
|
|
||||||
PageBasedFadeTransitionRoute<T>(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
class PageBasedFadeTransitionRoute<T> extends PageRoute<T> {
|
|
||||||
PageBasedFadeTransitionRoute(FadeTransitionPage<T> page)
|
|
||||||
: super(settings: page);
|
|
||||||
|
|
||||||
FadeTransitionPage<T> get _page => settings as FadeTransitionPage<T>;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Color? get barrierColor => null;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String? get barrierLabel => null;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Duration get transitionDuration => _page.duration;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Duration get reverseTransitionDuration => _page.duration;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get maintainState => true;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildPage(
|
|
||||||
BuildContext context,
|
|
||||||
Animation<double> animation,
|
|
||||||
Animation<double> secondaryAnimation,
|
|
||||||
) {
|
|
||||||
return _page.child;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildTransitions(
|
|
||||||
BuildContext context,
|
|
||||||
Animation<double> animation,
|
|
||||||
Animation<double> secondaryAnimation,
|
|
||||||
Widget child,
|
|
||||||
) {
|
|
||||||
final tween = CurveTween(curve: Curves.easeInOut);
|
|
||||||
return FadeTransition(
|
|
||||||
opacity: animation.drive(tween),
|
|
||||||
child: _page.child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:veggieseasons/styles.dart';
|
import '../styles.dart';
|
||||||
|
|
||||||
import 'settings_item.dart';
|
import 'settings_item.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:veggieseasons/styles.dart';
|
import '../styles.dart';
|
||||||
|
|
||||||
// The widgets in this file present a Cupertino-style settings item to the user.
|
// The widgets in this file present a Cupertino-style settings item to the user.
|
||||||
// In the future, the Cupertino package in the Flutter SDK will include
|
// In the future, the Cupertino package in the Flutter SDK will include
|
||||||
|
|||||||
@@ -1,249 +0,0 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
import 'package:veggieseasons/data/app_state.dart';
|
|
||||||
import 'package:veggieseasons/data/veggie.dart';
|
|
||||||
import 'package:veggieseasons/styles.dart';
|
|
||||||
|
|
||||||
/// Presents a series of trivia questions about a particular widget, and tracks
|
|
||||||
/// the user's score.
|
|
||||||
class TriviaView extends StatefulWidget {
|
|
||||||
final int? id;
|
|
||||||
final String? restorationId;
|
|
||||||
|
|
||||||
const TriviaView({this.id, this.restorationId, super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<TriviaView> createState() => _TriviaViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Possible states of the game.
|
|
||||||
enum PlayerStatus {
|
|
||||||
readyToAnswer,
|
|
||||||
wasCorrect,
|
|
||||||
wasIncorrect,
|
|
||||||
}
|
|
||||||
|
|
||||||
class _TriviaViewState extends State<TriviaView> with RestorationMixin {
|
|
||||||
/// Current app state. This is used to fetch veggie data.
|
|
||||||
late AppState appState;
|
|
||||||
|
|
||||||
/// The veggie trivia about which to show.
|
|
||||||
late Veggie veggie;
|
|
||||||
|
|
||||||
/// Index of the current trivia question.
|
|
||||||
RestorableInt triviaIndex = RestorableInt(0);
|
|
||||||
|
|
||||||
/// User's score on the current veggie.
|
|
||||||
RestorableInt score = RestorableInt(0);
|
|
||||||
|
|
||||||
/// Trivia question currently being displayed.
|
|
||||||
Trivia get currentTrivia => veggie.trivia[triviaIndex.value];
|
|
||||||
|
|
||||||
/// The current state of the game.
|
|
||||||
_RestorablePlayerStatus status =
|
|
||||||
_RestorablePlayerStatus(PlayerStatus.readyToAnswer);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String? get restorationId => widget.restorationId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
|
|
||||||
registerForRestoration(triviaIndex, 'index');
|
|
||||||
registerForRestoration(score, 'score');
|
|
||||||
registerForRestoration(status, 'status');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called at init and again if any dependencies (read: InheritedWidgets) on
|
|
||||||
// on which this object relies are changed.
|
|
||||||
@override
|
|
||||||
void didChangeDependencies() {
|
|
||||||
super.didChangeDependencies();
|
|
||||||
|
|
||||||
final newAppState = Provider.of<AppState>(context);
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
appState = newAppState;
|
|
||||||
veggie = appState.getVeggie(widget.id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when the widget associated with this object is swapped out for a new
|
|
||||||
// one. If the new widget has a different Veggie ID value, the state object
|
|
||||||
// needs to do a little work to reset itself for the new Veggie.
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(TriviaView oldWidget) {
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
|
|
||||||
if (oldWidget.id != widget.id) {
|
|
||||||
setState(() {
|
|
||||||
veggie = appState.getVeggie(widget.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
_resetGame();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
triviaIndex.dispose();
|
|
||||||
score.dispose();
|
|
||||||
status.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if (triviaIndex.value >= veggie.trivia.length) {
|
|
||||||
return _buildFinishedView();
|
|
||||||
} else if (status.value == PlayerStatus.readyToAnswer) {
|
|
||||||
return _buildQuestionView();
|
|
||||||
} else {
|
|
||||||
return _buildResultView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _resetGame() {
|
|
||||||
setState(() {
|
|
||||||
triviaIndex.value = 0;
|
|
||||||
score.value = 0;
|
|
||||||
status.value = PlayerStatus.readyToAnswer;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _processAnswer(int answerIndex) {
|
|
||||||
setState(() {
|
|
||||||
if (answerIndex == currentTrivia.correctAnswerIndex) {
|
|
||||||
status.value = PlayerStatus.wasCorrect;
|
|
||||||
score.value++;
|
|
||||||
} else {
|
|
||||||
status.value = PlayerStatus.wasIncorrect;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Widget shown when the game is over. It includes the score and a button to
|
|
||||||
// restart everything.
|
|
||||||
Widget _buildFinishedView() {
|
|
||||||
final themeData = CupertinoTheme.of(context);
|
|
||||||
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(32),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'All done!',
|
|
||||||
style: Styles.triviaFinishedTitleText(themeData),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text('You answered', style: themeData.textTheme.textStyle),
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
textBaseline: TextBaseline.alphabetic,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'${score.value}',
|
|
||||||
style: Styles.triviaFinishedBigText(themeData),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
child: Text(' of ', style: themeData.textTheme.textStyle),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'${veggie.trivia.length}',
|
|
||||||
style: Styles.triviaFinishedBigText(themeData),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Text('questions correctly!', style: themeData.textTheme.textStyle),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
CupertinoButton(
|
|
||||||
child: const Text('Try Again'),
|
|
||||||
onPressed: () => _resetGame(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Presents the current trivia's question and answer choices.
|
|
||||||
Widget _buildQuestionView() {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
currentTrivia.question,
|
|
||||||
style: CupertinoTheme.of(context).textTheme.textStyle,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 32),
|
|
||||||
for (int i = 0; i < currentTrivia.answers.length; i++)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
child: CupertinoButton(
|
|
||||||
color: CupertinoColors.activeBlue,
|
|
||||||
child: Text(
|
|
||||||
currentTrivia.answers[i],
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
onPressed: () => _processAnswer(i),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shows whether the last answer was right or wrong and prompts the user to
|
|
||||||
// continue through the game.
|
|
||||||
Widget _buildResultView() {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(32),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
status.value == PlayerStatus.wasCorrect
|
|
||||||
? 'That\'s right!'
|
|
||||||
: 'Sorry, that wasn\'t the right answer.',
|
|
||||||
style: CupertinoTheme.of(context).textTheme.textStyle,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
CupertinoButton(
|
|
||||||
child: const Text('Next Question'),
|
|
||||||
onPressed: () => setState(() {
|
|
||||||
triviaIndex.value++;
|
|
||||||
status.value = PlayerStatus.readyToAnswer;
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RestorablePlayerStatus extends RestorableValue<PlayerStatus> {
|
|
||||||
_RestorablePlayerStatus(this._defaultValue);
|
|
||||||
|
|
||||||
final PlayerStatus _defaultValue;
|
|
||||||
|
|
||||||
@override
|
|
||||||
PlayerStatus createDefaultValue() {
|
|
||||||
return _defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
PlayerStatus fromPrimitives(Object? data) {
|
|
||||||
return PlayerStatus.values[data as int];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Object toPrimitives() {
|
|
||||||
return value.index;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didUpdateValue(PlayerStatus? oldValue) {
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,51 +2,18 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:ui' as ui;
|
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:veggieseasons/data/veggie.dart';
|
import '../data/veggie.dart';
|
||||||
import 'package:veggieseasons/styles.dart';
|
import '../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
|
/// A Card-like Widget that responds to tap events by animating changes to its
|
||||||
/// elevation and invoking an optional [onPressed] callback.
|
/// elevation and invoking an optional [onPressed] callback.
|
||||||
class PressableCard extends StatefulWidget {
|
class PressableCard extends StatelessWidget {
|
||||||
const PressableCard({
|
const PressableCard({
|
||||||
required this.child,
|
required this.child,
|
||||||
this.borderRadius = const BorderRadius.all(Radius.circular(5)),
|
this.borderRadius = const BorderRadius.all(Radius.circular(16)),
|
||||||
this.upElevation = 2,
|
|
||||||
this.downElevation = 0,
|
|
||||||
this.shadowColor = CupertinoColors.black,
|
|
||||||
this.duration = const Duration(milliseconds: 100),
|
|
||||||
this.onPressed,
|
this.onPressed,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
@@ -57,42 +24,17 @@ class PressableCard extends StatefulWidget {
|
|||||||
|
|
||||||
final BorderRadius borderRadius;
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: onPressed,
|
||||||
setState(() => cardIsDown = false);
|
child: Container(
|
||||||
if (widget.onPressed != null) {
|
decoration: BoxDecoration(
|
||||||
widget.onPressed!();
|
borderRadius: borderRadius,
|
||||||
}
|
),
|
||||||
},
|
|
||||||
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(
|
child: ClipRRect(
|
||||||
borderRadius: widget.borderRadius,
|
borderRadius: borderRadius,
|
||||||
child: widget.child,
|
child: child,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -115,10 +57,10 @@ class VeggieCard extends StatelessWidget {
|
|||||||
|
|
||||||
Widget _buildDetails(BuildContext context) {
|
Widget _buildDetails(BuildContext context) {
|
||||||
final themeData = CupertinoTheme.of(context);
|
final themeData = CupertinoTheme.of(context);
|
||||||
return FrostyBackground(
|
return Container(
|
||||||
color: const Color(0x90ffffff),
|
color: Colors.white,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.fromLTRB(20, 16, 16, 20),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -126,6 +68,7 @@ class VeggieCard extends StatelessWidget {
|
|||||||
veggie.name,
|
veggie.name,
|
||||||
style: Styles.cardTitleText(themeData),
|
style: Styles.cardTitleText(themeData),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
veggie.shortDescription,
|
veggie.shortDescription,
|
||||||
style: Styles.cardDescriptionText(themeData),
|
style: Styles.cardDescriptionText(themeData),
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:veggieseasons/data/veggie.dart';
|
import '../data/veggie.dart';
|
||||||
import 'package:veggieseasons/styles.dart';
|
import '../styles.dart';
|
||||||
|
|
||||||
class ZoomClipAssetImage extends StatelessWidget {
|
class ZoomClipAssetImage extends StatelessWidget {
|
||||||
const ZoomClipAssetImage({
|
const ZoomClipAssetImage({
|
||||||
|
|||||||
45
veggieseasons/lib/widgets/veggie_seasons_page.dart
Normal file
45
veggieseasons/lib/widgets/veggie_seasons_page.dart
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
// Copyright 2024, the Flutter 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/cupertino.dart';
|
||||||
|
|
||||||
|
class VeggieSeasonsPage<T> extends Page<T> {
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const VeggieSeasonsPage({
|
||||||
|
super.key,
|
||||||
|
required this.child,
|
||||||
|
super.restorationId,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
VeggieSeasonsPageRoute<T> createRoute(BuildContext context) =>
|
||||||
|
VeggieSeasonsPageRoute<T>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
class VeggieSeasonsPageRoute<T> extends PageRoute<T> {
|
||||||
|
VeggieSeasonsPageRoute(VeggieSeasonsPage<T> page) : super(settings: page);
|
||||||
|
|
||||||
|
VeggieSeasonsPage<T> get _page => settings as VeggieSeasonsPage<T>;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color? get barrierColor => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get barrierLabel => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get maintainState => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Duration get transitionDuration => Duration.zero;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildPage(
|
||||||
|
BuildContext context,
|
||||||
|
Animation<double> animation,
|
||||||
|
Animation<double> secondaryAnimation,
|
||||||
|
) =>
|
||||||
|
_page.child;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user