Commit d0bac85d authored by Ian Hickson's avatar Ian Hickson

Move the new calculator demo into the gallery. (#4092)

parent cd19af8f
.DS_Store
.atom/
.idea
.packages
.pub/
build/
ios/.generated/
packages
pubspec.lock
# calculator
A Material Design Calculator written in Flutter.
## Getting Started
For help getting started with Flutter, view our online
[documentation](http://flutter.io/).
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.flutter.examples.calculator"
android:versionCode="1"
android:versionName="0.0.1">
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="21" />
<uses-permission android:name="android.permission.INTERNET"/>
<application android:name="org.domokit.sky.shell.SkyApplication"
android:label="Flutter Calculator" android:icon="@mipmap/ic_launcher">
<activity android:name="org.domokit.sky.shell.SkyActivity"
android:launchMode="singleTask"
android:theme="@android:style/Theme.Black.NoTitleBar"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
name: calculator
uses-material-design: true
{
"images" : [
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-Small@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-Small@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-Small-40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-Small-40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-60@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-Small.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-Small@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-Small-40.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-Small-40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-76.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-83.5@2x.png",
"scale" : "2x"
},
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "icon_16x16.png",
"scale" : "1x"
},
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "icon_16x16@2x.png",
"scale" : "2x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "icon_32x32.png",
"scale" : "1x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "icon_32x32@2x.png",
"scale" : "2x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "icon_128x128.png",
"scale" : "1x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "icon_128x128@2x.png",
"scale" : "2x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "icon_256x256.png",
"scale" : "1x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "icon_256x256@2x.png",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "icon_512x512.png",
"scale" : "1x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "icon_512x512@2x.png",
"scale" : "2x"
}
]
}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>Runner</string>
<key>CFBundleIdentifier</key>
<string>com.yourcompany.calculator</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>calculator</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9531" systemVersion="15C50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>
name: calculator
description: A new flutter project.
dependencies:
flutter:
path: ../../packages/flutter
dev_dependencies:
test: any # flutter_test provides the version constraints
flutter_test:
path: ../../packages/flutter_test
flutter_driver:
path: ../../packages/flutter_driver
......@@ -4,38 +4,32 @@
import 'package:flutter/material.dart';
import 'calc_expression.dart';
// A calculator application.
void main() {
runApp(new MaterialApp(
title: 'Calculator',
theme: new ThemeData(primarySwatch: Colors.blue),
home: new Calculator()));
}
import 'logic.dart';
class Calculator extends StatefulWidget {
Calculator({Key key}) : super(key: key);
static const String routeName = '/calculator';
@override
_CalculatorState createState() => new _CalculatorState();
}
class _CalculatorState extends State<Calculator> {
// As the user taps keys we update the current |_expression| and we also
// keep a stack of previous expressions so we can return to earlier states
// when the user hits the DEL key.
/// As the user taps keys we update the current `_expression` and we also
/// keep a stack of previous expressions so we can return to earlier states
/// when the user hits the DEL key.
final List<CalcExpression> _expressionStack = <CalcExpression>[];
CalcExpression _expression = new CalcExpression.Empty();
// Make |expression| the current expression and push the previous current
// Make `expression` the current expression and push the previous current
// expression onto the stack.
void pushExpression(CalcExpression expression) {
_expressionStack.add(_expression);
_expression = expression;
}
// Pop the top expression off of the stack and make it the current expression.
/// Pop the top expression off of the stack and make it the current expression.
void popCalcExpression() {
if (_expressionStack.length > 0) {
_expression = _expressionStack.removeLast();
......@@ -44,7 +38,7 @@ class _CalculatorState extends State<Calculator> {
}
}
// Set |resultExpression| to the currrent expression and clear the stack.
/// Set `resultExpression` to the currrent expression and clear the stack.
void setResult(CalcExpression resultExpression) {
_expressionStack.clear();
_expression = resultExpression;
......@@ -69,8 +63,7 @@ class _CalculatorState extends State<Calculator> {
}
void handlePlusTap() {
final CalcExpression expression =
_expression.appendOperation(Operation.Addition);
final CalcExpression expression = _expression.appendOperation(Operation.Addition);
if (expression != null) {
setState(() {
pushExpression(expression);
......@@ -88,8 +81,7 @@ class _CalculatorState extends State<Calculator> {
}
void handleMultTap() {
final CalcExpression expression =
_expression.appendOperation(Operation.Multiplication);
final CalcExpression expression = _expression.appendOperation(Operation.Multiplication);
if (expression != null) {
setState(() {
pushExpression(expression);
......@@ -98,8 +90,7 @@ class _CalculatorState extends State<Calculator> {
}
void handleDivTap() {
final CalcExpression expression =
_expression.appendOperation(Operation.Division);
final CalcExpression expression = _expression.appendOperation(Operation.Division);
if (expression != null) {
setState(() {
pushExpression(expression);
......@@ -126,12 +117,14 @@ class _CalculatorState extends State<Calculator> {
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text('Calculator')),
body: new Column(children: <Widget>[
// Give the key-pad 3/5 of the vertical space and the display
// 2/5.
body: new Column(
children: <Widget>[
// Give the key-pad 3/5 of the vertical space and the display 2/5.
new CalcDisplay(2, _expression.toString()),
new KeyPad(3, calcState: this)
]));
]
)
);
}
}
......@@ -145,8 +138,13 @@ class CalcDisplay extends StatelessWidget {
Widget build(BuildContext context) {
return new Flexible(
flex: _flex,
child: new Center(child: new Text(_contents,
style: const TextStyle(color: Colors.black, fontSize: 24.0))));
child: new Center(
child: new Text(
_contents,
style: const TextStyle(color: Colors.black, fontSize: 24.0)
)
)
);
}
}
......@@ -160,10 +158,13 @@ class KeyPad extends StatelessWidget {
Widget build(BuildContext context) {
return new Flexible(
flex: _flex,
child: new Row(children: <Widget>[
child: new Row(
children: <Widget>[
new MainKeyPad(calcState: calcState),
new OpKeyPad(calcState: calcState),
]));
]
)
);
}
}
......@@ -180,10 +181,10 @@ class MainKeyPad extends StatelessWidget {
// columns.
flex: 3,
child: new Material(
type: MaterialType.canvas,
elevation: 12,
color: Colors.grey[800],
child: new Column(children: <Widget>[
child: new Column(
children: <Widget>[
new KeyRow(<Widget>[
new NumberKey(7, calcState),
new NumberKey(8, calcState),
......@@ -204,7 +205,10 @@ class MainKeyPad extends StatelessWidget {
new NumberKey(0, calcState),
new CalcKey('=', calcState.handleEqualsTap),
])
])));
]
)
)
);
}
}
......@@ -215,17 +219,21 @@ class OpKeyPad extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Flexible(child: new Material(
type: MaterialType.canvas,
return new Flexible(
child: new Material(
elevation: 24,
color: Colors.grey[700],
child: new Column(children: <Widget>[
new CalcKey('DEL', calcState.handleDelTap),
child: new Column(
children: <Widget>[
new CalcKey('\u232B', calcState.handleDelTap),
new CalcKey('\u00F7', calcState.handleDivTap),
new CalcKey('\u00D7', calcState.handleMultTap),
new CalcKey('-', calcState.handleMinusTap),
new CalcKey('+', calcState.handlePlusTap)
])));
]
)
)
);
}
}
......@@ -236,22 +244,35 @@ class KeyRow extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Flexible(child: new Row(
mainAxisAlignment: MainAxisAlignment.center, children: this.keys));
return new Flexible(
child: new Row(
mainAxisAlignment: MainAxisAlignment.center, children: this.keys
)
);
}
}
class CalcKey extends StatelessWidget {
CalcKey(this.text, this.onTap);
final String text;
final GestureTapCallback onTap;
CalcKey(this.text, this.onTap);
@override
Widget build(BuildContext context) {
return new Flexible(child: new Container(child: new InkResponse(
return new Flexible(
// child: new Container(
child: new InkResponse(
onTap: this.onTap,
child: new Center(child: new Text(this.text,
style: const TextStyle(color: Colors.white, fontSize: 32.0))))));
child: new Center(
child: new Text(
this.text,
style: const TextStyle(color: Colors.white, fontSize: 32.0)
)
)
)
// )
);
}
}
......
......@@ -35,8 +35,10 @@ class FloatToken extends NumberToken {
static double _parse(String stringRep) {
String toParse = stringRep;
if (toParse.startsWith('.')) toParse = '0' + toParse;
if (toParse.endsWith('.')) toParse = toParse + '0';
if (toParse.startsWith('.'))
toParse = '0' + toParse;
if (toParse.endsWith('.'))
toParse = toParse + '0';
return double.parse(toParse);
}
}
......@@ -45,11 +47,12 @@ class FloatToken extends NumberToken {
class ResultToken extends NumberToken {
ResultToken(num number) : super.fromNumber(round(number));
// rounds |number| to 14 digits of precision. A double precision
// floating point number is guaranteed to have at least this many
// decimal digits of precision.
/// rounds `number` to 14 digits of precision. A double precision
/// floating point number is guaranteed to have at least this many
/// decimal digits of precision.
static num round(num number) {
if (number is int) return number;
if (number is int)
return number;
return double.parse(number.toStringAsPrecision(14));
}
}
......@@ -64,7 +67,7 @@ enum Operation { Addition, Subtraction, Multiplication, Division }
/// A token that represents an arithmetic operation symbol.
class OperationToken extends ExpressionToken {
OperationToken(Operation operation)
: this.operation = operation,
: operation = operation,
super(opString(operation));
Operation operation;
......@@ -86,18 +89,23 @@ class OperationToken extends ExpressionToken {
/// As the user taps different keys the current expression can be in one
/// of several states.
enum ExpressionState {
// The expression is empty or an operation symbol was just entered.
// A new number must be started now.
/// The expression is empty or an operation symbol was just entered.
/// A new number must be started now.
Start,
// A minus sign was entered as a leading negative prefix.
/// A minus sign was entered as a leading negative prefix.
LeadingNeg,
// We are in the midst of a number without a point.
/// We are in the midst of a number without a point.
Number,
// A point was just entered.
/// A point was just entered.
Point,
// We are in the midst of a number with a point.
/// We are in the midst of a number with a point.
NumberWithPoint,
// A result is being displayed
/// A result is being displayed
Result,
}
......@@ -112,21 +120,21 @@ class CalcExpression {
CalcExpression(this._list, this.state);
CalcExpression.Empty()
: this(new List<ExpressionToken>(), ExpressionState.Start);
: this(<ExpressionToken>[], ExpressionState.Start);
CalcExpression.Result(FloatToken result)
: _list = new List<ExpressionToken>(),
: _list = <ExpressionToken>[],
state = ExpressionState.Result {
_list.add(result);
}
// The tokens comprising the expression.
/// The tokens comprising the expression.
final List<ExpressionToken> _list;
// The state of the expression.
/// The state of the expression.
final ExpressionState state;
// The string representation of the expression. This will be displayed
// in the calculator's display panel.
/// The string representation of the expression. This will be displayed
/// in the calculator's display panel.
@override
String toString() {
StringBuffer buffer = new StringBuffer('');
......@@ -296,9 +304,9 @@ class CalcExpression {
return new CalcExpression(outList, ExpressionState.Result);
}
// Removes the next "term" from |list| and returns its numeric value.
// A "term" is a sequence of number tokens separated by multiplication
// and division symbols.
/// Removes the next "term" from `list` and returns its numeric value.
/// A "term" is a sequence of number tokens separated by multiplication
/// and division symbols.
static num removeNextTerm(List<ExpressionToken> list) {
assert(list != null && list.length >= 1);
final NumberToken firstNumToken = list.removeAt(0);
......
......@@ -5,6 +5,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;
import '../calculator/interface.dart';
import '../demo/all.dart';
import 'home.dart';
......@@ -12,6 +13,7 @@ final Map<String, WidgetBuilder> kRoutes = <String, WidgetBuilder>{
WeatherDemo.routeName: (BuildContext context) => new WeatherDemo(),
FitnessDemo.routeName: (BuildContext context) => new FitnessDemo(),
DrawingDemo.routeName: (BuildContext context) => new DrawingDemo(),
Calculator.routeName: (BuildContext context) => new Calculator(),
FlexibleSpaceDemo.routeName: (BuildContext context) => new FlexibleSpaceDemo(),
TabsFabDemo.routeName: (BuildContext context) => new TabsFabDemo(),
ButtonsDemo.routeName: (BuildContext context) => new ButtonsDemo(),
......
......@@ -5,6 +5,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import '../calculator/interface.dart';
import '../demo/all.dart';
import 'drawer.dart';
import 'header.dart';
......@@ -76,6 +77,7 @@ class GalleryHomeState extends State<GalleryHome> {
new GalleryItem(title: 'Weather', routeName: WeatherDemo.routeName),
new GalleryItem(title: 'Fitness', routeName: FitnessDemo.routeName),
new GalleryItem(title: 'Fancy lines', routeName: DrawingDemo.routeName),
new GalleryItem(title: 'Calculator', routeName: Calculator.routeName),
new GalleryItem(title: 'Flexible space toolbar', routeName: FlexibleSpaceDemo.routeName),
new GalleryItem(title: 'Floating action button', routeName: TabsFabDemo.routeName),
]
......
......@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:calculator/calc_expression.dart';
import 'package:flutter_gallery/calculator/logic.dart';
import 'package:test/test.dart';
void main() {
......
......@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:calculator/main.dart' as calculator;
import 'package:flutter_gallery/calculator/interface.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
......@@ -14,9 +14,7 @@ void main() {
// We press the "1" and the "2" buttons and check that the display
// reads "12".
testWidgets('Flutter calculator app smoke test', (WidgetTester tester) async {
calculator.main(); // builds the app and schedules a frame but doesn't trigger one
await tester.pump(); // see https://github.com/flutter/flutter/issues/1865
await tester.pump(); // triggers a frame
await tester.pumpWidget(new Calculator());
Finder oneButton = find.widgetWithText(InkResponse, '1');
expect(oneButton, findsOneWidget);
......
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