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, state = state.copyWith(
); buffer: resultStr,
} else {
state = state.copyWith(
buffer: result.toString(),
mode: CalculatorEngineMode.result, mode: CalculatorEngineMode.result,
); calcHistory: [
} '${state.buffer} = $resultStr',
...state.calcHistory,
]);
} }
} catch (err) { } catch (err) {
state = state.copyWith( state = state.copyWith(
@ -122,45 +137,214 @@ 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() { final String areaName;
if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS)) { final String label;
WidgetsFlutterBinding.ensureInitialized(); final CalculatorEngineCallback op;
setWindowTitle('Simplistic Calculator'); final CalcButtonType type;
setWindowMinSize(const Size(kWindowWidth, kWindowHeight));
getCurrentScreen().then((screen) {
setWindowFrame(
const Rect.fromLTWH(
300,
300,
kWindowHeight,
kWindowWidth,
),
);
});
}
} }
final buttonDefinitions = <ButtonDefinition>[
ButtonDefinition(
areaName: 'clear',
op: (engine) => engine.clear(),
label: 'AC',
),
ButtonDefinition(
areaName: 'bkspc',
op: (engine) => engine.backspace(),
label: '',
),
ButtonDefinition(
areaName: 'lparen',
op: (engine) => engine.addToBuffer('('),
label: '(',
),
ButtonDefinition(
areaName: 'rparen',
op: (engine) => engine.addToBuffer(')'),
label: ')',
),
ButtonDefinition(
areaName: 'sqrt',
op: (engine) => engine.addToBuffer('sqrt('),
label: '',
),
ButtonDefinition(
areaName: 'pow',
op: (engine) => engine.addToBuffer('^'),
label: '^',
),
ButtonDefinition(
areaName: 'abs',
op: (engine) => engine.addToBuffer('abs('),
label: 'Abs',
),
ButtonDefinition(
areaName: 'sgn',
op: (engine) => engine.addToBuffer('sgn('),
label: 'Sgn',
),
ButtonDefinition(
areaName: 'ceil',
op: (engine) => engine.addToBuffer('ceil('),
label: 'Ceil',
),
ButtonDefinition(
areaName: 'floor',
op: (engine) => engine.addToBuffer('floor('),
label: 'Floor',
),
ButtonDefinition(
areaName: 'e',
op: (engine) => engine.addToBuffer('e('),
label: 'e',
),
ButtonDefinition(
areaName: 'ln',
op: (engine) => engine.addToBuffer('ln('),
label: 'ln',
),
ButtonDefinition(
areaName: 'sin',
op: (engine) => engine.addToBuffer('sin('),
label: 'Sin',
),
ButtonDefinition(
areaName: 'cos',
op: (engine) => engine.addToBuffer('cos('),
label: 'Cos',
),
ButtonDefinition(
areaName: 'tan',
op: (engine) => engine.addToBuffer('tan('),
label: 'Tan',
),
ButtonDefinition(
areaName: 'fact',
op: (engine) => engine.addToBuffer('!'),
label: '!',
),
ButtonDefinition(
areaName: 'arcsin',
op: (engine) => engine.addToBuffer('arcsin('),
label: 'Arc Sin',
),
ButtonDefinition(
areaName: 'arccos',
op: (engine) => engine.addToBuffer('arccos('),
label: 'Arc Cos',
),
ButtonDefinition(
areaName: 'arctan',
op: (engine) => engine.addToBuffer('arctan('),
label: 'Arc Tan',
),
ButtonDefinition(
areaName: 'mod',
op: (engine) => engine.addToBuffer('%'),
label: 'Mod',
),
ButtonDefinition(
areaName: 'seven',
op: (engine) => engine.addToBuffer('7'),
label: '7',
),
ButtonDefinition(
areaName: 'eight',
op: (engine) => engine.addToBuffer('8'),
label: '8',
),
ButtonDefinition(
areaName: 'nine',
op: (engine) => engine.addToBuffer('9'),
label: '9',
),
ButtonDefinition(
areaName: 'four',
op: (engine) => engine.addToBuffer('4'),
label: '4',
),
ButtonDefinition(
areaName: 'five',
op: (engine) => engine.addToBuffer('5'),
label: '5',
),
ButtonDefinition(
areaName: 'six',
op: (engine) => engine.addToBuffer('6'),
label: '6',
),
ButtonDefinition(
areaName: 'one',
op: (engine) => engine.addToBuffer('1'),
label: '1',
),
ButtonDefinition(
areaName: 'two',
op: (engine) => engine.addToBuffer('2'),
label: '2',
),
ButtonDefinition(
areaName: 'three',
op: (engine) => engine.addToBuffer('3'),
label: '3',
),
ButtonDefinition(
areaName: 'zero',
op: (engine) => engine.addToBuffer('0'),
label: '0',
),
ButtonDefinition(
areaName: 'point',
op: (engine) => engine.addToBuffer('.'),
label: '.',
),
ButtonDefinition(
areaName: 'equals',
op: (engine) => engine.evaluate(),
label: '=',
type: CalcButtonType.elevated,
),
ButtonDefinition(
areaName: 'plus',
op: (engine) => engine.addToBuffer('+', continueWithResult: true),
label: '+',
),
ButtonDefinition(
areaName: 'minus',
op: (engine) => engine.addToBuffer('-', continueWithResult: true),
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 { class CalculatorApp extends ConsumerWidget {
const CalculatorApp({Key? key}) : super(key: key); const CalculatorApp({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final Display display = ref.watch(displayProvider); final state = ref.watch(calculatorStateProvider);
return MaterialApp( return MaterialApp(
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
@ -170,18 +354,18 @@ class CalculatorApp extends ConsumerWidget {
child: SafeArea( child: SafeArea(
child: LayoutGrid( child: LayoutGrid(
areas: ''' areas: '''
display display display display display display display display history
clear bkspc lparen rparen clear bkspc lparen rparen history
sqrt pow abs sgn sqrt pow abs sgn history
ceil floor e ln ceil floor e ln history
sin cos tan fact sin cos tan fact history
arcsin arccos arctan mod arcsin arccos arctan mod history
seven eight nine divide seven eight nine divide history
four five six multiply four five six multiply history
one two three minus one two three minus history
zero point equals plus zero point equals plus history
''', ''',
columnSizes: [1.fr, 1.fr, 1.fr, 1.fr], columnSizes: [1.fr, 1.fr, 1.fr, 1.fr, 2.fr],
rowSizes: [ rowSizes: [
2.fr, 2.fr,
1.fr, 1.fr,
@ -201,9 +385,9 @@ class CalculatorApp extends ConsumerWidget {
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 8), horizontal: 8, vertical: 8),
child: display.error.isEmpty child: state.error.isEmpty
? AutoSizeText( ? AutoSizeText(
display.buffer, state.buffer,
textAlign: TextAlign.end, textAlign: TextAlign.end,
style: const TextStyle( style: const TextStyle(
fontSize: 80, fontSize: 80,
@ -212,7 +396,7 @@ class CalculatorApp extends ConsumerWidget {
maxLines: 2, maxLines: 2,
) )
: AutoSizeText( : AutoSizeText(
display.error, state.error,
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: const TextStyle( style: const TextStyle(
fontSize: 80, fontSize: 80,
@ -223,265 +407,37 @@ class CalculatorApp extends ConsumerWidget {
), ),
), ),
), ),
NamedAreaGridPlacement( ...buttonDefinitions.map(
areaName: 'clear', (definition) => NamedAreaGridPlacement(
child: CalcButton( areaName: definition.areaName,
op: (engine) => engine.clear(), child: CalcButton(
label: 'AC', label: definition.label,
), op: definition.op,
), type: definition.type,
NamedAreaGridPlacement(
areaName: 'bkspc',
child: CalcButton(
op: (engine) => engine.backspace(),
label: '',
),
),
NamedAreaGridPlacement(
areaName: 'lparen',
child: CalcButton(
op: (engine) => engine.addToBuffer('('),
label: '(',
),
),
NamedAreaGridPlacement(
areaName: 'rparen',
child: CalcButton(
op: (engine) => engine.addToBuffer(')'),
label: ')',
),
),
NamedAreaGridPlacement(
areaName: 'sqrt',
child: CalcButton(
op: (engine) => engine.addToBuffer('sqrt('),
label: '',
),
),
NamedAreaGridPlacement(
areaName: 'pow',
child: CalcButton(
op: (engine) => engine.addToBuffer('^'),
label: '^',
),
),
NamedAreaGridPlacement(
areaName: 'abs',
child: CalcButton(
op: (engine) => engine.addToBuffer('abs('),
label: 'Abs',
),
),
NamedAreaGridPlacement(
areaName: 'sgn',
child: CalcButton(
op: (engine) => engine.addToBuffer('sgn('),
label: 'Sgn',
),
),
NamedAreaGridPlacement(
areaName: 'ceil',
child: CalcButton(
op: (engine) => engine.addToBuffer('ceil('),
label: 'Ceil',
),
),
NamedAreaGridPlacement(
areaName: 'floor',
child: CalcButton(
op: (engine) => engine.addToBuffer('floor('),
label: 'Floor',
),
),
NamedAreaGridPlacement(
areaName: 'e',
child: CalcButton(
op: (engine) => engine.addToBuffer('e('),
label: 'e',
),
),
NamedAreaGridPlacement(
areaName: 'ln',
child: CalcButton(
op: (engine) => engine.addToBuffer('ln('),
label: 'ln',
),
),
NamedAreaGridPlacement(
areaName: 'sin',
child: CalcButton(
op: (engine) => engine.addToBuffer('sin('),
label: 'Sin',
),
),
NamedAreaGridPlacement(
areaName: 'cos',
child: CalcButton(
op: (engine) => engine.addToBuffer('cos('),
label: 'Cos',
),
),
NamedAreaGridPlacement(
areaName: 'tan',
child: CalcButton(
op: (engine) => engine.addToBuffer('tan('),
label: 'Tan',
),
),
NamedAreaGridPlacement(
areaName: 'fact',
child: CalcButton(
op: (engine) => engine.addToBuffer('!'),
label: '!',
),
),
NamedAreaGridPlacement(
areaName: 'arcsin',
child: CalcButton(
op: (engine) => engine.addToBuffer('arcsin('),
label: 'Arc Sin',
),
),
NamedAreaGridPlacement(
areaName: 'arccos',
child: CalcButton(
op: (engine) => engine.addToBuffer('arccos('),
label: 'Arc Cos',
),
),
NamedAreaGridPlacement(
areaName: 'arctan',
child: CalcButton(
op: (engine) => engine.addToBuffer('arctan('),
label: 'Arc Tan',
),
),
NamedAreaGridPlacement(
areaName: 'mod',
child: CalcButton(
op: (engine) => engine.addToBuffer('%'),
label: 'Mod',
),
),
NamedAreaGridPlacement(
areaName: 'seven',
child: CalcButton(
op: (engine) => engine.addToBuffer('7'),
label: '7',
),
),
NamedAreaGridPlacement(
areaName: 'eight',
child: CalcButton(
op: (engine) => engine.addToBuffer('8'),
label: '8',
),
),
NamedAreaGridPlacement(
areaName: 'nine',
child: CalcButton(
op: (engine) => engine.addToBuffer('9'),
label: '9',
),
),
NamedAreaGridPlacement(
areaName: 'four',
child: CalcButton(
op: (engine) => engine.addToBuffer('4'),
label: '4',
),
),
NamedAreaGridPlacement(
areaName: 'five',
child: CalcButton(
op: (engine) => engine.addToBuffer('5'),
label: '5',
),
),
NamedAreaGridPlacement(
areaName: 'six',
child: CalcButton(
op: (engine) => engine.addToBuffer('6'),
label: '6',
),
),
NamedAreaGridPlacement(
areaName: 'one',
child: CalcButton(
op: (engine) => engine.addToBuffer('1'),
label: '1',
),
),
NamedAreaGridPlacement(
areaName: 'two',
child: CalcButton(
op: (engine) => engine.addToBuffer('2'),
label: '2',
),
),
NamedAreaGridPlacement(
areaName: 'three',
child: CalcButton(
op: (engine) => engine.addToBuffer('3'),
label: '3',
),
),
NamedAreaGridPlacement(
areaName: 'zero',
child: CalcButton(
op: (engine) => engine.addToBuffer('0'),
label: '0',
),
),
NamedAreaGridPlacement(
areaName: 'point',
child: CalcButton(
op: (engine) => engine.addToBuffer('.'),
label: '.',
),
),
NamedAreaGridPlacement(
areaName: 'equals',
child: const CalcEqualsButton(),
),
NamedAreaGridPlacement(
areaName: 'plus',
child: CalcButton(
op: (engine) => engine.addToBuffer(
'+',
continueWithResult: true,
),
label: '+',
),
),
NamedAreaGridPlacement(
areaName: 'minus',
child: CalcButton(
op: (engine) => engine.addToBuffer(
'-',
continueWithResult: true,
),
label: '-',
),
),
NamedAreaGridPlacement(
areaName: 'multiply',
child: CalcButton(
op: (engine) => engine.addToBuffer(
'*',
continueWithResult: true,
), ),
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,
),
),
),
...state.calcHistory.map(
(result) => ListTile(
title: Text(result),
),
)
],
), ),
label: '/',
), ),
), ),
], ],
@ -493,47 +449,32 @@ class CalculatorApp extends ConsumerWidget {
} }
} }
class CalcEqualsButton extends ConsumerWidget { typedef CalculatorEngineCallback = void Function(CalculatorEngine engine);
const CalcEqualsButton({ enum CalcButtonType { outlined, elevated }
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),
),
),
),
);
}
}
typedef _CalculatorEngineCallback = void Function(CalculatorEngine engine);
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