Commit d96cbdd0 authored by Adam Barth's avatar Adam Barth

Port demo_launcher and fitness to fn3

parent 8fb59f1d
......@@ -5,7 +5,7 @@
import 'package:sky/material.dart';
import 'package:sky/painting.dart';
import 'package:sky/services.dart';
import 'package:sky/widgets.dart';
import 'package:sky/widgets_next.dart';
AssetBundle _initBundle() {
if (rootBundle != null)
......@@ -138,7 +138,7 @@ List<SkyDemo> demos = [
const double kCardHeight = 120.0;
const EdgeDims kListPadding = const EdgeDims.all(4.0);
class DemoList extends Component {
class DemoList extends StatelessComponent {
Widget buildCardContents(SkyDemo demo) {
return new Container(
decoration: demo.decoration,
......@@ -158,7 +158,7 @@ class DemoList extends Component {
);
}
Widget buildDemo(SkyDemo demo) {
Widget buildDemo(BuildContext context, SkyDemo demo) {
return new GestureDetector(
key: demo.key,
onTap: () => launch(demo.href, demo.bundle),
......@@ -171,7 +171,7 @@ class DemoList extends Component {
);
}
Widget build() {
Widget build(BuildContext context) {
return new ScrollableList<SkyDemo>(
items: demos,
itemExtent: kCardHeight,
......@@ -181,27 +181,29 @@ class DemoList extends Component {
}
}
class SkyHome extends App {
Widget build() {
return new Theme(
data: new ThemeData(
brightness: ThemeBrightness.light,
primarySwatch: Colors.teal
),
child: new Title(
title: 'Sky Demos',
child: new Scaffold(
toolbar: new ToolBar(center: new Text('Sky Demos')),
body: new Material(
type: MaterialType.canvas,
child: new DemoList()
)
)
final ThemeData _theme = new ThemeData(
brightness: ThemeBrightness.light,
primarySwatch: Colors.teal
);
class DemoHome extends StatelessComponent {
Widget build(BuildContext context) {
return new Scaffold(
toolbar: new ToolBar(center: new Text('Sky Demos')),
body: new Material(
type: MaterialType.canvas,
child: new DemoList()
)
);
}
}
void main() {
runApp(new SkyHome());
runApp(new App(
title: 'Sky Demos',
theme: _theme,
routes: {
'/': (NavigatorState navigator, Route route) => new DemoHome()
}
));
}
......@@ -4,7 +4,7 @@
part of fitness;
class FitnessItemList extends Component {
class FitnessItemList extends StatelessComponent {
FitnessItemList({ Key key, this.items, this.onDismissed }) : super(key: key) {
assert(items != null);
assert(onDismissed != null);
......@@ -13,32 +13,26 @@ class FitnessItemList extends Component {
final List<FitnessItem> items;
final FitnessItemHandler onDismissed;
Widget build() {
Widget build(BuildContext context) {
return new Material(
type: MaterialType.canvas,
child: new ScrollableList<FitnessItem>(
padding: const EdgeDims.all(4.0),
items: items,
itemExtent: kFitnessItemHeight,
itemBuilder: (item) => item.toRow(onDismissed: onDismissed)
itemBuilder: (_, item) => item.toRow(onDismissed: onDismissed)
)
);
}
}
class DialogMenuItem extends ButtonBase {
class DialogMenuItem extends StatelessComponent {
DialogMenuItem(this.children, { Key key, this.onPressed }) : super(key: key);
List<Widget> children;
Function onPressed;
void syncConstructorArguments(DialogMenuItem source) {
children = source.children;
onPressed = source.onPressed;
super.syncConstructorArguments(source);
}
Widget buildContent() {
Widget build(BuildContext context) {
return new GestureDetector(
onTap: onPressed,
child: new Container(
......@@ -57,25 +51,16 @@ class DialogMenuItem extends ButtonBase {
class FeedFragment extends StatefulComponent {
FeedFragment({ this.navigator, this.userData, this.onItemCreated, this.onItemDeleted });
Navigator navigator;
UserData userData;
FitnessItemHandler onItemCreated;
FitnessItemHandler onItemDeleted;
final NavigatorState navigator;
final UserData userData;
final FitnessItemHandler onItemCreated;
final FitnessItemHandler onItemDeleted;
FitnessMode _fitnessMode = FitnessMode.feed;
void initState() {
// if (debug)
// new Timer(new Duration(seconds: 1), dumpState);
super.initState();
}
FeedFragmentState createState() => new FeedFragmentState();
}
void syncConstructorArguments(FeedFragment source) {
navigator = source.navigator;
userData = source.userData;
onItemCreated = source.onItemCreated;
onItemDeleted = source.onItemDeleted;
}
class FeedFragmentState extends State<FeedFragment> {
FitnessMode _fitnessMode = FitnessMode.feed;
AnimationStatus _snackBarStatus = AnimationStatus.dismissed;
bool _isShowingSnackBar = false;
......@@ -94,7 +79,7 @@ class FeedFragment extends StatefulComponent {
showing: _drawerShowing,
level: 3,
onDismissed: _handleDrawerDismissed,
navigator: navigator,
navigator: config.navigator,
children: [
new DrawerHeader(child: new Text('Fitness')),
new DrawerItem(
......@@ -136,8 +121,8 @@ class FeedFragment extends StatefulComponent {
}
void _handleShowSettings() {
navigator.pop();
navigator.pushNamed('/settings');
config.navigator.pop();
config.navigator.pushNamed('/settings');
}
// TODO(jackson): We should be localizing
......@@ -160,7 +145,7 @@ class FeedFragment extends StatefulComponent {
FitnessItem _undoItem;
void _handleItemDismissed(FitnessItem item) {
onItemDeleted(item);
config.onItemDeleted(item);
setState(() {
_undoItem = item;
_isShowingSnackBar = true;
......@@ -174,7 +159,7 @@ class FeedFragment extends StatefulComponent {
double startY;
double endY;
List<Point> dataSet = new List<Point>();
for (FitnessItem item in userData.items) {
for (FitnessItem item in config.userData.items) {
if (item is Measurement) {
double x = item.when.millisecondsSinceEpoch.toDouble();
double y = item.weight;
......@@ -189,9 +174,9 @@ class FeedFragment extends StatefulComponent {
dataSet.add(new Point(x, y));
}
}
if (userData.goalWeight != null && userData.goalWeight > 0.0) {
startY = math.min(startY, userData.goalWeight);
endY = math.max(endY, userData.goalWeight);
if (config.userData.goalWeight != null && config.userData.goalWeight > 0.0) {
startY = math.min(startY, config.userData.goalWeight);
endY = math.max(endY, config.userData.goalWeight);
}
playfair.ChartData data = new playfair.ChartData(
startX: startX,
......@@ -201,17 +186,17 @@ class FeedFragment extends StatefulComponent {
dataSet: dataSet,
numHorizontalGridlines: 5,
roundToPlaces: 1,
indicatorLine: userData.goalWeight,
indicatorLine: config.userData.goalWeight,
indicatorText: "GOAL WEIGHT"
);
return new playfair.Chart(data: data);
}
Widget buildBody() {
TextStyle style = Theme.of(this).text.title;
if (userData == null)
TextStyle style = Theme.of(context).text.title;
if (config.userData == null)
return new Material(type: MaterialType.canvas);
if (userData.items.length == 0)
if (config.userData.items.length == 0)
return new Material(
type: MaterialType.canvas,
child: new Row(
......@@ -222,7 +207,7 @@ class FeedFragment extends StatefulComponent {
switch (_fitnessMode) {
case FitnessMode.feed:
return new FitnessItemList(
items: userData.items.reversed.toList(),
items: config.userData.items.reversed.toList(),
onDismissed: _handleItemDismissed
);
case FitnessMode.chart:
......@@ -237,7 +222,7 @@ class FeedFragment extends StatefulComponent {
}
void _handleUndo() {
onItemCreated(_undoItem);
config.onItemCreated(_undoItem);
setState(() {
_undoItem = null;
_isShowingSnackBar = false;
......@@ -256,9 +241,9 @@ class FeedFragment extends StatefulComponent {
}
void _handleActionButtonPressed() {
showDialog(navigator, (navigator) => new AddItemDialog(navigator)).then((routeName) {
showDialog(config.navigator, (NavigatorState navigator) => new AddItemDialog(navigator)).then((routeName) {
if (routeName != null)
navigator.pushNamed(routeName);
config.navigator.pushNamed(routeName);
});
}
......@@ -274,7 +259,7 @@ class FeedFragment extends StatefulComponent {
}
}
Widget build() {
Widget build(BuildContext context) {
return new Scaffold(
toolbar: buildToolBar(),
body: buildBody(),
......@@ -288,12 +273,12 @@ class FeedFragment extends StatefulComponent {
class AddItemDialog extends StatefulComponent {
AddItemDialog(this.navigator);
Navigator navigator;
final NavigatorState navigator;
void syncConstructorArguments(AddItemDialog source) {
this.navigator = source.navigator;
}
AddItemDialogState createState() => new AddItemDialogState();
}
class AddItemDialogState extends State<AddItemDialog> {
// TODO(jackson): Internationalize
static final Map<String, String> _labels = {
'/measurements/new': 'Measure',
......@@ -308,7 +293,7 @@ class AddItemDialog extends StatefulComponent {
});
}
Widget build() {
Widget build(BuildContext context) {
List<Widget> menuItems = [];
for(String routeName in _labels.keys) {
menuItems.add(new DialogMenuItem([
......@@ -319,16 +304,16 @@ class AddItemDialog extends StatefulComponent {
return new Dialog(
title: new Text("What are you doing?"),
content: new Block(menuItems),
onDismiss: navigator.pop,
onDismiss: config.navigator.pop,
actions: [
new FlatButton(
child: new Text('CANCEL'),
onPressed: navigator.pop
onPressed: config.navigator.pop
),
new FlatButton(
child: new Text('ADD'),
onPressed: () {
navigator.pop(_addItemRoute);
config.navigator.pop(_addItemRoute);
}
),
]
......
......@@ -26,7 +26,7 @@ abstract class FitnessItem {
FitnessItemRow toRow({ FitnessItemHandler onDismissed });
}
abstract class FitnessItemRow extends Component {
abstract class FitnessItemRow extends StatelessComponent {
FitnessItemRow({ FitnessItem item, this.onDismissed })
: this.item = item,
......@@ -37,9 +37,9 @@ abstract class FitnessItemRow extends Component {
final FitnessItem item;
final FitnessItemHandler onDismissed;
Widget buildContent();
Widget buildContent(BuildContext context);
Widget build() {
Widget build(BuildContext context) {
return new Dismissable(
onDismissed: () => onDismissed(item),
child: new Container(
......@@ -51,10 +51,10 @@ abstract class FitnessItemRow extends Component {
// stay put even when the tile is dismissed!
decoration: new BoxDecoration(
border: new Border(
bottom: new BorderSide(color: Theme.of(this).dividerColor)
bottom: new BorderSide(color: Theme.of(context).dividerColor)
)
),
child: buildContent()
child: buildContent(context)
)
);
}
......
......@@ -8,7 +8,7 @@ import 'package:playfair/playfair.dart' as playfair;
import 'package:sky/animation.dart';
import 'package:sky/material.dart';
import 'package:sky/painting.dart';
import 'package:sky/widgets.dart';
import 'package:sky/src/fn3.dart';
import 'user_data.dart';
import 'date_utils.dart';
......@@ -85,63 +85,53 @@ class UserDataImpl extends UserData {
}
}
class FitnessApp extends App {
NavigationState _navigationState;
class FitnessApp extends StatefulComponent {
FitnessAppState createState() => new FitnessAppState();
}
class FitnessAppState extends State<FitnessApp> {
UserDataImpl _userData;
void didMount() {
super.didMount();
Map<String, RouteBuilder> _routes;
void initState() {
super.initState();
loadFitnessData().then((UserData data) {
setState(() => _userData = data);
}).catchError((e) {
print("Failed to load data: $e");
setState(() => _userData = new UserDataImpl());
});
}
void initState() {
_navigationState = new NavigationState([
new Route(
name: '/',
builder: (navigator, route) => new FeedFragment(
_routes = {
'/': (NavigatorState navigator, Route route) {
return new FeedFragment(
navigator: navigator,
userData: _userData,
onItemCreated: _handleItemCreated,
onItemDeleted: _handleItemDeleted
)
),
new Route(
name: '/meals/new',
builder: (navigator, route) => new MealFragment(
);
},
'/meals/new': (navigator, route) {
return new MealFragment(
navigator: navigator,
onCreated: _handleItemCreated
)
),
new Route(
name: '/measurements/new',
builder: (navigator, route) => new MeasurementFragment(
);
},
'/measurements/new': (NavigatorState navigator, Route route) {
return new MeasurementFragment(
navigator: navigator,
onCreated: _handleItemCreated
)
),
new Route(
name: '/settings',
builder: (navigator, route) => new SettingsFragment(
);
},
'/settings': (navigator, route) {
return new SettingsFragment(
navigator: navigator,
userData: _userData,
updater: settingsUpdater
)
),
]);
super.initState();
}
void onBack() {
if (_navigationState.hasPrevious()) {
setState(() => _navigationState.pop());
} else {
super.onBack();
}
);
}
};
}
void _handleItemCreated(FitnessItem item) {
......@@ -168,17 +158,17 @@ class FitnessApp extends App {
});
}
Widget build() {
return new Theme(
data: new ThemeData(
brightness: ThemeBrightness.light,
primarySwatch: Colors.indigo,
accentColor: Colors.pinkAccent[200]
),
child: new Title(
title: 'Fitness',
child: new Navigator(_navigationState)
)
final ThemeData _theme = new ThemeData(
brightness: ThemeBrightness.light,
primarySwatch: Colors.indigo,
accentColor: Colors.pinkAccent[200]
);
Widget build(BuildContext) {
return new App(
theme: _theme,
title: 'Fitness',
routes: _routes
);
}
}
......
......@@ -18,7 +18,7 @@ class MealRow extends FitnessItemRow {
MealRow({ Meal meal, FitnessItemHandler onDismissed })
: super(item: meal, onDismissed: onDismissed);
Widget buildContent() {
Widget buildContent(BuildContext context) {
Meal meal = item;
List<Widget> children = [
new Flexible(
......@@ -30,42 +30,40 @@ class MealRow extends FitnessItemRow {
new Flexible(
child: new Text(
meal.displayDate,
style: Theme.of(this).text.caption.copyWith(textAlign: TextAlign.right)
style: Theme.of(context).text.caption.copyWith(textAlign: TextAlign.right)
)
)
];
return new Row(
children,
alignItems: FlexAlignItems.baseline,
textBaseline: DefaultTextStyle.of(this).textBaseline
textBaseline: DefaultTextStyle.of(context).textBaseline
);
}
}
class MealFragment extends StatefulComponent {
MealFragment({ this.navigator, this.onCreated });
Navigator navigator;
NavigatorState navigator;
FitnessItemHandler onCreated;
void syncConstructorArguments(MealFragment source) {
navigator = source.navigator;
onCreated = source.onCreated;
}
MealFragmentState createState() => new MealFragmentState();
}
class MealFragmentState extends State<MealFragment> {
String _description = "";
void _handleSave() {
onCreated(new Meal(when: new DateTime.now(), description: _description));
navigator.pop();
config.onCreated(new Meal(when: new DateTime.now(), description: _description));
config.navigator.pop();
}
Widget buildToolBar() {
return new ToolBar(
left: new IconButton(
icon: "navigation/close",
onPressed: navigator.pop),
onPressed: config.navigator.pop),
center: new Text('New Meal'),
right: [new InkWell(
child: new GestureDetector(
......@@ -104,7 +102,7 @@ class MealFragment extends StatefulComponent {
);
}
Widget build() {
Widget build(BuildContext context) {
return new Scaffold(
toolbar: buildToolBar(),
body: buildBody()
......
......@@ -30,26 +30,26 @@ class MeasurementRow extends FitnessItemRow {
MeasurementRow({ Measurement measurement, FitnessItemHandler onDismissed })
: super(item: measurement, onDismissed: onDismissed);
Widget buildContent() {
Widget buildContent(BuildContext context) {
Measurement measurement = item;
List<Widget> children = [
new Flexible(
child: new Text(
measurement.displayWeight,
style: Theme.of(this).text.subhead
style: Theme.of(context).text.subhead
)
),
new Flexible(
child: new Text(
measurement.displayDate,
style: Theme.of(this).text.caption.copyWith(textAlign: TextAlign.right)
style: Theme.of(context).text.caption.copyWith(textAlign: TextAlign.right)
)
)
];
return new Row(
children,
alignItems: FlexAlignItems.baseline,
textBaseline: DefaultTextStyle.of(this).textBaseline
textBaseline: DefaultTextStyle.of(context).textBaseline
);
}
}
......@@ -57,17 +57,16 @@ class MeasurementRow extends FitnessItemRow {
class MeasurementDateDialog extends StatefulComponent {
MeasurementDateDialog({ this.navigator, this.previousDate });
Navigator navigator;
DateTime previousDate;
final NavigatorState navigator;
final DateTime previousDate;
MeasurementDateDialogState createState() => new MeasurementDateDialogState();
}
class MeasurementDateDialogState extends State<MeasurementDateDialog> {
@override
void initState() {
_selectedDate = previousDate;
}
void syncConstructorArguments(MeasurementDateDialog source) {
navigator = source.navigator;
previousDate = source.previousDate;
_selectedDate = config.previousDate;
}
DateTime _selectedDate;
......@@ -78,7 +77,7 @@ class MeasurementDateDialog extends StatefulComponent {
});
}
Widget build() {
Widget build(BuildContext context) {
return new Dialog(
content: new DatePicker(
selectedDate: _selectedDate,
......@@ -90,12 +89,12 @@ class MeasurementDateDialog extends StatefulComponent {
actions: [
new FlatButton(
child: new Text('CANCEL'),
onPressed: navigator.pop
onPressed: config.navigator.pop
),
new FlatButton(
child: new Text('OK'),
onPressed: () {
navigator.pop(_selectedDate);
config.navigator.pop(_selectedDate);
}
),
]
......@@ -104,17 +103,15 @@ class MeasurementDateDialog extends StatefulComponent {
}
class MeasurementFragment extends StatefulComponent {
MeasurementFragment({ this.navigator, this.onCreated });
Navigator navigator;
FitnessItemHandler onCreated;
final NavigatorState navigator;
final FitnessItemHandler onCreated;
void syncConstructorArguments(MeasurementFragment source) {
navigator = source.navigator;
onCreated = source.onCreated;
}
MeasurementFragmentState createState() => new MeasurementFragmentState();
}
class MeasurementFragmentState extends State<MeasurementFragment> {
String _weight = "";
DateTime _when = new DateTime.now();
String _errorMessage = null;
......@@ -129,15 +126,15 @@ class MeasurementFragment extends StatefulComponent {
_errorMessage = "Save failed";
});
}
onCreated(new Measurement(when: _when, weight: parsedWeight));
navigator.pop();
config.onCreated(new Measurement(when: _when, weight: parsedWeight));
config.navigator.pop();
}
Widget buildToolBar() {
return new ToolBar(
left: new IconButton(
icon: "navigation/close",
onPressed: navigator.pop),
onPressed: config.navigator.pop),
center: new Text('New Measurement'),
right: [new InkWell(
child: new GestureDetector(
......@@ -157,7 +154,7 @@ class MeasurementFragment extends StatefulComponent {
static final GlobalKey weightKey = new GlobalKey();
void _handleDatePressed() {
showDialog(navigator, (navigator) {
showDialog(config.navigator, (NavigatorState navigator) {
return new MeasurementDateDialog(navigator: navigator, previousDate: _when);
}).then((DateTime value) {
if (value == null)
......@@ -168,7 +165,7 @@ class MeasurementFragment extends StatefulComponent {
});
}
Widget buildBody() {
Widget buildBody(BuildContext context) {
Measurement measurement = new Measurement(when: _when);
// TODO(jackson): Revisit the layout of this pane to be more maintainable
return new Material(
......@@ -182,7 +179,7 @@ class MeasurementFragment extends StatefulComponent {
height: 50.0,
child: new Column([
new Text('Measurement Date'),
new Text(measurement.displayDate, style: Theme.of(this).text.caption),
new Text(measurement.displayDate, style: Theme.of(context).text.caption),
], alignItems: FlexAlignItems.start)
)
),
......@@ -204,10 +201,10 @@ class MeasurementFragment extends StatefulComponent {
return new SnackBar(content: new Text(_errorMessage), showing: true);
}
Widget build() {
Widget build(BuildContext context) {
return new Scaffold(
toolbar: buildToolBar(),
body: buildBody(),
body: buildBody(context),
snackBar: buildSnackBar()
);
}
......
......@@ -10,38 +10,35 @@ typedef void SettingsUpdater({
});
class SettingsFragment extends StatefulComponent {
SettingsFragment({ this.navigator, this.userData, this.updater });
Navigator navigator;
UserData userData;
SettingsUpdater updater;
final NavigatorState navigator;
final UserData userData;
final SettingsUpdater updater;
void syncConstructorArguments(SettingsFragment source) {
navigator = source.navigator;
userData = source.userData;
updater = source.updater;
}
SettingsFragmentState createState() => new SettingsFragmentState();
}
class SettingsFragmentState extends State<SettingsFragment> {
void _handleBackupChanged(bool value) {
assert(updater != null);
updater(backup: value ? BackupMode.enabled : BackupMode.disabled);
assert(config.updater != null);
config.updater(backup: value ? BackupMode.enabled : BackupMode.disabled);
}
Widget buildToolBar() {
return new ToolBar(
left: new IconButton(
icon: "navigation/arrow_back",
onPressed: navigator.pop),
onPressed: config.navigator.pop
),
center: new Text('Settings')
);
}
String get goalWeightText {
if (userData.goalWeight == null || userData.goalWeight == 0.0)
if (config.userData.goalWeight == null || config.userData.goalWeight == 0.0)
return "None";
else
return "${userData.goalWeight}";
return "${config.userData.goalWeight}";
}
static final GlobalKey weightGoalKey = new GlobalKey();
......@@ -51,7 +48,7 @@ class SettingsFragment extends StatefulComponent {
void _handleGoalWeightChanged(String goalWeight) {
// TODO(jackson): Looking for null characters to detect enter key is a hack
if (goalWeight.endsWith("\u{0}")) {
navigator.pop(double.parse(goalWeight.replaceAll("\u{0}", "")));
config.navigator.pop(double.parse(goalWeight.replaceAll("\u{0}", "")));
} else {
setState(() {
try {
......@@ -64,7 +61,7 @@ class SettingsFragment extends StatefulComponent {
}
void _handleGoalWeightPressed() {
showDialog(navigator, (navigator) {
showDialog(config.navigator, (NavigatorState navigator) {
return new Dialog(
title: new Text("Goal Weight"),
content: new Input(
......@@ -91,10 +88,10 @@ class SettingsFragment extends StatefulComponent {
),
]
);
}).then((double goalWeight) => updater(goalWeight: goalWeight));
}).then((double goalWeight) => config.updater(goalWeight: goalWeight));
}
Widget buildSettingsPane() {
Widget buildSettingsPane(BuildContext context) {
return new Material(
type: MaterialType.canvas,
child: new ScrollableViewport(
......@@ -102,17 +99,17 @@ class SettingsFragment extends StatefulComponent {
padding: const EdgeDims.symmetric(vertical: 20.0),
child: new BlockBody([
new DrawerItem(
onPressed: () { _handleBackupChanged(!(userData.backupMode == BackupMode.enabled)); },
onPressed: () { _handleBackupChanged(!(config.userData.backupMode == BackupMode.enabled)); },
child: new Row([
new Flexible(child: new Text('Back up data to the cloud')),
new Switch(value: userData.backupMode == BackupMode.enabled, onChanged: _handleBackupChanged),
new Switch(value: config.userData.backupMode == BackupMode.enabled, onChanged: _handleBackupChanged),
])
),
new DrawerItem(
onPressed: () => _handleGoalWeightPressed(),
child: new Column([
new Text('Goal Weight'),
new Text(goalWeightText, style: Theme.of(this).text.caption),
new Text(goalWeightText, style: Theme.of(context).text.caption),
],
alignItems: FlexAlignItems.start
)
......@@ -123,10 +120,10 @@ class SettingsFragment extends StatefulComponent {
);
}
Widget build() {
Widget build(BuildContext context) {
return new Scaffold(
toolbar: buildToolBar(),
body: buildSettingsPane()
body: buildSettingsPane(context)
);
}
}
......@@ -16,7 +16,7 @@ import 'package:sky/src/fn3/scrollable.dart';
import 'package:sky/src/fn3/theme.dart';
import 'package:sky/src/fn3/transitions.dart';
typedef Dialog DialogBuilder(NavigatorState navigator);
typedef Widget DialogBuilder(NavigatorState navigator);
/// A material design dialog
///
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment