Commit 719add81 authored by Mitch Rudominer's avatar Mitch Rudominer Committed by Ian Hickson

New Material Design calculator example. (#3999)

* New Material Design calculator example. (Attempt 2 after git fetch upstream).

* Responded to code review.

* Second round of code review.
parent 2a9994d0
......@@ -19,10 +19,11 @@ flutter analyze --flutter-repo
(cd packages/flx; dart -c test/all.dart)
(cd dev/manual_tests; flutter test)
(cd examples/calculator; flutter test)
(cd examples/hello_world; flutter test)
(cd examples/layers; flutter test)
(cd examples/flutter_gallery; flutter test)
(cd examples/stocks; flutter test)
(cd examples/flutter_gallery; flutter test)
# generate and analyze our large sample app
dart dev/tools/mega_gallery.dart
......
.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
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.
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.
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.
This diff was suppressed by a .gitattributes entry.
<?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>
This diff is collapsed.
// Copyright 2016 The Chromium 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 'calc_expression.dart';
// A calculator application.
void main() {
runApp(new MaterialApp(
title: 'Calculator',
theme: new ThemeData(primarySwatch: Colors.blue),
home: new Calculator()));
}
class Calculator extends StatefulWidget {
Calculator({Key key}) : super(key: key);
@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.
final List<CalcExpression> _expressionStack = <CalcExpression>[];
CalcExpression _expression = new CalcExpression.Empty();
// 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.
void popCalcExpression() {
if (_expressionStack.length > 0) {
_expression = _expressionStack.removeLast();
} else {
_expression = new CalcExpression.Empty();
}
}
// Set |resultExpression| to the currrent expression and clear the stack.
void setResult(CalcExpression resultExpression) {
_expressionStack.clear();
_expression = resultExpression;
}
void handleNumberTap(int n) {
final CalcExpression expression = _expression.appendDigit(n);
if (expression != null) {
setState(() {
pushExpression(expression);
});
}
}
void handlePointTap() {
final CalcExpression expression = _expression.appendPoint();
if (expression != null) {
setState(() {
pushExpression(expression);
});
}
}
void handlePlusTap() {
final CalcExpression expression =
_expression.appendOperation(Operation.Addition);
if (expression != null) {
setState(() {
pushExpression(expression);
});
}
}
void handleMinusTap() {
final CalcExpression expression = _expression.appendMinus();
if (expression != null) {
setState(() {
pushExpression(expression);
});
}
}
void handleMultTap() {
final CalcExpression expression =
_expression.appendOperation(Operation.Multiplication);
if (expression != null) {
setState(() {
pushExpression(expression);
});
}
}
void handleDivTap() {
final CalcExpression expression =
_expression.appendOperation(Operation.Division);
if (expression != null) {
setState(() {
pushExpression(expression);
});
}
}
void handleEqualsTap() {
final CalcExpression resultExpression = _expression.computeResult();
if (resultExpression != null) {
setState(() {
setResult(resultExpression);
});
}
}
void handleDelTap() {
setState(() {
popCalcExpression();
});
}
@override
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.
new CalcDisplay(2, _expression.toString()),
new KeyPad(3, calcState: this)
]));
}
}
class CalcDisplay extends StatelessWidget {
CalcDisplay(this._flex, this._contents);
final int _flex;
final String _contents;
@override
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))));
}
}
class KeyPad extends StatelessWidget {
KeyPad(this._flex, {this.calcState});
final int _flex;
final _CalculatorState calcState;
@override
Widget build(BuildContext context) {
return new Flexible(
flex: _flex,
child: new Row(children: <Widget>[
new MainKeyPad(calcState: calcState),
new OpKeyPad(calcState: calcState),
]));
}
}
class MainKeyPad extends StatelessWidget {
MainKeyPad({this.calcState});
final _CalculatorState calcState;
@override
Widget build(BuildContext context) {
return new Flexible(
// We set flex equal to the number of columns so that the main keypad
// and the op keypad have sizes proportional to their number of
// columns.
flex: 3,
child: new Material(
type: MaterialType.canvas,
elevation: 12,
color: Colors.grey[800],
child: new Column(children: <Widget>[
new KeyRow(<Widget>[
new NumberKey(7, calcState),
new NumberKey(8, calcState),
new NumberKey(9, calcState)
]),
new KeyRow(<Widget>[
new NumberKey(4, calcState),
new NumberKey(5, calcState),
new NumberKey(6, calcState)
]),
new KeyRow(<Widget>[
new NumberKey(1, calcState),
new NumberKey(2, calcState),
new NumberKey(3, calcState)
]),
new KeyRow(<Widget>[
new CalcKey('.', calcState.handlePointTap),
new NumberKey(0, calcState),
new CalcKey('=', calcState.handleEqualsTap),
])
])));
}
}
class OpKeyPad extends StatelessWidget {
OpKeyPad({this.calcState});
final _CalculatorState calcState;
@override
Widget build(BuildContext context) {
return new Flexible(child: new Material(
type: MaterialType.canvas,
elevation: 24,
color: Colors.grey[700],
child: new Column(children: <Widget>[
new CalcKey('DEL', calcState.handleDelTap),
new CalcKey('\u00F7', calcState.handleDivTap),
new CalcKey('\u00D7', calcState.handleMultTap),
new CalcKey('-', calcState.handleMinusTap),
new CalcKey('+', calcState.handlePlusTap)
])));
}
}
class KeyRow extends StatelessWidget {
KeyRow(this.keys);
final List<Widget> keys;
@override
Widget build(BuildContext context) {
return new Flexible(child: new Row(
mainAxisAlignment: MainAxisAlignment.center, children: this.keys));
}
}
class CalcKey extends StatelessWidget {
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(
onTap: this.onTap,
child: new Center(child: new Text(this.text,
style: const TextStyle(color: Colors.white, fontSize: 32.0))))));
}
}
class NumberKey extends CalcKey {
NumberKey(int value, _CalculatorState calcState)
: super('$value', () {
calcState.handleNumberTap(value);
});
}
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
// Copyright 2016 The Chromium 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:calculator/calc_expression.dart';
import 'package:test/test.dart';
void main() {
test('Test order of operations: 12 + 3 * 4 = 24', () {
CalcExpression expression = new CalcExpression.Empty();
expression = expression.appendDigit(1);
expression = expression.appendDigit(2);
expression = expression.appendOperation(Operation.Addition);
expression = expression.appendDigit(3);
expression = expression.appendOperation(Operation.Multiplication);
expression = expression.appendDigit(4);
expression = expression.computeResult();
expect(expression.state, equals(ExpressionState.Result));
expect(expression.toString(), equals("24"));
});
test('Test floating point 0.1 + 0.2 = 0.3', () {
CalcExpression expression = new CalcExpression.Empty();
expression = expression.appendDigit(0);
expression = expression.appendPoint();
expression = expression.appendDigit(1);
expression = expression.appendOperation(Operation.Addition);
expression = expression.appendDigit(0);
expression = expression.appendPoint();
expression = expression.appendDigit(2);
expression = expression.computeResult();
expect(expression.state, equals(ExpressionState.Result));
expect(expression.toString(), equals("0.3"));
});
test('Test floating point 1.0/10.0 = 0.1', () {
CalcExpression expression = new CalcExpression.Empty();
expression = expression.appendDigit(1);
expression = expression.appendPoint();
expression = expression.appendDigit(0);
expression = expression.appendOperation(Operation.Division);
expression = expression.appendDigit(1);
expression = expression.appendDigit(0);
expression = expression.appendPoint();
expression = expression.appendDigit(0);
expression = expression.computeResult();
expect(expression.state, equals(ExpressionState.Result));
expect(expression.toString(), equals("0.1"));
});
test('Test 1/0 = Infinity', () {
CalcExpression expression = new CalcExpression.Empty();
expression = expression.appendDigit(1);
expression = expression.appendOperation(Operation.Division);
expression = expression.appendDigit(0);
expression = expression.computeResult();
expect(expression.state, equals(ExpressionState.Result));
expect(expression.toString(), equals("Infinity"));
});
test('Test use result in next calculation: 1 + 1 = 2 + 1 = 3 + 1 = 4', () {
CalcExpression expression = new CalcExpression.Empty();
expression = expression.appendDigit(1);
expression = expression.appendOperation(Operation.Addition);
expression = expression.appendDigit(1);
expression = expression.computeResult();
expression = expression.appendOperation(Operation.Addition);
expression = expression.appendDigit(1);
expression = expression.computeResult();
expression = expression.appendOperation(Operation.Addition);
expression = expression.appendDigit(1);
expression = expression.computeResult();
expect(expression.state, equals(ExpressionState.Result));
expect(expression.toString(), equals("4"));
});
test('Test minus -3 - -2 = -1', () {
CalcExpression expression = new CalcExpression.Empty();
expression = expression.appendMinus();
expression = expression.appendDigit(3);
expression = expression.appendMinus();
expression = expression.appendMinus();
expression = expression.appendDigit(2);
expression = expression.computeResult();
expect(expression.state, equals(ExpressionState.Result));
expect(expression.toString(), equals("-1"));
});
}
// Copyright 2016 The Chromium 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:calculator/main.dart' as calculator;
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
if (binding is LiveTestWidgetsFlutterBinding)
binding.allowAllFrames = true;
// 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
Finder oneButton = find.widgetWithText(InkResponse, '1');
expect(oneButton, findsOneWidget);
Finder twoButton = find.widgetWithText(InkResponse, '2');
expect(twoButton, findsOneWidget);
await tester.tap(oneButton);
await tester.pump();
await tester.tap(twoButton);
await tester.pump();
await tester.pump(const Duration(seconds: 1)); // Wait until it has finished.
Finder display = find.widgetWithText(Flexible, '12');
expect(display, findsOneWidget);
});
}
......@@ -9,12 +9,16 @@ import 'package:flutter_gallery/gallery/item.dart' as flutter_gallery_item;
import 'package:flutter_gallery/main.dart' as flutter_gallery_main;
// Warning: the following strings must be kept in sync with GalleryHome.
const List<String> demoCategories = const <String>['Demos', 'Components', 'Style'];
const List<String> demoCategories = const <String>[
'Demos',
'Components',
'Style'
];
Finder findGalleryItemByRouteName(WidgetTester tester, String routeName) {
return find.byWidgetPredicate((Widget widget) {
return widget is flutter_gallery_item.GalleryItem
&& widget.routeName == routeName;
return widget is flutter_gallery_item.GalleryItem &&
widget.routeName == routeName;
});
}
......@@ -24,7 +28,8 @@ Finder byTooltip(WidgetTester tester, String message) {
});
}
Finder findNavigationMenuButton(WidgetTester tester) => byTooltip(tester, 'Open navigation menu');
Finder findNavigationMenuButton(WidgetTester tester) =>
byTooltip(tester, 'Open navigation menu');
Finder findBackButton(WidgetTester tester) => byTooltip(tester, 'Back');
......@@ -38,7 +43,8 @@ Future<Null> smokeDemo(WidgetTester tester, String routeName) async {
await tester.tap(menuItem);
await tester.pump(); // Launch the demo.
await tester.pump(const Duration(seconds: 1)); // Wait until the demo has opened.
await tester
.pump(const Duration(seconds: 1)); // Wait until the demo has opened.
// Go back
Finder backButton = findBackButton(tester);
......@@ -50,12 +56,13 @@ Future<Null> smokeDemo(WidgetTester tester, String routeName) async {
}
void main() {
TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
if (binding is LiveTestWidgetsFlutterBinding)
binding.allowAllFrames = true;
TestWidgetsFlutterBinding binding =
TestWidgetsFlutterBinding.ensureInitialized();
if (binding is LiveTestWidgetsFlutterBinding) binding.allowAllFrames = true;
testWidgets('Flutter Gallery app smoke test', (WidgetTester tester) async {
flutter_gallery_main.main(); // builds the app and schedules a frame but doesn't trigger one
flutter_gallery_main
.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
......@@ -63,14 +70,16 @@ void main() {
for (String category in demoCategories.reversed) {
await tester.tap(find.text(category));
await tester.pump();
await tester.pump(const Duration(seconds: 1)); // Wait until the menu has expanded.
await tester.pump(
const Duration(seconds: 1)); // Wait until the menu has expanded.
}
final List<double> scrollDeltas = new List<double>();
double previousY = tester.getTopRight(find.text(demoCategories[0])).y;
final List<String> routeNames = flutter_gallery_app.kRoutes.keys.toList();
for (String routeName in routeNames) {
final double y = tester.getTopRight(findGalleryItemByRouteName(tester, routeName)).y;
final double y =
tester.getTopRight(findGalleryItemByRouteName(tester, routeName)).y;
scrollDeltas.add(previousY - y);
previousY = y;
}
......@@ -79,7 +88,8 @@ void main() {
for (int i = 0; i < routeNames.length; i += 1) {
final String routeName = routeNames[i];
await smokeDemo(tester, routeName);
await tester.scroll(findGalleryItemByRouteName(tester, routeName), new Offset(0.0, scrollDeltas[i]));
await tester.scroll(findGalleryItemByRouteName(tester, routeName),
new Offset(0.0, scrollDeltas[i]));
await tester.pump();
}
......@@ -87,7 +97,8 @@ void main() {
expect(navigationMenuButton, findsOneWidget);
await tester.tap(navigationMenuButton);
await tester.pump(); // Start opening drawer.
await tester.pump(const Duration(seconds: 1)); // Wait until it's really opened.
await tester
.pump(const Duration(seconds: 1)); // Wait until it's really opened.
// switch theme
await tester.tap(find.text('Dark'));
......
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