Modern Calculator App Built in Flutter π’ | FuzzuTech
Demo :
Click Video πππ
π Features:
-
Embed YouTube Short video
-
Include Flutter code snippet (formatted in code block)
-
Add tags as Blogger “labels”
-
CTA: “Subscribe to FuzzuTech on YouTube for daily tech projects!”
Code :
import 'package:flutter/material.dart';
void main() {
runApp(CalculatorApp());
}
class CalculatorApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Modern Calculator',
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: Colors.black,
colorScheme: ColorScheme.dark(
primary: Colors.blue,
secondary: Colors.blueAccent,
),
),
home: CalculatorScreen(),
debugShowCheckedModeBanner: false,
);
}
}
class CalculatorScreen extends StatefulWidget {
@override
_CalculatorScreenState createState() => _CalculatorScreenState();
}
class _CalculatorScreenState extends State<CalculatorScreen> {
// Current expression being built
String _expression = '';
// Result of the calculation
String _result = '0';
// Track if we're starting a new expression
bool _newExpression = true;
// Button layout organized in rows
final List<List<String>> _buttonRows = [
['C', '⌫', '%', '÷'],
['7', '8', '9', '×'],
['4', '5', '6', '-'],
['1', '2', '3', '+'],
['+/-', '0', '.', '='],
];
// Check if a character is an operator
bool _isOperator(String char) {
return ['+', '-', '×', '÷'].contains(char);
}
// Handle button presses
void _onButtonPressed(String button) {
setState(() {
switch (button) {
case 'C':
_clear();
break;
case '⌫':
_backspace();
break;
case '=':
_calculate();
break;
case '+/-':
_toggleSign();
break;
case '%':
_percentage();
break;
case '.':
_addDecimal();
break;
default:
if (_isOperator(button)) {
_addOperator(button);
} else {
_addDigit(button);
}
}
});
}
// Clear everything
void _clear() {
_expression = '';
_result = '0';
_newExpression = true;
}
// Remove last character
void _backspace() {
if (_expression.isNotEmpty) {
_expression = _expression.substring(0, _expression.length - 1);
}
if (_expression.isEmpty) {
_result = '0';
_newExpression = true;
}
}
// Add digit to expression
void _addDigit(String digit) {
if (_newExpression) {
_expression = digit;
_newExpression = false;
} else {
_expression += digit;
}
_result = _expression;
}
// Add operator to expression
void _addOperator(String operator) {
if (_expression.isEmpty) return;
// Replace last operator if needed
final lastChar = _expression[_expression.length - 1];
if (_isOperator(lastChar)) {
_expression = _expression.substring(0, _expression.length - 1);
}
_expression += operator;
_newExpression = false;
_result = _expression;
}
// Add decimal point
void _addDecimal() {
if (_newExpression) {
_expression = '0.';
_newExpression = false;
} else {
// Check if current number already has a decimal
final parts = _expression.split(RegExp(r'[+\-×÷]'));
final currentNumber = parts.last;
if (!currentNumber.contains('.')) {
_expression += '.';
}
}
_result = _expression;
}
// Toggle positive/negative sign
void _toggleSign() {
if (_expression.isEmpty) return;
try {
// Evaluate current expression and toggle sign
final currentValue = _evaluateExpression(_expression);
final toggledValue = -currentValue;
_expression = toggledValue.toString();
_result = _expression;
_newExpression = false;
} catch (e) {
_result = 'Error';
}
}
// Convert to percentage
void _percentage() {
if (_expression.isEmpty) return;
try {
final currentValue = _evaluateExpression(_expression);
final percentageValue = currentValue / 100;
_expression = percentageValue.toString();
_result = _expression;
_newExpression = false;
} catch (e) {
_result = 'Error';
}
}
// Convert infix expression to Reverse Polish Notation using shunting-yard algorithm
List<String> _shuntingYard(List<String> tokens) {
final output = <String>[];
final operators = <String>[];
final precedence = {'+': 1, '-': 1, '×': 2, '÷': 2};
for (final token in tokens) {
if (double.tryParse(token) != null) {
// Number - add to output
output.add(token);
} else if (_isOperator(token)) {
// Operator - handle precedence
while (operators.isNotEmpty &&
precedence[operators.last]! >= precedence[token]!) {
output.add(operators.removeLast());
}
operators.add(token);
}
}
// Add remaining operators
while (operators.isNotEmpty) {
output.add(operators.removeLast());
}
return output;
}
// Evaluate RPN expression
double _evaluateRPN(List<String> rpn) {
final stack = <double>[];
for (final token in rpn) {
if (double.tryParse(token) != null) {
stack.add(double.parse(token));
} else {
final b = stack.removeLast();
final a = stack.removeLast();
switch (token) {
case '+':
stack.add(a + b);
break;
case '-':
stack.add(a - b);
break;
case '×':
stack.add(a * b);
break;
case '÷':
if (b == 0) throw Exception('Division by zero');
stack.add(a / b);
break;
}
}
}
return stack.last;
}
// Tokenize expression string
List<String> _tokenize(String expression) {
final tokens = <String>[];
final buffer = StringBuffer();
for (int i = 0; i < expression.length; i++) {
final char = expression[i];
if (_isOperator(char)) {
if (buffer.isNotEmpty) {
tokens.add(buffer.toString());
buffer.clear();
}
tokens.add(char);
} else {
buffer.write(char);
}
}
if (buffer.isNotEmpty) {
tokens.add(buffer.toString());
}
return tokens;
}
// Main evaluation function
double _evaluateExpression(String expression) {
if (expression.isEmpty) return 0;
try {
final tokens = _tokenize(expression);
final rpn = _shuntingYard(tokens);
return _evaluateRPN(rpn);
} catch (e) {
throw Exception('Invalid expression');
}
}
// Perform calculation
void _calculate() {
if (_expression.isEmpty) return;
try {
final result = _evaluateExpression(_expression);
_result = result.toString();
// Format result to avoid unnecessary decimal places
if (result % 1 == 0) {
_result = result.toInt().toString();
} else {
_result = result.toStringAsFixed(6).replaceAll(RegExp(r'0+$'), '').replaceAll(RegExp(r'\.$'), '');
}
_expression = _result;
_newExpression = true;
} catch (e) {
_result = 'Error';
_newExpression = true;
}
}
// Get button background color based on type
Color _getButtonColor(String button) {
if (button == '=') {
return Colors.blue;
} else if (_isOperator(button) || button == '%') {
return Colors.grey[800]!;
} else if (button == 'C' || button == '⌫') {
return Colors.grey[700]!;
} else {
return Colors.grey[900]!;
}
}
// Get button text color
Color _getTextColor(String button) {
if (button == 'C' || button == '⌫') {
return Colors.redAccent;
}
return Colors.white;
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
// Display area
Expanded(
flex: 1,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Expression display
SingleChildScrollView(
scrollDirection: Axis.horizontal,
reverse: true,
child: Text(
_expression.isEmpty ? '0' : _expression,
style: TextStyle(
fontSize: 24,
color: Colors.grey[400],
),
textAlign: TextAlign.end,
),
),
const SizedBox(height: 10),
// Result display
SingleChildScrollView(
scrollDirection: Axis.horizontal,
reverse: true,
child: Text(
_result,
style: const TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
color: Colors.white,
),
textAlign: TextAlign.end,
),
),
],
),
),
),
// Button grid
Expanded(
flex: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: _buttonRows.map((row) {
return Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: row.map((button) {
return Expanded(
child: Padding(
padding: const EdgeInsets.all(4.0),
child: ElevatedButton(
onPressed: () => _onButtonPressed(button),
style: ElevatedButton.styleFrom(
backgroundColor: _getButtonColor(button),
foregroundColor: _getTextColor(button),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
elevation: 2,
shadowColor: Colors.black38,
),
child: Text(
button,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w500,
),
),
),
),
);
}).toList(),
),
);
}).toList(),
),
),
),
],
),
),
);
}
}
Comments
Post a Comment