Refactoring `simplistic_calculator` (#1032)

Seperate configuraiton data from display.
Also, it now uses constructor tearoffs.
pull/1033/head
Brett Morgan 3 years ago committed by GitHub
parent 4366e1015a
commit 67a6d1a068
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -12,9 +12,25 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:math_expressions/math_expressions.dart'; import 'package:math_expressions/math_expressions.dart';
import 'package:window_size/window_size.dart'; import 'package:window_size/window_size.dart';
const double windowWidth = 600;
const double windowHeight = 900;
void main() {
if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS)) {
WidgetsFlutterBinding.ensureInitialized();
setWindowTitle('Simplistic Calculator');
}
runApp(
const ProviderScope(
child: CalculatorApp(),
),
);
}
@immutable @immutable
class Display { class CalculatorState {
const Display({ const CalculatorState({
required this.buffer, required this.buffer,
required this.calcHistory, required this.calcHistory,
required this.mode, required this.mode,
@ -26,13 +42,13 @@ class Display {
final CalculatorEngineMode mode; final CalculatorEngineMode mode;
final String error; final String error;
Display copyWith({ CalculatorState copyWith({
String? buffer, String? buffer,
List<String>? calcHistory, List<String>? calcHistory,
CalculatorEngineMode? mode, CalculatorEngineMode? mode,
String? error, String? error,
}) => }) =>
Display( CalculatorState(
buffer: buffer ?? this.buffer, buffer: buffer ?? this.buffer,
calcHistory: calcHistory ?? this.calcHistory, calcHistory: calcHistory ?? this.calcHistory,
mode: mode ?? this.mode, mode: mode ?? this.mode,
@ -42,10 +58,10 @@ class Display {
enum CalculatorEngineMode { input, result } enum CalculatorEngineMode { input, result }
class CalculatorEngine extends StateNotifier<Display> { class CalculatorEngine extends StateNotifier<CalculatorState> {
CalculatorEngine() CalculatorEngine()
: super( : super(
const Display( const CalculatorState(
buffer: '0', buffer: '0',
calcHistory: [], calcHistory: [],
mode: CalculatorEngineMode.result, mode: CalculatorEngineMode.result,
@ -100,17 +116,16 @@ class CalculatorEngine extends StateNotifier<Display> {
mode: CalculatorEngineMode.result, mode: CalculatorEngineMode.result,
); );
} else { } else {
if (result.ceil() == result) { final resultStr = result.ceil() == result
state = state.copyWith( ? result.toInt().toString()
buffer: result.toInt().toString(), : result.toString();
mode: CalculatorEngineMode.result,
);
} else {
state = state.copyWith( state = state.copyWith(
buffer: result.toString(), buffer: resultStr,
mode: CalculatorEngineMode.result, mode: CalculatorEngineMode.result,
); calcHistory: [
} '${state.buffer} = $resultStr',
...state.calcHistory,
]);
} }
} catch (err) { } catch (err) {
state = state.copyWith( state = state.copyWith(
@ -122,392 +137,311 @@ class CalculatorEngine extends StateNotifier<Display> {
} }
} }
final displayProvider = final calculatorStateProvider =
StateNotifierProvider<CalculatorEngine, Display>((_) => CalculatorEngine()); StateNotifierProvider<CalculatorEngine, CalculatorState>(
(_) => CalculatorEngine());
void main() { class ButtonDefinition {
setupWindow(); const ButtonDefinition({
runApp( required this.areaName,
const ProviderScope( required this.label,
child: CalculatorApp(), required this.op,
), this.type = CalcButtonType.outlined,
);
}
const double kWindowWidth = 600;
const double kWindowHeight = 900;
void setupWindow() {
if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS)) {
WidgetsFlutterBinding.ensureInitialized();
setWindowTitle('Simplistic Calculator');
setWindowMinSize(const Size(kWindowWidth, kWindowHeight));
getCurrentScreen().then((screen) {
setWindowFrame(
const Rect.fromLTWH(
300,
300,
kWindowHeight,
kWindowWidth,
),
);
}); });
}
}
class CalculatorApp extends ConsumerWidget { final String areaName;
const CalculatorApp({Key? key}) : super(key: key); final String label;
final CalculatorEngineCallback op;
@override final CalcButtonType type;
Widget build(BuildContext context, WidgetRef ref) { }
final Display display = ref.watch(displayProvider);
return MaterialApp( final buttonDefinitions = <ButtonDefinition>[
debugShowCheckedModeBanner: false, ButtonDefinition(
home: Scaffold(
body: Container(
color: Colors.white,
child: SafeArea(
child: LayoutGrid(
areas: '''
display display display display
clear bkspc lparen rparen
sqrt pow abs sgn
ceil floor e ln
sin cos tan fact
arcsin arccos arctan mod
seven eight nine divide
four five six multiply
one two three minus
zero point equals plus
''',
columnSizes: [1.fr, 1.fr, 1.fr, 1.fr],
rowSizes: [
2.fr,
1.fr,
1.fr,
1.fr,
1.fr,
1.fr,
2.fr,
2.fr,
2.fr,
2.fr
],
children: [
NamedAreaGridPlacement(
areaName: 'display',
child: SizedBox.expand(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 8),
child: display.error.isEmpty
? AutoSizeText(
display.buffer,
textAlign: TextAlign.end,
style: const TextStyle(
fontSize: 80,
color: Colors.black,
),
maxLines: 2,
)
: AutoSizeText(
display.error,
textAlign: TextAlign.start,
style: const TextStyle(
fontSize: 80,
color: Colors.red,
),
maxLines: 2,
),
),
),
),
NamedAreaGridPlacement(
areaName: 'clear', areaName: 'clear',
child: CalcButton(
op: (engine) => engine.clear(), op: (engine) => engine.clear(),
label: 'AC', label: 'AC',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'bkspc', areaName: 'bkspc',
child: CalcButton(
op: (engine) => engine.backspace(), op: (engine) => engine.backspace(),
label: '', label: '',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'lparen', areaName: 'lparen',
child: CalcButton(
op: (engine) => engine.addToBuffer('('), op: (engine) => engine.addToBuffer('('),
label: '(', label: '(',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'rparen', areaName: 'rparen',
child: CalcButton(
op: (engine) => engine.addToBuffer(')'), op: (engine) => engine.addToBuffer(')'),
label: ')', label: ')',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'sqrt', areaName: 'sqrt',
child: CalcButton(
op: (engine) => engine.addToBuffer('sqrt('), op: (engine) => engine.addToBuffer('sqrt('),
label: '', label: '',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'pow', areaName: 'pow',
child: CalcButton(
op: (engine) => engine.addToBuffer('^'), op: (engine) => engine.addToBuffer('^'),
label: '^', label: '^',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'abs', areaName: 'abs',
child: CalcButton(
op: (engine) => engine.addToBuffer('abs('), op: (engine) => engine.addToBuffer('abs('),
label: 'Abs', label: 'Abs',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'sgn', areaName: 'sgn',
child: CalcButton(
op: (engine) => engine.addToBuffer('sgn('), op: (engine) => engine.addToBuffer('sgn('),
label: 'Sgn', label: 'Sgn',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'ceil', areaName: 'ceil',
child: CalcButton(
op: (engine) => engine.addToBuffer('ceil('), op: (engine) => engine.addToBuffer('ceil('),
label: 'Ceil', label: 'Ceil',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'floor', areaName: 'floor',
child: CalcButton(
op: (engine) => engine.addToBuffer('floor('), op: (engine) => engine.addToBuffer('floor('),
label: 'Floor', label: 'Floor',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'e', areaName: 'e',
child: CalcButton(
op: (engine) => engine.addToBuffer('e('), op: (engine) => engine.addToBuffer('e('),
label: 'e', label: 'e',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'ln', areaName: 'ln',
child: CalcButton(
op: (engine) => engine.addToBuffer('ln('), op: (engine) => engine.addToBuffer('ln('),
label: 'ln', label: 'ln',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'sin', areaName: 'sin',
child: CalcButton(
op: (engine) => engine.addToBuffer('sin('), op: (engine) => engine.addToBuffer('sin('),
label: 'Sin', label: 'Sin',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'cos', areaName: 'cos',
child: CalcButton(
op: (engine) => engine.addToBuffer('cos('), op: (engine) => engine.addToBuffer('cos('),
label: 'Cos', label: 'Cos',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'tan', areaName: 'tan',
child: CalcButton(
op: (engine) => engine.addToBuffer('tan('), op: (engine) => engine.addToBuffer('tan('),
label: 'Tan', label: 'Tan',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'fact', areaName: 'fact',
child: CalcButton(
op: (engine) => engine.addToBuffer('!'), op: (engine) => engine.addToBuffer('!'),
label: '!', label: '!',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'arcsin', areaName: 'arcsin',
child: CalcButton(
op: (engine) => engine.addToBuffer('arcsin('), op: (engine) => engine.addToBuffer('arcsin('),
label: 'Arc Sin', label: 'Arc Sin',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'arccos', areaName: 'arccos',
child: CalcButton(
op: (engine) => engine.addToBuffer('arccos('), op: (engine) => engine.addToBuffer('arccos('),
label: 'Arc Cos', label: 'Arc Cos',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'arctan', areaName: 'arctan',
child: CalcButton(
op: (engine) => engine.addToBuffer('arctan('), op: (engine) => engine.addToBuffer('arctan('),
label: 'Arc Tan', label: 'Arc Tan',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'mod', areaName: 'mod',
child: CalcButton(
op: (engine) => engine.addToBuffer('%'), op: (engine) => engine.addToBuffer('%'),
label: 'Mod', label: 'Mod',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'seven', areaName: 'seven',
child: CalcButton(
op: (engine) => engine.addToBuffer('7'), op: (engine) => engine.addToBuffer('7'),
label: '7', label: '7',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'eight', areaName: 'eight',
child: CalcButton(
op: (engine) => engine.addToBuffer('8'), op: (engine) => engine.addToBuffer('8'),
label: '8', label: '8',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'nine', areaName: 'nine',
child: CalcButton(
op: (engine) => engine.addToBuffer('9'), op: (engine) => engine.addToBuffer('9'),
label: '9', label: '9',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'four', areaName: 'four',
child: CalcButton(
op: (engine) => engine.addToBuffer('4'), op: (engine) => engine.addToBuffer('4'),
label: '4', label: '4',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'five', areaName: 'five',
child: CalcButton(
op: (engine) => engine.addToBuffer('5'), op: (engine) => engine.addToBuffer('5'),
label: '5', label: '5',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'six', areaName: 'six',
child: CalcButton(
op: (engine) => engine.addToBuffer('6'), op: (engine) => engine.addToBuffer('6'),
label: '6', label: '6',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'one', areaName: 'one',
child: CalcButton(
op: (engine) => engine.addToBuffer('1'), op: (engine) => engine.addToBuffer('1'),
label: '1', label: '1',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'two', areaName: 'two',
child: CalcButton(
op: (engine) => engine.addToBuffer('2'), op: (engine) => engine.addToBuffer('2'),
label: '2', label: '2',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'three', areaName: 'three',
child: CalcButton(
op: (engine) => engine.addToBuffer('3'), op: (engine) => engine.addToBuffer('3'),
label: '3', label: '3',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'zero', areaName: 'zero',
child: CalcButton(
op: (engine) => engine.addToBuffer('0'), op: (engine) => engine.addToBuffer('0'),
label: '0', label: '0',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'point', areaName: 'point',
child: CalcButton(
op: (engine) => engine.addToBuffer('.'), op: (engine) => engine.addToBuffer('.'),
label: '.', label: '.',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'equals', areaName: 'equals',
child: const CalcEqualsButton(), op: (engine) => engine.evaluate(),
label: '=',
type: CalcButtonType.elevated,
), ),
NamedAreaGridPlacement( ButtonDefinition(
areaName: 'plus', areaName: 'plus',
child: CalcButton( op: (engine) => engine.addToBuffer('+', continueWithResult: true),
op: (engine) => engine.addToBuffer(
'+',
continueWithResult: true,
),
label: '+', label: '+',
), ),
), ButtonDefinition(
NamedAreaGridPlacement(
areaName: 'minus', areaName: 'minus',
child: CalcButton( op: (engine) => engine.addToBuffer('-', continueWithResult: true),
op: (engine) => engine.addToBuffer(
'-',
continueWithResult: true,
),
label: '-', label: '-',
), ),
ButtonDefinition(
areaName: 'multiply',
op: (engine) => engine.addToBuffer('*', continueWithResult: true),
label: '*',
), ),
ButtonDefinition(
areaName: 'divide',
op: (engine) => engine.addToBuffer('/', continueWithResult: true),
label: '/',
),
];
class CalculatorApp extends ConsumerWidget {
const CalculatorApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(calculatorStateProvider);
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Container(
color: Colors.white,
child: SafeArea(
child: LayoutGrid(
areas: '''
display display display display history
clear bkspc lparen rparen history
sqrt pow abs sgn history
ceil floor e ln history
sin cos tan fact history
arcsin arccos arctan mod history
seven eight nine divide history
four five six multiply history
one two three minus history
zero point equals plus history
''',
columnSizes: [1.fr, 1.fr, 1.fr, 1.fr, 2.fr],
rowSizes: [
2.fr,
1.fr,
1.fr,
1.fr,
1.fr,
1.fr,
2.fr,
2.fr,
2.fr,
2.fr
],
children: [
NamedAreaGridPlacement( NamedAreaGridPlacement(
areaName: 'multiply', areaName: 'display',
child: SizedBox.expand(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 8),
child: state.error.isEmpty
? AutoSizeText(
state.buffer,
textAlign: TextAlign.end,
style: const TextStyle(
fontSize: 80,
color: Colors.black,
),
maxLines: 2,
)
: AutoSizeText(
state.error,
textAlign: TextAlign.start,
style: const TextStyle(
fontSize: 80,
color: Colors.red,
),
maxLines: 2,
),
),
),
),
...buttonDefinitions.map(
(definition) => NamedAreaGridPlacement(
areaName: definition.areaName,
child: CalcButton( child: CalcButton(
op: (engine) => engine.addToBuffer( label: definition.label,
'*', op: definition.op,
continueWithResult: true, type: definition.type,
), ),
label: '*',
), ),
), ),
NamedAreaGridPlacement( NamedAreaGridPlacement(
areaName: 'divide', areaName: 'history',
child: CalcButton( child: Padding(
op: (engine) => engine.addToBuffer( padding: const EdgeInsets.symmetric(horizontal: 8),
'/', child: ListView(
continueWithResult: true, children: [
const ListTile(
title: Text(
'History',
style: TextStyle(
fontWeight: FontWeight.bold,
),
), ),
label: '/',
), ),
...state.calcHistory.map(
(result) => ListTile(
title: Text(result),
), ),
)
], ],
), ),
), ),
), ),
],
), ),
);
}
}
class CalcEqualsButton extends ConsumerWidget {
const CalcEqualsButton({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
return SizedBox.expand(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () => ref.read(displayProvider.notifier).evaluate(),
child: const AutoSizeText(
'=',
style: TextStyle(fontSize: 40, color: Colors.white),
), ),
), ),
), ),
@ -515,25 +449,32 @@ class CalcEqualsButton extends ConsumerWidget {
} }
} }
typedef _CalculatorEngineCallback = void Function(CalculatorEngine engine); typedef CalculatorEngineCallback = void Function(CalculatorEngine engine);
enum CalcButtonType { outlined, elevated }
class CalcButton extends ConsumerWidget { class CalcButton extends ConsumerWidget {
const CalcButton({ const CalcButton({
Key? key, Key? key,
required this.op, required this.op,
required this.label, required this.label,
required this.type,
}) : super(key: key); }) : super(key: key);
final _CalculatorEngineCallback op; final CalculatorEngineCallback op;
final String label; final String label;
final CalcButtonType type;
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final buttonConstructor = type == CalcButtonType.elevated
? ElevatedButton.new
: OutlinedButton.new;
return SizedBox.expand( return SizedBox.expand(
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8),
child: OutlinedButton( child: buttonConstructor(
onPressed: () => op(ref.read(displayProvider.notifier)), onPressed: () => op(ref.read(calculatorStateProvider.notifier)),
child: AutoSizeText( child: AutoSizeText(
label, label,
style: const TextStyle(fontSize: 40, color: Colors.black54), style: const TextStyle(fontSize: 40, color: Colors.black54),

Loading…
Cancel
Save