From 2bb837ea6e103dccf4c06fb455c68d44d591b3cc Mon Sep 17 00:00:00 2001 From: Michael Thomsen Date: Mon, 2 Sep 2024 08:19:58 +0200 Subject: [PATCH] Update VeggieSeasons Settings screen to use CupertinoList (#2426) ## 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]. [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 --- veggieseasons/lib/screens/settings.dart | 191 ++++++++++-------- veggieseasons/lib/styles.dart | 16 +- veggieseasons/lib/widgets/settings_group.dart | 111 ---------- veggieseasons/lib/widgets/settings_item.dart | 161 --------------- 4 files changed, 109 insertions(+), 370 deletions(-) delete mode 100644 veggieseasons/lib/widgets/settings_group.dart delete mode 100644 veggieseasons/lib/widgets/settings_item.dart diff --git a/veggieseasons/lib/screens/settings.dart b/veggieseasons/lib/screens/settings.dart index 48a8be26e..350b6735a 100644 --- a/veggieseasons/lib/screens/settings.dart +++ b/veggieseasons/lib/screens/settings.dart @@ -8,8 +8,6 @@ import 'package:provider/provider.dart'; import '../data/preferences.dart'; import '../data/veggie.dart'; import '../styles.dart'; -import '../widgets/settings_group.dart'; -import '../widgets/settings_item.dart'; class VeggieCategorySettingsScreen extends StatelessWidget { const VeggieCategorySettingsScreen({super.key, this.restorationId}); @@ -40,7 +38,7 @@ class VeggieCategorySettingsScreen extends StatelessWidget { child: FutureBuilder>( future: currentPrefs, builder: (context, snapshot) { - final items = []; + final tiles = []; for (final category in VeggieCategory.values) { CupertinoSwitch toggle; @@ -66,17 +64,20 @@ class VeggieCategorySettingsScreen extends StatelessWidget { ); } - items.add(SettingsItem( - label: veggieCategoryNames[category]!, - content: toggle, - )); + tiles.add( + CupertinoListTile.notched( + title: Text(veggieCategoryNames[category]!), + trailing: toggle, + ), + ); } return ListView( restorationId: 'list', children: [ - SettingsGroup( - items: items, + CupertinoListSection.insetGrouped( + hasLeading: false, + children: tiles, ), ], ); @@ -121,33 +122,39 @@ class CalorieSettingsScreen extends StatelessWidget { FutureBuilder( future: model.desiredCalories, builder: (context, snapshot) { - final steps = []; + final tiles = []; for (var cals = max; cals < min; cals += step) { - steps.add( - SettingsItem( - label: cals.toString(), - icon: SettingsIcon( - icon: Styles.checkIcon, + tiles.add( + CupertinoListTile.notched( + title: Text('$cals calories'), + trailing: SettingsIcon( + icon: CupertinoIcons.check_mark, foregroundColor: snapshot.hasData && snapshot.data == cals ? CupertinoColors.activeBlue : Styles.transparentColor, backgroundColor: Styles.transparentColor, ), - onPress: snapshot.hasData + onTap: snapshot.hasData ? () => model.setDesiredCalories(cals) : null, ), ); } - return SettingsGroup( - items: steps, - header: const SettingsGroupHeader('Available calorie levels'), - footer: - const SettingsGroupFooter('These are used for serving ' - 'calculations'), + return CupertinoListSection.insetGrouped( + header: Text( + 'Available calorie levels'.toUpperCase(), + style: Styles.settingsGroupHeaderText( + CupertinoTheme.of(context)), + ), + footer: Text( + 'These are used for serving calculations', + style: Styles.settingsGroupFooterText( + CupertinoTheme.of(context)), + ), + children: tiles, ); }, ), @@ -168,59 +175,50 @@ class SettingsScreen extends StatefulWidget { } class _SettingsScreenState extends State { - SettingsItem _buildCaloriesItem(BuildContext context, Preferences prefs) { - return SettingsItem( - label: 'Calorie Target', - icon: const SettingsIcon( - backgroundColor: Styles.iconBlue, + CupertinoListTile _buildCaloriesTile( + BuildContext context, Preferences prefs) { + return CupertinoListTile.notched( + leading: const SettingsIcon( + backgroundColor: CupertinoColors.systemBlue, icon: Styles.calorieIcon, ), - content: FutureBuilder( + title: const Text('Calorie Target'), + additionalInfo: FutureBuilder( future: prefs.desiredCalories, builder: (context, snapshot) { - return Row( - children: [ - Text( - snapshot.data?.toString() ?? '', - style: CupertinoTheme.of(context).textTheme.textStyle, - ), - const SizedBox(width: 8), - const SettingsNavigationIndicator(), - ], + return Text( + snapshot.data?.toString() ?? '', + style: CupertinoTheme.of(context).textTheme.textStyle, ); }, ), - onPress: () { - context.go('/settings/calories'); - }, + trailing: const CupertinoListTileChevron(), + onTap: () => context.go('/settings/calories'), ); } - SettingsItem _buildCategoriesItem(BuildContext context, Preferences prefs) { - return SettingsItem( - label: 'Preferred Categories', - subtitle: 'What types of veggies you prefer!', - icon: const SettingsIcon( - backgroundColor: Styles.iconGold, + CupertinoListTile _buildCategoriesTile( + BuildContext context, Preferences prefs) { + return CupertinoListTile.notched( + leading: const SettingsIcon( + backgroundColor: CupertinoColors.systemOrange, icon: Styles.preferenceIcon, ), - content: const SettingsNavigationIndicator(), - onPress: () { - context.go('/settings/categories'); - }, + title: const Text('Preferred Categories'), + trailing: const CupertinoListTileChevron(), + onTap: () => context.go('/settings/categories'), ); } - SettingsItem _buildRestoreDefaultsItem( + CupertinoListTile _buildRestoreDefaultsTile( BuildContext context, Preferences prefs) { - return SettingsItem( - label: 'Restore Defaults', - icon: const SettingsIcon( + return CupertinoListTile.notched( + leading: const SettingsIcon( backgroundColor: CupertinoColors.systemRed, icon: Styles.resetIcon, ), - content: const SettingsNavigationIndicator(), - onPress: () { + title: const Text('Restore Defaults'), + onTap: () { showCupertinoDialog( context: context, builder: (context) => CupertinoAlertDialog( @@ -254,36 +252,61 @@ class _SettingsScreenState extends State { Widget build(BuildContext context) { final prefs = Provider.of(context); - return RestorationScope( - restorationId: widget.restorationId, - child: CupertinoPageScaffold( - child: Container( - color: - Styles.scaffoldBackground(CupertinoTheme.brightnessOf(context)), - child: CustomScrollView( - restorationId: 'list', - slivers: [ - const CupertinoSliverNavigationBar( - largeTitle: Text('Settings'), - ), - SliverSafeArea( - top: false, - sliver: SliverList( - delegate: SliverChildListDelegate( - [ - SettingsGroup( - items: [ - _buildCaloriesItem(context, prefs), - _buildCategoriesItem(context, prefs), - _buildRestoreDefaultsItem(context, prefs), - ], - ), - ], - ), + return CupertinoPageScaffold( + backgroundColor: + Styles.scaffoldBackground(CupertinoTheme.brightnessOf(context)), + child: CustomScrollView( + slivers: [ + const CupertinoSliverNavigationBar( + largeTitle: Text('Settings'), + ), + SliverList( + delegate: SliverChildListDelegate( + [ + CupertinoListSection.insetGrouped( + children: [ + _buildCaloriesTile(context, prefs), + _buildCategoriesTile(context, prefs), + ], ), - ), - ], + CupertinoListSection.insetGrouped( + children: [ + _buildRestoreDefaultsTile(context, prefs), + ], + ), + ], + ), ), + ], + ), + ); + } +} + +class SettingsIcon extends StatelessWidget { + const SettingsIcon({ + required this.icon, + this.foregroundColor = CupertinoColors.white, + this.backgroundColor = CupertinoColors.black, + super.key, + }); + + final Color backgroundColor; + final Color foregroundColor; + final IconData icon; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: backgroundColor, + ), + child: Center( + child: Icon( + icon, + color: foregroundColor, + size: 20, ), ), ); diff --git a/veggieseasons/lib/styles.dart b/veggieseasons/lib/styles.dart index b3bd168df..14650c129 100644 --- a/veggieseasons/lib/styles.dart +++ b/veggieseasons/lib/styles.dart @@ -107,7 +107,7 @@ abstract class Styles { static TextStyle settingsGroupFooterText(CupertinoThemeData themeData) => themeData.textTheme.textStyle.copyWith( - color: Styles.settingsGroupSubtitle, + color: const Color(0xff777777), fontSize: 13, letterSpacing: -0.08, ); @@ -116,7 +116,7 @@ abstract class Styles { static Color? scaffoldBackground(Brightness brightness) => brightness == Brightness.light - ? CupertinoColors.lightBackgroundGray + ? CupertinoColors.extraLightBackgroundGray : null; static const frostedBackground = Color(0xccf8f8f8); @@ -203,12 +203,6 @@ abstract class Styles { static const Color settingsBackground = Color(0xffefeff4); - static const Color settingsGroupSubtitle = Color(0xff777777); - - static const Color iconBlue = Color(0xff0000ff); - - static const Color iconGold = Color(0xffdba800); - static const preferenceIcon = IconData( 0xf443, fontFamily: CupertinoIcons.iconFont, @@ -227,12 +221,6 @@ abstract class Styles { fontPackage: CupertinoIcons.iconFontPackage, ); - static const checkIcon = IconData( - 0xf383, - fontFamily: CupertinoIcons.iconFont, - fontPackage: CupertinoIcons.iconFontPackage, - ); - static const servingInfoBorderColor = Color(0xffb0b0b0); static const ColorFilter desaturatedColorFilter = diff --git a/veggieseasons/lib/widgets/settings_group.dart b/veggieseasons/lib/widgets/settings_group.dart deleted file mode 100644 index dacbaee7a..000000000 --- a/veggieseasons/lib/widgets/settings_group.dart +++ /dev/null @@ -1,111 +0,0 @@ -// 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 'package:flutter/cupertino.dart'; -import '../styles.dart'; - -import 'settings_item.dart'; - -// The widgets in this file present a group of Cupertino-style settings items to -// the user. In the future, the Cupertino package in the Flutter SDK will -// include dedicated widgets for this purpose, but for now they're done here. -// -// See https://github.com/flutter/flutter/projects/29 for more info. - -class SettingsGroupHeader extends StatelessWidget { - const SettingsGroupHeader(this.title, {super.key}); - - final String title; - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.only( - left: 15, - right: 15, - bottom: 6, - ), - child: Text( - title.toUpperCase(), - style: Styles.settingsGroupHeaderText(CupertinoTheme.of(context)), - ), - ); - } -} - -class SettingsGroupFooter extends StatelessWidget { - const SettingsGroupFooter(this.title, {super.key}); - - final String title; - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.only( - left: 15, - right: 15, - top: 7.5, - ), - child: Text(title, - style: Styles.settingsGroupFooterText(CupertinoTheme.of(context))), - ); - } -} - -class SettingsGroup extends StatelessWidget { - SettingsGroup({ - required this.items, - this.header, - this.footer, - super.key, - }) : assert(items.isNotEmpty); - - final List items; - final Widget? header; - final Widget? footer; - @override - Widget build(BuildContext context) { - var brightness = CupertinoTheme.brightnessOf(context); - final dividedItems = [items[0]]; - for (var i = 1; i < items.length; i++) { - dividedItems.add(Container( - color: Styles.settingsLineation(brightness), - height: 0.3, - )); - dividedItems.add(items[i]); - } - - return Padding( - padding: EdgeInsets.only( - top: header == null ? 35 : 22, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (header != null) header!, - Container( - decoration: BoxDecoration( - color: CupertinoColors.white, - border: Border( - top: BorderSide( - color: Styles.settingsLineation(brightness), - width: 0, - ), - bottom: BorderSide( - color: Styles.settingsLineation(brightness), - width: 0, - ), - ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: dividedItems, - ), - ), - if (footer != null) footer!, - ], - ), - ); - } -} diff --git a/veggieseasons/lib/widgets/settings_item.dart b/veggieseasons/lib/widgets/settings_item.dart deleted file mode 100644 index 16a7dd19a..000000000 --- a/veggieseasons/lib/widgets/settings_item.dart +++ /dev/null @@ -1,161 +0,0 @@ -// 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:async'; - -import 'package:flutter/cupertino.dart'; -import '../styles.dart'; - -// 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 -// dedicated widgets for this purpose, but for now they're done here. -// -// See https://github.com/flutter/flutter/projects/29 for more info. - -typedef SettingsItemCallback = FutureOr Function(); - -class SettingsNavigationIndicator extends StatelessWidget { - const SettingsNavigationIndicator({super.key}); - - @override - Widget build(BuildContext context) { - return const Icon( - CupertinoIcons.forward, - color: Styles.settingsMediumGray, - size: 21, - ); - } -} - -class SettingsIcon extends StatelessWidget { - const SettingsIcon({ - required this.icon, - this.foregroundColor = CupertinoColors.white, - this.backgroundColor = CupertinoColors.black, - super.key, - }); - - final Color backgroundColor; - final Color foregroundColor; - final IconData icon; - - @override - Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(5), - color: backgroundColor, - ), - child: Center( - child: Icon( - icon, - color: foregroundColor, - size: 20, - ), - ), - ); - } -} - -class SettingsItem extends StatefulWidget { - const SettingsItem({ - required this.label, - this.icon, - this.content, - this.subtitle, - this.onPress, - super.key, - }); - - final String label; - final Widget? icon; - final Widget? content; - final String? subtitle; - final SettingsItemCallback? onPress; - - @override - State createState() => _SettingsItemState(); -} - -class _SettingsItemState extends State { - bool pressed = false; - - @override - Widget build(BuildContext context) { - var themeData = CupertinoTheme.of(context); - var brightness = CupertinoTheme.brightnessOf(context); - return AnimatedContainer( - duration: const Duration(milliseconds: 200), - color: Styles.settingsItemColor(brightness), - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () async { - if (widget.onPress != null) { - setState(() { - pressed = true; - }); - await widget.onPress!(); - Future.delayed( - const Duration(milliseconds: 150), - () { - setState(() { - pressed = false; - }); - }, - ); - } - }, - child: SizedBox( - height: widget.subtitle == null ? 44 : 57, - child: Row( - children: [ - if (widget.icon != null) - Padding( - padding: const EdgeInsets.only( - left: 15, - bottom: 2, - ), - child: SizedBox( - height: 29, - width: 29, - child: widget.icon, - ), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.only( - left: 15, - ), - child: widget.subtitle != null - ? Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 8.5), - Text(widget.label, - style: themeData.textTheme.textStyle), - const SizedBox(height: 4), - Text( - widget.subtitle!, - style: Styles.settingsItemSubtitleText(themeData), - ), - ], - ) - : Padding( - padding: const EdgeInsets.only(top: 1.5), - child: Text(widget.label, - style: themeData.textTheme.textStyle), - ), - ), - ), - Padding( - padding: const EdgeInsets.only(right: 11), - child: widget.content ?? Container(), - ), - ], - ), - ), - ), - ); - } -}