mirror of
https://github.com/flutter/samples.git
synced 2025-11-08 13:58:47 +00:00
Fleshes out settings screen with groups, items, and navigation (#43)
This commit is contained in:
@@ -228,7 +228,7 @@ class LocalVeggieProvider {
|
|||||||
name: 'Squash',
|
name: 'Squash',
|
||||||
imageAssetPath: 'assets/images/squash.jpg',
|
imageAssetPath: 'assets/images/squash.jpg',
|
||||||
category: VeggieCategory.gourd,
|
category: VeggieCategory.gourd,
|
||||||
shortDescription: 'Bigger and heartier than summer squashes.',
|
shortDescription: 'Just slather them in butter and pop \'em in the oven.',
|
||||||
accentColor: Color(0x40dbb721),
|
accentColor: Color(0x40dbb721),
|
||||||
seasons: [Season.winter, Season.autumn],
|
seasons: [Season.winter, Season.autumn],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -68,12 +68,14 @@ class Preferences extends Model {
|
|||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
_desiredCalories = prefs.getInt(_caloriesKey) ?? 2000;
|
_desiredCalories = prefs.getInt(_caloriesKey) ?? 2000;
|
||||||
_preferredCategories.clear();
|
_preferredCategories.clear();
|
||||||
final names = prefs.getString(_preferredCategoriesKey) ?? '';
|
final names = prefs.getString(_preferredCategoriesKey);
|
||||||
|
|
||||||
for (final name in names.split(',')) {
|
if (names != null) {
|
||||||
final index = int.parse(name) ?? 0;
|
for (final name in names.split(',')) {
|
||||||
if (VeggieCategory.values[index] != null) {
|
final index = int.tryParse(name) ?? -1;
|
||||||
_preferredCategories.add(VeggieCategory.values[index]);
|
if (VeggieCategory.values[index] != null) {
|
||||||
|
_preferredCategories.add(VeggieCategory.values[index]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,19 +3,208 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:scoped_model/scoped_model.dart';
|
||||||
|
import 'package:veggieseasons/data/preferences.dart';
|
||||||
|
import 'package:veggieseasons/data/veggie.dart';
|
||||||
import 'package:veggieseasons/styles.dart';
|
import 'package:veggieseasons/styles.dart';
|
||||||
|
import 'package:veggieseasons/widgets/settings_group.dart';
|
||||||
|
import 'package:veggieseasons/widgets/settings_item.dart';
|
||||||
|
|
||||||
class SettingsScreen extends StatelessWidget {
|
class VeggieCategorySettingsScreen extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final model = ScopedModel.of<Preferences>(context, rebuildOnChange: true);
|
||||||
|
final currentPrefs = model.preferredCategories;
|
||||||
|
|
||||||
return CupertinoPageScaffold(
|
return CupertinoPageScaffold(
|
||||||
navigationBar: CupertinoNavigationBar(
|
navigationBar: CupertinoNavigationBar(
|
||||||
middle: Text('Settings'),
|
middle: Text('Preferred Categories'),
|
||||||
|
previousPageTitle: 'Settings',
|
||||||
),
|
),
|
||||||
backgroundColor: Styles.scaffoldBackground,
|
backgroundColor: Styles.scaffoldBackground,
|
||||||
child: Center(
|
child: FutureBuilder<Set<VeggieCategory>>(
|
||||||
child: Text('Not yet implemented.'),
|
future: currentPrefs,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final items = <SettingsItem>[];
|
||||||
|
|
||||||
|
for (final category in VeggieCategory.values) {
|
||||||
|
CupertinoSwitch toggle;
|
||||||
|
|
||||||
|
// It's possible that category data hasn't loaded from shared prefs
|
||||||
|
// yet, so display it if possible and fall back to disabled switches
|
||||||
|
// otherwise.
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
toggle = CupertinoSwitch(
|
||||||
|
value: snapshot.data.contains(category),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value) {
|
||||||
|
model.addPreferredCategory(category);
|
||||||
|
} else {
|
||||||
|
model.removePreferredCategory(category);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
toggle = CupertinoSwitch(
|
||||||
|
value: false,
|
||||||
|
onChanged: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
items.add(SettingsItem(
|
||||||
|
label: veggieCategoryNames[category],
|
||||||
|
content: toggle,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView(
|
||||||
|
children: [
|
||||||
|
SettingsGroup(
|
||||||
|
items: items,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CalorieSettingsScreen extends StatelessWidget {
|
||||||
|
static const max = 1000;
|
||||||
|
static const min = 2600;
|
||||||
|
static const step = 200;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final model = ScopedModel.of<Preferences>(context, rebuildOnChange: true);
|
||||||
|
|
||||||
|
return CupertinoPageScaffold(
|
||||||
|
navigationBar: CupertinoNavigationBar(
|
||||||
|
previousPageTitle: 'Settings',
|
||||||
|
),
|
||||||
|
backgroundColor: Styles.scaffoldBackground,
|
||||||
|
child: ListView(
|
||||||
|
children: [
|
||||||
|
FutureBuilder<int>(
|
||||||
|
future: model.desiredCalories,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final steps = <SettingsItem>[];
|
||||||
|
|
||||||
|
for (int cals = max; cals < min; cals += step) {
|
||||||
|
steps.add(
|
||||||
|
SettingsItem(
|
||||||
|
label: cals.toString(),
|
||||||
|
icon: SettingsIcon(
|
||||||
|
icon: Styles.checkIcon,
|
||||||
|
foregroundColor: snapshot.hasData && snapshot.data == cals
|
||||||
|
? CupertinoColors.activeBlue
|
||||||
|
: Styles.transparentColor,
|
||||||
|
backgroundColor: Styles.transparentColor,
|
||||||
|
),
|
||||||
|
onPress: snapshot.hasData
|
||||||
|
? () => model.setDesiredCalories(cals)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SettingsGroup(
|
||||||
|
items: steps,
|
||||||
|
header: SettingsGroupHeader('Available calorie levels'),
|
||||||
|
footer: SettingsGroupFooter('These are used for serving '
|
||||||
|
'calculations'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SettingsScreen extends StatelessWidget {
|
||||||
|
Widget _buildCaloriesItem(BuildContext context, Preferences prefs) {
|
||||||
|
return SettingsItem(
|
||||||
|
label: 'Calorie Target',
|
||||||
|
icon: SettingsIcon(
|
||||||
|
backgroundColor: Styles.iconBlue,
|
||||||
|
icon: Styles.calorieIcon,
|
||||||
|
),
|
||||||
|
content: FutureBuilder<int>(
|
||||||
|
future: prefs.desiredCalories,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
return Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Text(snapshot.data?.toString() ?? ''),
|
||||||
|
SizedBox(width: 8.0),
|
||||||
|
SettingsNavigationIndicator(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onPress: () {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
CupertinoPageRoute(
|
||||||
|
builder: (context) => CalorieSettingsScreen(),
|
||||||
|
title: 'Calorie Target',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCategoriesItem(BuildContext context, Preferences prefs) {
|
||||||
|
return SettingsItem(
|
||||||
|
label: 'Preferred Categories',
|
||||||
|
subtitle: 'What types of veggies you prefer!',
|
||||||
|
icon: SettingsIcon(
|
||||||
|
backgroundColor: Styles.iconGold,
|
||||||
|
icon: Styles.preferenceIcon,
|
||||||
|
),
|
||||||
|
content: SettingsNavigationIndicator(),
|
||||||
|
onPress: () {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
CupertinoPageRoute(
|
||||||
|
builder: (context) => VeggieCategorySettingsScreen(),
|
||||||
|
title: 'Preferred Categories',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final prefs = ScopedModel.of<Preferences>(context, rebuildOnChange: true);
|
||||||
|
|
||||||
|
return CupertinoPageScaffold(
|
||||||
|
child: Container(
|
||||||
|
color: Styles.scaffoldBackground,
|
||||||
|
child: CustomScrollView(
|
||||||
|
slivers: <Widget>[
|
||||||
|
CupertinoSliverNavigationBar(
|
||||||
|
largeTitle: Text('Settings'),
|
||||||
|
),
|
||||||
|
SliverSafeArea(
|
||||||
|
top: false,
|
||||||
|
sliver: SliverList(
|
||||||
|
delegate: SliverChildListDelegate(
|
||||||
|
<Widget>[
|
||||||
|
SettingsGroup(
|
||||||
|
items: [
|
||||||
|
_buildCaloriesItem(context, prefs),
|
||||||
|
_buildCategoriesItem(context, prefs),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
132
veggieseasons/lib/widgets/settings_group.dart
Normal file
132
veggieseasons/lib/widgets/settings_group.dart
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
// 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 'package:veggieseasons/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);
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: 15.0,
|
||||||
|
right: 15.0,
|
||||||
|
bottom: 6.0,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
title.toUpperCase(),
|
||||||
|
style: TextStyle(
|
||||||
|
color: CupertinoColors.inactiveGray,
|
||||||
|
fontSize: 13.5,
|
||||||
|
letterSpacing: -0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SettingsGroupFooter extends StatelessWidget {
|
||||||
|
const SettingsGroupFooter(this.title);
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: 15.0,
|
||||||
|
right: 15.0,
|
||||||
|
top: 7.5,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Styles.settingsGroupSubtitle,
|
||||||
|
fontSize: 13.0,
|
||||||
|
letterSpacing: -0.08,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SettingsGroup extends StatelessWidget {
|
||||||
|
SettingsGroup({
|
||||||
|
@required this.items,
|
||||||
|
this.header,
|
||||||
|
this.footer,
|
||||||
|
}) : assert(items != null),
|
||||||
|
assert(items.length > 0);
|
||||||
|
|
||||||
|
final List<SettingsItem> items;
|
||||||
|
final Widget header;
|
||||||
|
final Widget footer;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final dividedItems = <Widget>[items[0]];
|
||||||
|
|
||||||
|
for (int i = 1; i < items.length; i++) {
|
||||||
|
dividedItems.add(Container(
|
||||||
|
color: Styles.settingsLineation,
|
||||||
|
height: 0.3,
|
||||||
|
));
|
||||||
|
dividedItems.add(items[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Widget> columnChildren = [];
|
||||||
|
|
||||||
|
if (header != null) {
|
||||||
|
columnChildren.add(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
columnChildren.add(
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: CupertinoColors.white,
|
||||||
|
border: Border(
|
||||||
|
top: const BorderSide(
|
||||||
|
color: Styles.settingsLineation,
|
||||||
|
width: 0.0,
|
||||||
|
),
|
||||||
|
bottom: const BorderSide(
|
||||||
|
color: Styles.settingsLineation,
|
||||||
|
width: 0.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: dividedItems,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (footer != null) {
|
||||||
|
columnChildren.add(footer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
top: header == null ? 35.0 : 22.0,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: columnChildren,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
179
veggieseasons/lib/widgets/settings_item.dart
Normal file
179
veggieseasons/lib/widgets/settings_item.dart
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
// 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 'package:veggieseasons/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 FutureOr<void> SettingsItemCallback();
|
||||||
|
|
||||||
|
class SettingsNavigationIndicator extends StatelessWidget {
|
||||||
|
const SettingsNavigationIndicator({Key key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Icon(
|
||||||
|
CupertinoIcons.forward,
|
||||||
|
color: Styles.settingsMediumGray,
|
||||||
|
size: 21.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SettingsIcon extends StatelessWidget {
|
||||||
|
const SettingsIcon({
|
||||||
|
@required this.icon,
|
||||||
|
this.foregroundColor = CupertinoColors.white,
|
||||||
|
this.backgroundColor = CupertinoColors.black,
|
||||||
|
Key key,
|
||||||
|
}) : assert(icon != null),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
final Color backgroundColor;
|
||||||
|
final Color foregroundColor;
|
||||||
|
final IconData icon;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(5.0),
|
||||||
|
color: backgroundColor,
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Icon(
|
||||||
|
icon,
|
||||||
|
color: foregroundColor,
|
||||||
|
size: 20.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SettingsItem extends StatefulWidget {
|
||||||
|
const SettingsItem({
|
||||||
|
@required this.label,
|
||||||
|
this.icon,
|
||||||
|
this.content,
|
||||||
|
this.subtitle,
|
||||||
|
this.onPress,
|
||||||
|
Key key,
|
||||||
|
}) : assert(label != null),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
final String label;
|
||||||
|
final Widget icon;
|
||||||
|
final Widget content;
|
||||||
|
final String subtitle;
|
||||||
|
final SettingsItemCallback onPress;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => new SettingsItemState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class SettingsItemState extends State<SettingsItem> {
|
||||||
|
bool pressed = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
List<Widget> rowChildren = [];
|
||||||
|
|
||||||
|
if (widget.icon != null) {
|
||||||
|
rowChildren.add(
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 15.0,
|
||||||
|
bottom: 2.0,
|
||||||
|
),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 29.0,
|
||||||
|
width: 29.0,
|
||||||
|
child: widget.icon,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget titleSection;
|
||||||
|
|
||||||
|
if (widget.subtitle == null) {
|
||||||
|
titleSection = Padding(
|
||||||
|
padding: EdgeInsets.only(top: 1.5),
|
||||||
|
child: Text(widget.label),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
titleSection = Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
SizedBox(height: 8.5),
|
||||||
|
Text(widget.label),
|
||||||
|
SizedBox(height: 4.0),
|
||||||
|
Text(
|
||||||
|
widget.subtitle,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12.0,
|
||||||
|
letterSpacing: -0.2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
rowChildren.add(
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 15.0,
|
||||||
|
),
|
||||||
|
child: titleSection,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
rowChildren.add(
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 11.0),
|
||||||
|
child: widget.content ?? Container(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
color: pressed ? Styles.settingsItemPressed : Styles.transparentColor,
|
||||||
|
child: GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () async {
|
||||||
|
if (widget.onPress != null) {
|
||||||
|
setState(() {
|
||||||
|
pressed = true;
|
||||||
|
});
|
||||||
|
await widget.onPress();
|
||||||
|
Future.delayed(
|
||||||
|
Duration(milliseconds: 150),
|
||||||
|
() {
|
||||||
|
setState(() {
|
||||||
|
pressed = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
height: widget.subtitle == null ? 44.0 : 57.0,
|
||||||
|
child: Row(
|
||||||
|
children: rowChildren,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user