croupier: Become more Material

The implementation of the UI is now much more Material.
- Scaffold with Toolbar and Drawer
- Routes for Settings and Debug pages
- Styled text and buttons (though not all of them)

Fixed some minor bugs:
- Suit order in card collection should be Club/Diamond/Spade/Hearts
- Animating ZCards are now always drawn on top of static ZCards.

Added Debug Mode:
- This flag had to be threaded through the Croupier and Game logic.
  When enabled, it will allow you to see the Debug Button bar.

Missing
- some backgrounds aren't styled right yet.
- styles are a complete copy-paste of the Syncslides styles

Change-Id: I1dd5d2de2d2996c867f54ce4ab067c9245e71710
diff --git a/Makefile b/Makefile
index 7ae15e9..c8306b0 100644
--- a/Makefile
+++ b/Makefile
@@ -46,7 +46,7 @@
 	SYNCBASE_FLAGS += $(SYNCBASE_NAME_FLAG)
 endif
 
-# If this is not the first mojo shell, then you must reuse the devservers
+# If this is not the first mojo shell, then you must reuse the dev servers
 # to avoid a "port in use" error.
 ifneq ($(shell fuser 31841/tcp),)
 	REUSE_FLAG := --reuse-servers
@@ -130,6 +130,12 @@
 endif
 	$(call RUN_SKY_APP,$<)
 
+# Occasionally, mojo will leave some dev servers running on port 31841, which
+# prevents proper restarts. This rule cleans up the offending process.
+.PHONY: stop-mojo
+stop-mojo:
+	-fuser -k 31841/tcp
+
 CROUPIER_SHORTCUT_NAME := Croupier
 CROUPIER_URL := mojo://storage.googleapis.com/mojo_services/croupier/croupier.flx
 CROUPIER_URL_TO_ICON := https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png
diff --git a/lib/components/card.dart b/lib/components/card.dart
index e3dbd89..b2f5e71 100644
--- a/lib/components/card.dart
+++ b/lib/components/card.dart
@@ -57,7 +57,7 @@
         animateEntrance = dataComponent.animateEntrance,
         z = dataComponent.z;
 
-  _ZCardState createState() => new _ZCardState();
+  ZCardState createState() => new ZCardState();
 }
 
 class Card extends widgets.StatefulComponent {
@@ -101,9 +101,15 @@
   // Check if the data between these Cards matches.
   // This isn't == since I don't want to override that and hashCode.
   bool isMatchWith(Card c) {
-    return c.card == card && c.faceUp == faceUp && c.width == width &&
-      c.height == height && c.rotation == rotation && c.useKey == useKey &&
-      c.visible == visible && c.animateEntrance == animateEntrance && c.z == z;
+    return c.card == card &&
+        c.faceUp == faceUp &&
+        c.width == width &&
+        c.height == height &&
+        c.rotation == rotation &&
+        c.useKey == useKey &&
+        c.visible == visible &&
+        c.animateEntrance == animateEntrance &&
+        c.z == z;
   }
 
   CardState createState() => new CardState();
@@ -121,7 +127,8 @@
     widgets.Widget image = new widgets.Opacity(
         opacity: config.visible ? 1.0 : 0.0,
         child: new widgets.Transform(
-            child: _imageFromCard(config.card, config.faceUp, config.width, config.height),
+            child: _imageFromCard(
+                config.card, config.faceUp, config.width, config.height),
             transform:
                 new vector_math.Matrix4.identity().rotateZ(config.rotation),
             alignment: new FractionalOffset(0.5, 0.5)));
@@ -130,14 +137,15 @@
   }
 }
 
-widgets.Widget _imageFromCard(logic_card.Card c, bool faceUp, double width, double height) {
+widgets.Widget _imageFromCard(
+    logic_card.Card c, bool faceUp, double width, double height) {
   // TODO(alexfandrianto): Instead of 'default', what if we were told which theme to use?
   String imageName =
       "images/default/${c.deck}/${faceUp ? 'up' : 'down'}/${c.identifier}.png";
   return new widgets.AssetImage(name: imageName, width: width, height: height);
 }
 
-class _ZCardState extends widgets.State<ZCard> {
+class ZCardState extends widgets.State<ZCard> {
   ValuePerformance<Point> _performance;
   List<
       Point> _pointQueue; // at least 1 longer than the current animation index.
@@ -215,6 +223,11 @@
     return _animationIndex < _pointQueue.length - 1;
   }
 
+  // Return the current animation position of the ZCard.
+  Point get localPosition {
+    return _performance.variable.value;
+  }
+
   void _tryAnimate() {
     // Let animations finish... (Is this a good idea?)
     if (!_performance.isAnimating && _needsAnimation()) {
@@ -231,7 +244,8 @@
 
   widgets.Widget build(widgets.BuildContext context) {
     widgets.Widget image = new widgets.Transform(
-        child: _imageFromCard(config.card, config.faceUp, config.width, config.height),
+        child: _imageFromCard(
+            config.card, config.faceUp, config.width, config.height),
         transform: new vector_math.Matrix4.identity().rotateZ(config.rotation),
         alignment: new FractionalOffset(0.5, 0.5));
 
diff --git a/lib/components/card_collection.dart b/lib/components/card_collection.dart
index 2248fba..9ece2cd 100644
--- a/lib/components/card_collection.dart
+++ b/lib/components/card_collection.dart
@@ -192,7 +192,10 @@
                   opacity: 0.45,
                   child: emptyBackgroundImage == ""
                       ? null
-                      : new AssetImage(name: emptyBackgroundImage, fit: ImageFit.scaleDown, height: config.heightCard))));
+                      : new AssetImage(
+                          name: emptyBackgroundImage,
+                          fit: ImageFit.scaleDown,
+                          height: config.heightCard))));
     }
 
     double w = config.width ?? config.widthCard * 5;
@@ -269,19 +272,19 @@
             decoration: new BoxDecoration(backgroundColor: Colors.white),
             child: new Stack(<Widget>[
               new Positioned(
-                  top: 0.0,
+                  top: 2 * _produceRowHeight + 2 * _whiteLineHeight,
                   child: _produceRow(ss,
                       emptyBackgroundImage: "images/suits/Spade.png")),
               new Positioned(
-                  top: _produceRowHeight + _whiteLineHeight,
+                  top: 3 * _produceRowHeight + 3 * _whiteLineHeight,
                   child: _produceRow(hs,
                       emptyBackgroundImage: "images/suits/Heart.png")),
               new Positioned(
-                  top: 2 * _produceRowHeight + 2 * _whiteLineHeight,
+                  top: 0.0,
                   child: _produceRow(cs,
                       emptyBackgroundImage: "images/suits/Club.png")),
               new Positioned(
-                  top: 3 * _produceRowHeight + 3 * _whiteLineHeight,
+                  top: _produceRowHeight + _whiteLineHeight,
                   child: _produceRow(ds,
                       emptyBackgroundImage: "images/suits/Diamond.png"))
             ]));
diff --git a/lib/components/croupier.dart b/lib/components/croupier.dart
index 183a180..5ccdf6e 100644
--- a/lib/components/croupier.dart
+++ b/lib/components/croupier.dart
@@ -2,16 +2,16 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-import '../logic/croupier.dart' as logic_croupier;
-import '../logic/croupier_settings.dart' show CroupierSettings;
-import '../logic/game/game.dart' as logic_game;
-import 'game.dart' as component_game;
-import 'croupier_settings.dart' show CroupierSettingsComponent;
-import 'croupier_profile.dart' show CroupierProfileComponent;
+import 'dart:ui' as ui;
 
 import 'package:flutter/material.dart';
 
-import 'dart:ui' as ui;
+import '../logic/croupier.dart' as logic_croupier;
+import '../logic/croupier_settings.dart' show CroupierSettings;
+import '../logic/game/game.dart' as logic_game;
+import '../styles/common.dart' as style;
+import 'croupier_profile.dart' show CroupierProfileComponent;
+import 'game.dart' as component_game;
 
 typedef void NoArgCb();
 
@@ -24,8 +24,7 @@
 }
 
 class CroupierComponentState extends State<CroupierComponent> {
-  ui.Size screenSize;
-
+  @override
   void initState() {
     super.initState();
     // TODO(alexfandrianto): ui.window.size.width and ui.window.size.height?
@@ -45,18 +44,14 @@
         });
   }
 
-  void sizeChanged(ui.Size newSize) {
-    print(newSize);
-    setState(() {
-      screenSize = newSize;
-    });
-  }
-
   Widget build(BuildContext context) {
-    return new SizeObserver(onSizeChanged: sizeChanged, child: _buildHelper());
-  }
+    // TODO(alexfandrianto): A better way to do this is to show the splash
+    // screen while the Store is initializing.
+    // https://github.com/vanadium/issues/issues/958
+    if (config.croupier.settings == null) {
+      return _buildSplashScreen();
+    }
 
-  Widget _buildHelper() {
     switch (config.croupier.state) {
       case logic_croupier.CroupierState.Welcome:
         // in which we show them a UI to start a new game, join a game, or change some settings.
@@ -64,49 +59,38 @@
             padding: new EdgeDims.only(top: ui.window.padding.top),
             child: new Column([
               new FlatButton(
-                  child: new Text('Create Game'),
+                  child: new Text('Create Game', style: style.Text.titleStyle),
                   onPressed: makeSetStateCallback(
                       logic_croupier.CroupierState.ChooseGame)),
               new FlatButton(
-                  child: new Text('Join Game'),
+                  child: new Text('Join Game', style: style.Text.titleStyle),
                   onPressed: makeSetStateCallback(
-                      logic_croupier.CroupierState.JoinGame)),
-              new FlatButton(
-                  child: new Text('Settings'),
-                  onPressed: makeSetStateCallback(
-                      logic_croupier.CroupierState.Settings))
+                      logic_croupier.CroupierState.JoinGame))
             ]));
-      case logic_croupier.CroupierState.Settings:
-        // in which we let them pick an avatar, name, and color. And return to the previous screen after.
-        return new Container(
-            padding: new EdgeDims.only(top: ui.window.padding.top),
-            child: new CroupierSettingsComponent(
-                config.croupier.settings,
-                config.croupier.settings_manager.save,
-                makeSetStateCallback(logic_croupier.CroupierState.Welcome)));
       case logic_croupier.CroupierState.ChooseGame:
         // in which we let them pick a game out of the many possible games... There aren't that many.
         return new Container(
             padding: new EdgeDims.only(top: ui.window.padding.top),
             child: new Flex([
               new FlatButton(
-                  child: new Text('Proto'),
+                  child: new Text('Proto', style: style.Text.titleStyle),
                   onPressed: makeSetStateCallback(
                       logic_croupier.CroupierState.ArrangePlayers,
                       logic_game.GameType.Proto)),
               new FlatButton(
-                  child: new Text('Hearts'),
+                  child: new Text('Hearts', style: style.Text.titleStyle),
                   onPressed: makeSetStateCallback(
                       logic_croupier.CroupierState.ArrangePlayers,
                       logic_game.GameType.Hearts)),
-              new FlatButton(child: new Text('Poker')),
               new FlatButton(
-                  child: new Text('Solitaire'),
+                  child: new Text('Poker', style: style.Text.titleStyle)),
+              new FlatButton(
+                  child: new Text('Solitaire', style: style.Text.titleStyle),
                   onPressed: makeSetStateCallback(
                       logic_croupier.CroupierState.ArrangePlayers,
                       logic_game.GameType.Solitaire)),
               new FlatButton(
-                  child: new Text('Back'),
+                  child: new Text('Back', style: style.Text.subtitleStyle),
                   onPressed: makeSetStateCallback(
                       logic_croupier.CroupierState.Welcome))
             ], direction: FlexDirection.vertical));
@@ -128,10 +112,13 @@
         return new Container(
             padding: new EdgeDims.only(top: ui.window.padding.top),
             child: new Column([
-              new Text("Can join these games..."),
+              profileWidgets.length == 0
+                  ? new Text("Looking for Games...",
+                      style: style.Text.titleStyle)
+                  : new Text("Available Games", style: style.Text.titleStyle),
               new Grid(profileWidgets, maxChildExtent: 150.0),
               new FlatButton(
-                  child: new Text('Back'),
+                  child: new Text('Back', style: style.Text.subtitleStyle),
                   onPressed: makeSetStateCallback(
                       logic_croupier.CroupierState.Welcome))
             ]));
@@ -166,11 +153,17 @@
               config.croupier.game.quit();
               makeSetStateCallback(logic_croupier.CroupierState.Welcome)();
             },
-                width: screenSize.width,
-                height: screenSize.height - ui.window.padding.top));
+                width: ui.window.size.width,
+                height: ui.window.size.height - ui.window.padding.top));
       default:
         assert(false);
         return null;
     }
   }
+
+  // TODO(alexfandrianto): Can we do better than this?
+  Widget _buildSplashScreen() {
+    return new Container(
+        child: new Text("Loading Croupier...", style: style.Text.titleStyle));
+  }
 }
diff --git a/lib/components/croupier_profile.dart b/lib/components/croupier_profile.dart
index d8fe840..0a9390a 100644
--- a/lib/components/croupier_profile.dart
+++ b/lib/components/croupier_profile.dart
@@ -2,10 +2,11 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-import '../logic/croupier_settings.dart' show CroupierSettings;
-
 import 'package:flutter/material.dart';
 
+import '../logic/croupier_settings.dart' show CroupierSettings;
+import '../styles/common.dart' as style;
+
 class CroupierProfileComponent extends StatelessComponent {
   final CroupierSettings settings;
   CroupierProfileComponent(this.settings);
@@ -14,7 +15,9 @@
     return new Container(
         decoration:
             new BoxDecoration(backgroundColor: new Color(settings.color)),
-        child: new Column(
-            [new AssetImage(name: CroupierSettings.makeAvatarUrl(settings.avatar)), new Text(settings.name)]));
+        child: new Column([
+          new AssetImage(name: CroupierSettings.makeAvatarUrl(settings.avatar)),
+          new Text(settings.name, style: style.Text.liveNow)
+        ]));
   }
 }
diff --git a/lib/components/croupier_settings.dart b/lib/components/croupier_settings.dart
index fe6f194..fc218c5 100644
--- a/lib/components/croupier_settings.dart
+++ b/lib/components/croupier_settings.dart
@@ -31,9 +31,8 @@
 class CroupierSettingsComponent extends StatefulComponent {
   final CroupierSettings settings;
   final SaveDataCb saveDataCb;
-  final NoArgCb backCb;
 
-  CroupierSettingsComponent(this.settings, this.saveDataCb, this.backCb);
+  CroupierSettingsComponent(this.settings, this.saveDataCb);
 
   CroupierSettingsComponentState createState() =>
       new CroupierSettingsComponentState();
@@ -61,18 +60,34 @@
   }
 
   Widget _makeImageButton(String url, NoArgCb cb) {
-    return new FlatButton(child: new AssetImage(name: CroupierSettings.makeAvatarUrl(url)), onPressed: cb);
+    return new FlatButton(
+        child: new AssetImage(name: CroupierSettings.makeAvatarUrl(url)),
+        onPressed: cb);
   }
 
   Widget build(BuildContext context) {
+    return new Scaffold(
+        toolBar: _buildToolBar(), body: _buildSettingsPane(context));
+  }
+
+  Widget _buildToolBar() {
+    return new ToolBar(
+        left: new IconButton(
+            icon: "navigation/arrow_back",
+            onPressed: () => Navigator.of(context).pop()),
+        center: new Text("Settings"));
+  }
+
+  Widget _buildSettingsPane(BuildContext context) {
     List<Widget> w = new List<Widget>();
     w.add(_makeButtonRow(nameKey, new Text(config.settings.name)));
     w.add(_makeButtonRow(
         colorKey, _makeColoredRectangle(config.settings.color, "", null)));
     w.add(_makeButtonRow(
-        avatarKey, new AssetImage(name: CroupierSettings.makeAvatarUrl(config.settings.avatar))));
+        avatarKey,
+        new AssetImage(
+            name: CroupierSettings.makeAvatarUrl(config.settings.avatar))));
 
-    w.add(new FlatButton(child: new Text("Return"), onPressed: config.backCb));
     return new Column(w);
   }
 
diff --git a/lib/components/debug_route.dart b/lib/components/debug_route.dart
new file mode 100644
index 0000000..a0551ed
--- /dev/null
+++ b/lib/components/debug_route.dart
@@ -0,0 +1,53 @@
+// Copyright 2015 The Vanadium Authors. 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/material.dart';
+
+import '../logic/croupier.dart' show Croupier;
+import '../styles/common.dart' as style;
+
+// TODO(alexfandrianto): Remove this file once Flutter alpha branch is updated
+// and this route's Switch can be placed within the DrawerItem.
+// https://github.com/vanadium/issues/issues/957
+class DebugRoute extends StatefulComponent {
+  final Croupier croupier;
+
+  DebugRoute(this.croupier);
+
+  DebugRouteState createState() => new DebugRouteState();
+}
+
+class DebugRouteState extends State<DebugRoute> {
+  Widget build(BuildContext context) {
+    return new Scaffold(
+        toolBar: _buildToolBar(), body: _buildDebugPane(context));
+  }
+
+  Widget _buildToolBar() {
+    return new ToolBar(
+        left: new IconButton(
+            icon: "navigation/arrow_back",
+            onPressed: () => Navigator.of(context).pop()),
+        center: new Text("Debug Settings"));
+  }
+
+  Widget _buildDebugPane(BuildContext context) {
+    return new Row([
+      new Text('Debug Mode', style: style.Text.titleStyle),
+      new Switch(value: config.croupier.debugMode, onChanged: _handleDebugMode),
+    ],
+        justifyContent: FlexJustifyContent.spaceAround,
+        alignItems: FlexAlignItems.start);
+  }
+
+  void _handleDebugMode(bool value) {
+    print("new value is ${value}. Old is ${config.croupier.debugMode}");
+    setState(() {
+      config.croupier.debugMode = value;
+      if (config.croupier.game != null) {
+        config.croupier.game.debugMode = value;
+      }
+    });
+  }
+}
diff --git a/lib/components/game.dart b/lib/components/game.dart
index 9bb71b0..66d13da 100644
--- a/lib/components/game.dart
+++ b/lib/components/game.dart
@@ -12,6 +12,7 @@
 import 'card.dart' as component_card;
 import 'card_collection.dart'
     show CardCollectionComponent, DropType, CardCollectionOrientation, AcceptCb;
+import '../styles/common.dart' as style;
 
 import 'package:flutter/animation.dart';
 import 'package:flutter/material.dart';
@@ -61,7 +62,8 @@
 
   // A helper that subclasses might override to create buttons.
   Widget _makeButton(String text, NoArgCb callback) {
-    return new FlatButton(child: new Text(text), onPressed: callback);
+    return new FlatButton(
+        child: new Text(text, style: style.Text.liveNow), onPressed: callback);
   }
 
   @override
@@ -106,6 +108,25 @@
     }
   }
 
+  bool _isMoving(logic_card.Card c) {
+    CardAnimationData data = cardLevelMap[c];
+    RenderBox box = context.findRenderObject();
+    Point localOld =
+        data.oldPoint != null ? box.globalToLocal(data.oldPoint) : null;
+    Point localNew = box.globalToLocal(data.newPoint);
+
+    // We also need confirmation from the ZCard that we are moving.
+    component_card.GlobalCardKey zCardKey =
+        new component_card.GlobalCardKey(c, component_card.CardUIType.ZCARD);
+    component_card.ZCardState zCardKeyState = zCardKey.currentState;
+
+    // It is moving if there is an old position, the new one isn't equal to the
+    // old one, and the ZCard hasn't arrived at the new position yet.
+    return localOld != null &&
+        localOld != localNew &&
+        localNew != zCardKeyState?.localPosition;
+  }
+
   // Helper to build the card animation layer.
   // Note: This isn't a component because of its dependence on Widgets.
   Widget buildCardAnimationLayer(List<int> visibleCardCollections) {
@@ -117,9 +138,20 @@
 
     List<Widget> positionedCards = new List<Widget>();
 
-    // Sort the cards by z-index.
+    // Sort the cards by z-index. If a card is animating, it gets a z bonus.
     List<logic_card.Card> orderedKeys = cardLevelMap.keys.toList()
       ..sort((logic_card.Card a, logic_card.Card b) {
+        bool isMovingA = _isMoving(a);
+        bool isMovingB = _isMoving(b);
+
+        // Moving cards take much higher priority.
+        if (isMovingA && !isMovingB) {
+          return 1;
+        } else if (!isMovingA && isMovingB) {
+          return -1;
+        }
+
+        // If both are moving/non-moving, we break ties with the z-index.
         double diff = cardLevelMap[a].z - cardLevelMap[b].z;
         return diff.sign.toInt();
       });
diff --git a/lib/components/hearts/hearts.part.dart b/lib/components/hearts/hearts.part.dart
index 9fa4991..a310ee5 100644
--- a/lib/components/hearts/hearts.part.dart
+++ b/lib/components/hearts/hearts.part.dart
@@ -207,19 +207,25 @@
     });
   }
 
-  Widget _makeDebugButtons() => new Container(
-      width: config.width,
-      child: new Flex([
-        new Flexible(flex: 1, child: new Text('P${config.game.playerNumber}')),
-        new Flexible(
-            flex: 5,
-            child: _makeButton('Switch Player', _switchPlayersCallback)),
-        new Flexible(
-            flex: 5, child: _makeButton('Switch View', _switchViewCallback)),
-        new Flexible(
-            flex: 5, child: _makeButton('End Round', _endRoundDebugCallback)),
-        new Flexible(flex: 4, child: _makeButton('Quit', _quitGameCallback))
-      ]));
+  Widget _makeDebugButtons() {
+    if (config.game.debugMode == false) {
+      return new Flex([]);
+    }
+    return new Container(
+        width: config.width,
+        child: new Flex([
+          new Flexible(
+              flex: 1, child: new Text('P${config.game.playerNumber}')),
+          new Flexible(
+              flex: 5,
+              child: _makeButton('Switch Player', _switchPlayersCallback)),
+          new Flexible(
+              flex: 5, child: _makeButton('Switch View', _switchViewCallback)),
+          new Flexible(
+              flex: 5, child: _makeButton('End Round', _endRoundDebugCallback)),
+          new Flexible(flex: 4, child: _makeButton('Quit', _quitGameCallback))
+        ]));
+  }
 
   @override
   Widget _makeButton(String text, NoArgCb callback, {bool inactive: false}) {
diff --git a/lib/components/main_route.dart b/lib/components/main_route.dart
new file mode 100644
index 0000000..5a0a715
--- /dev/null
+++ b/lib/components/main_route.dart
@@ -0,0 +1,69 @@
+// Copyright 2015 The Vanadium Authors. 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/material.dart';
+
+import '../logic/croupier.dart' show Croupier;
+import '../styles/common.dart' as style;
+import 'croupier.dart' show CroupierComponent;
+
+final GlobalKey _scaffoldKey = new GlobalKey();
+
+class MainRoute extends StatefulComponent {
+  final Croupier croupier;
+
+  MainRoute(this.croupier);
+
+  MainRouteState createState() => new MainRouteState();
+}
+
+class MainRouteState extends State<MainRoute> {
+  Widget build(BuildContext context) {
+    return new Scaffold(
+        key: _scaffoldKey,
+        toolBar: new ToolBar(
+            left:
+                new IconButton(icon: "navigation/menu", onPressed: _showDrawer),
+            center: new Text('Croupier')),
+        body: new Material(child: new CroupierComponent(config.croupier)));
+  }
+
+  void _showDrawer() {
+    showDrawer(
+        context: context,
+        child: new Block(<Widget>[
+          new DrawerHeader(
+              child: new Text('Croupier', style: style.Text.titleStyle)),
+          new DrawerItem(
+              icon: 'action/settings',
+              // TODO(alexfandrianto): Fix the Splash Screen, and we won't need
+              // to check if settings is null here.
+              // https://github.com/vanadium/issues/issues/958
+              onPressed:
+                  config.croupier.settings != null ? _handleShowSettings : null,
+              child: new Text('Settings')),
+          // TODO(alexfandrianto): Once Flutter alpha branch is updated, this
+          // DrawerItem can have a Switch inside instead of DebugRoute.
+          // https://github.com/vanadium/issues/issues/957
+          new DrawerItem(
+              icon: 'action/build',
+              onPressed: _handleShowDebug,
+              child: new Text('Debug Mode')),
+          new DrawerItem(
+              icon: 'action/help', child: new Text('Help & Feedback'))
+        ]));
+  }
+
+  void _handleShowSettings() {
+    Navigator.of(context)
+      ..pop()
+      ..pushNamed('/settings');
+  }
+
+  void _handleShowDebug() {
+    Navigator.of(context)
+      ..pop()
+      ..pushNamed('/debug');
+  }
+}
diff --git a/lib/components/proto/proto.part.dart b/lib/components/proto/proto.part.dart
index d6b9873..4a11900 100644
--- a/lib/components/proto/proto.part.dart
+++ b/lib/components/proto/proto.part.dart
@@ -57,11 +57,16 @@
     });
   }
 
-  Widget _makeDebugButtons() => new Flex([
-        new Text('P${config.game.playerNumber}'),
-        _makeButton('Switch View', _switchPlayersCallback),
-        _makeButton('Quit', _quitGameCallback)
-      ]);
+  Widget _makeDebugButtons() {
+    if (config.game.debugMode == false) {
+      return new Flex([]);
+    }
+    return new Flex([
+      new Text('P${config.game.playerNumber}'),
+      _makeButton('Switch View', _switchPlayersCallback),
+      _makeButton('Quit', _quitGameCallback)
+    ]);
+  }
 
   void _switchPlayersCallback() {
     setState(() {
diff --git a/lib/components/settings_route.dart b/lib/components/settings_route.dart
new file mode 100644
index 0000000..ab133bd
--- /dev/null
+++ b/lib/components/settings_route.dart
@@ -0,0 +1,19 @@
+// Copyright 2015 The Vanadium Authors. 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/material.dart';
+
+import '../logic/croupier.dart' show Croupier;
+import 'croupier_settings.dart' show CroupierSettingsComponent;
+
+class SettingsRoute extends StatelessComponent {
+  final Croupier croupier;
+
+  SettingsRoute(this.croupier);
+
+  Widget build(BuildContext context) {
+    return new CroupierSettingsComponent(
+        croupier.settings, croupier.settings_manager.save);
+  }
+}
diff --git a/lib/components/solitaire/solitaire.part.dart b/lib/components/solitaire/solitaire.part.dart
index 44661ec..fdcec8f 100644
--- a/lib/components/solitaire/solitaire.part.dart
+++ b/lib/components/solitaire/solitaire.part.dart
@@ -18,8 +18,6 @@
   Widget build(BuildContext context) {
     SolitaireGame game = config.game as SolitaireGame;
 
-    print("Building Solitaire!");
-
     // Build Solitaire and have it fill up the card level map.
     // Unfortunately, this is required so that we can know which card components
     // to collect.
@@ -57,15 +55,21 @@
     });
   }
 
-  Widget _makeDebugButtons() => new Container(
-      width: config.width,
-      child: new Flex([
-        new Flexible(flex: 1, child: new Text('P${config.game.playerNumber}')),
-        new Flexible(flex: 5, child: _makeButton('Cheat', _cheatCallback)),
-        new Flexible(
-            flex: 5, child: _makeButton('End Round', _endRoundDebugCallback)),
-        new Flexible(flex: 4, child: _makeButton('Quit', _quitGameCallback))
-      ]));
+  Widget _makeDebugButtons() {
+    if (config.game.debugMode == false) {
+      return new Flex([]);
+    }
+    return new Container(
+        width: config.width,
+        child: new Flex([
+          new Flexible(
+              flex: 1, child: new Text('P${config.game.playerNumber}')),
+          new Flexible(flex: 5, child: _makeButton('Cheat', _cheatCallback)),
+          new Flexible(
+              flex: 5, child: _makeButton('End Round', _endRoundDebugCallback)),
+          new Flexible(flex: 4, child: _makeButton('Quit', _quitGameCallback))
+        ]));
+  }
 
   @override
   Widget _makeButton(String text, NoArgCb callback, {bool inactive: false}) {
diff --git a/lib/logic/create_game.dart b/lib/logic/create_game.dart
index 94e7caa..ae08b66 100644
--- a/lib/logic/create_game.dart
+++ b/lib/logic/create_game.dart
@@ -7,17 +7,18 @@
 import 'proto/proto.dart' as proto_impl;
 import 'solitaire/solitaire.dart' as solitaire_impl;
 
-game_impl.Game createGame(game_impl.GameType gt, int pn,
+game_impl.Game createGame(game_impl.GameType gt, int pn, bool debugMode,
     {int gameID, bool isCreator}) {
   switch (gt) {
     case game_impl.GameType.Proto:
-      return new proto_impl.ProtoGame(pn, gameID: gameID, isCreator: isCreator);
+      return new proto_impl.ProtoGame(pn, gameID: gameID, isCreator: isCreator)
+        ..debugMode = debugMode;
     case game_impl.GameType.Hearts:
       return new hearts_impl.HeartsGame(pn,
-          gameID: gameID, isCreator: isCreator);
+          gameID: gameID, isCreator: isCreator)..debugMode = debugMode;
     case game_impl.GameType.Solitaire:
       return new solitaire_impl.SolitaireGame(pn,
-          gameID: gameID, isCreator: isCreator);
+          gameID: gameID, isCreator: isCreator)..debugMode = debugMode;
     default:
       assert(false);
       return null;
diff --git a/lib/logic/croupier.dart b/lib/logic/croupier.dart
index 559b367..ee119ac 100644
--- a/lib/logic/croupier.dart
+++ b/lib/logic/croupier.dart
@@ -10,14 +10,7 @@
 import 'game/game.dart'
     show Game, GameType, GameStartData, stringToGameType, gameTypeToString;
 
-enum CroupierState {
-  Welcome,
-  Settings,
-  ChooseGame,
-  JoinGame,
-  ArrangePlayers,
-  PlayGame
-}
+enum CroupierState { Welcome, ChooseGame, JoinGame, ArrangePlayers, PlayGame }
 
 typedef void NoArgCb();
 
@@ -36,6 +29,8 @@
   Future _scanFuture;
   Future _advertiseFuture;
 
+  bool debugMode = false; // whether to show debug buttons or not
+
   Croupier() {
     state = CroupierState.Welcome;
     settings_everyone = new Map<int, CroupierSettings>();
@@ -46,6 +41,9 @@
 
     settings_manager.load().then((String csString) {
       settings = new CroupierSettings.fromJSONString(csString);
+      if (this.informUICb != null) {
+        this.informUICb();
+      }
       settings_manager.createSettingsSyncgroup(); // don't wait for this future.
     });
   }
@@ -98,11 +96,6 @@
         // data should be empty.
         assert(data == null);
         break;
-      case CroupierState.Settings:
-        // data should be empty.
-        // All settings changes affect the croupier settings directly without changing app state.
-        assert(data == null);
-        break;
       case CroupierState.ChooseGame:
         if (data == null) {
           // Back button pressed.
@@ -112,7 +105,7 @@
 
         // data should be the game id here.
         GameType gt = data as GameType;
-        game = cg.createGame(gt, 0,
+        game = cg.createGame(gt, 0, this.debugMode,
             isCreator: true); // Start as player 0 of whatever game type.
 
         _advertiseFuture = settings_manager
@@ -137,7 +130,8 @@
 
         // data would probably be the game id again.
         GameStartData gsd = data as GameStartData;
-        game = cg.createGame(stringToGameType(gsd.type), gsd.playerNumber,
+        game = cg.createGame(
+            stringToGameType(gsd.type), gsd.playerNumber, this.debugMode,
             gameID: gsd.gameID); // Start as player 0 of whatever game type.
         String sgName;
         games_found.forEach((String name, GameStartData g) {
@@ -169,13 +163,6 @@
         assert(false);
     }
 
-    // TODO(alexfandrianto): We may want to have a splash screen or something
-    // when the user first loads the app. It takes a few seconds before the
-    // Syncbase tables are created.
-    if (settings == null) {
-      return; // you can't switch till the settings are present.
-    }
-
     // A simplified way of clearing out the games and players found.
     // They will need to be re-discovered in the future.
     if (nextState == CroupierState.Welcome) {
diff --git a/lib/logic/game/game_def.part.dart b/lib/logic/game/game_def.part.dart
index f969623..70315df 100644
--- a/lib/logic/game/game_def.part.dart
+++ b/lib/logic/game/game_def.part.dart
@@ -91,6 +91,7 @@
     _playerNumber = other;
   }
 
+  bool debugMode = false;
   String debugString = 'hello?';
 
   NoArgCb updateCallback; // Used to inform components of when a change has occurred. This is especially important when something non-UI related changes what should be drawn.
diff --git a/lib/main.dart b/lib/main.dart
index 141b3a5..1f45727 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -3,10 +3,12 @@
 // license that can be found in the LICENSE file.
 
 import 'package:flutter/material.dart';
-import 'package:flutter/material.dart' show Colors;
 
 import 'logic/croupier.dart' show Croupier;
-import 'components/croupier.dart' show CroupierComponent;
+import 'components/settings_route.dart' show SettingsRoute;
+import 'components/debug_route.dart' show DebugRoute;
+import 'components/main_route.dart' show MainRoute;
+import 'styles/common.dart' as style;
 
 class CroupierApp extends StatefulComponent {
   CroupierApp();
@@ -23,21 +25,17 @@
   }
 
   Widget build(BuildContext context) {
-    return new Container(
-        decoration: new BoxDecoration(
-            backgroundColor: const Color(0xFF6666FF), borderRadius: 5.0),
-        child: new DefaultTextStyle(
-            style: Theme.of(context).text.body1,
-            child: new CroupierComponent(this.croupier)));
+    return new MaterialApp(
+        title: 'Croupier',
+        routes: <String, RouteBuilder>{
+          "/": (RouteArguments args) => new MainRoute(croupier),
+          "/settings": (RouteArguments args) => new SettingsRoute(croupier),
+          "/debug": (RouteArguments args) => new DebugRoute(croupier)
+        },
+        theme: style.theme);
   }
 }
 
 void main() {
-  runApp(new MaterialApp(
-      title: 'Croupier',
-      routes: <String, RouteBuilder>{
-        "/": (RouteArguments args) => new CroupierApp()
-      },
-      theme: new ThemeData(
-          brightness: ThemeBrightness.light, primarySwatch: Colors.purple)));
+  runApp(new CroupierApp());
 }
diff --git a/lib/styles/common.dart b/lib/styles/common.dart
new file mode 100644
index 0000000..ccbef24
--- /dev/null
+++ b/lib/styles/common.dart
@@ -0,0 +1,40 @@
+// Copyright 2015 The Vanadium Authors. 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/material.dart';
+
+class Text {
+  static final Color secondaryTextColor = Colors.grey[500];
+  static final Color errorTextColor = Colors.red[500];
+  static final TextStyle titleStyle = new TextStyle(fontSize: 18.0);
+  static final TextStyle subtitleStyle =
+      new TextStyle(fontSize: 12.0, color: secondaryTextColor);
+  static final TextStyle liveNow =
+      new TextStyle(fontSize: 12.0, color: theme.accentColor);
+  static final TextStyle error = new TextStyle(color: errorTextColor);
+}
+
+class Size {
+  static const double thumbnailWidth = 250.0;
+  static const double listHeight = 150.0;
+  static const double thumbnailNavHeight = 150.0;
+  static const double thumbnailNavWidth = 267.0;
+}
+
+class Spacing {
+  static final EdgeDims extraSmallPadding = new EdgeDims.all(2.0);
+  static final EdgeDims smallPadding = new EdgeDims.all(5.0);
+  static final EdgeDims normalPadding = new EdgeDims.all(10.0);
+  static final EdgeDims normalMargin = new EdgeDims.all(2.0);
+  static final EdgeDims listItemMargin = new EdgeDims.TRBL(3.0, 6.0, 0.0, 6.0);
+  static final EdgeDims thumbnailNavMargin = new EdgeDims.all(3.0);
+}
+
+class Box {
+  static final BoxDecoration liveNow = new BoxDecoration(
+      border: new Border.all(color: theme.accentColor), borderRadius: 2.0);
+}
+
+ThemeData theme = new ThemeData(
+    primarySwatch: Colors.blueGrey, accentColor: Colors.orangeAccent[700]);
diff --git a/manifest.yaml b/manifest.yaml
index 49f0273..3bac6e9 100644
--- a/manifest.yaml
+++ b/manifest.yaml
@@ -1,3 +1,10 @@
+name: croupier
+material-design-icons:
+  - name: action/build
+  - name: action/help
+  - name: action/settings
+  - name: navigation/arrow_back
+  - name: navigation/menu
 assets:
   - images/default/classic/down/c10.png
   - images/default/classic/down/c1.png
@@ -217,4 +224,4 @@
   - images/avatars/Club.png
   - images/avatars/Diamond.png
   - images/avatars/Heart.png
-  - images/avatars/Spade.png
\ No newline at end of file
+  - images/avatars/Spade.png