Skip to content

Commit

Permalink
expense_manager
Browse files Browse the repository at this point in the history
  • Loading branch information
gusterwoei committed May 27, 2024
1 parent 80fe9be commit fb74a7a
Show file tree
Hide file tree
Showing 100 changed files with 9,844 additions and 0 deletions.
46 changes: 46 additions & 0 deletions expense_manager/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
libisar.dylib
tmp

# IntelliJ related
*.iml
*.ipr
*.iws
.idea/

# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/

# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/

# Symbolication related
app.*.symbols

# Obfuscation related
app.*.map.json

# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
11 changes: 11 additions & 0 deletions expense_manager/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Introduction

`expense_manager` is a simple yet functional personal expense manager app built with Flutter. It allows you to:

- Track your daily expense
- Track your income
- View your expenses and incomes
- Group your expenses into categories
- Tag your expenses easily!
- A dashboard that shows the overall earnings
- Beautiful report pages with charts
32 changes: 32 additions & 0 deletions expense_manager/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.

# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml

linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
file_names: false
prefer_const_constructors: false
prefer_const_literals_to_create_immutables: false

# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
21 changes: 21 additions & 0 deletions expense_manager/lib/components/action_delete_icon.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'package:flutter/material.dart';

class ActionDeleteIcon extends StatelessWidget {
final VoidCallback onTap;

const ActionDeleteIcon({
super.key,
required this.onTap,
});

@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
child: Padding(
padding: EdgeInsets.all(12),
child: Icon(Icons.delete, color: Colors.white),
),
);
}
}
122 changes: 122 additions & 0 deletions expense_manager/lib/components/amount_dialog.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import 'package:expense_manager/misc/extensions.dart';
import 'package:expense_manager/components/dialog/custom_dialog.dart';
import 'package:expense_manager/misc/utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';

class AmountDialog extends StatelessWidget {
final int amount;
final ValueChanged<int> onAmountChanged;

const AmountDialog({
super.key,
this.amount = 0,
required this.onAmountChanged,
});

@override
Widget build(BuildContext context) {
return CustomDialog(
maxWidth: 600,
child: GetBuilder<_AmountDialogController>(
init: _AmountDialogController(amount: amount),
builder: (controller) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding:
EdgeInsets.symmetric(horizontal: 16).copyWith(top: 16),
child: Text(
controller.amount.toMoney(),
style: Theme.of(context).textTheme.displaySmall,
),
),

// number pads
GridView.count(
crossAxisCount: 3,
shrinkWrap: true,
childAspectRatio: 2.3,
children: controller.keys.map((numKey) {
return InkWell(
onTap: () {
controller.calculateAmount(numKey);
},
child: Center(
child: numKey == 'back'
? Icon(Icons.backspace)
: Text(numKey, style: TextStyle(fontSize: 21)),
),
);
}).toList(),
),

Padding(
padding: EdgeInsets.all(8),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
child: Text(
'CANCEL',
style: TextStyle(color: Colors.grey),
),
onPressed: () => goBack(context),
),
TextButton(
child: Text('OK'),
onPressed: () {
onAmountChanged.call(controller.amount);
goBack(context);
},
),
],
),
),
],
);
}),
);
}
}

class _AmountDialogController extends GetxController {
final List<String> keys = [
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'',
'0',
'back'
];
int amount;

_AmountDialogController({required this.amount});

void calculateAmount(String numKey) {
if (numKey.isEmpty) return;

switch (numKey) {
case 'back':
String amountText = amount.toString();
amountText = amountText.length <= 1
? '0'
: amountText.substring(0, amountText.length - 1);
amount = int.parse(amountText);
break;
default:
final amountText = amount.toString() + numKey;
amount = int.parse(amountText);
}

update();
}
}
44 changes: 44 additions & 0 deletions expense_manager/lib/components/amount_input_view.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import 'package:expense_manager/misc/extensions.dart';
import 'package:flutter/material.dart';
import 'amount_dialog.dart';

class AmountInputView extends StatelessWidget {
final int initialAmount;
final ValueChanged<int> onChange;

const AmountInputView({
super.key,
this.initialAmount = 0,
required this.onChange,
});

@override
Widget build(BuildContext context) {
return InkWell(
onTap: () => _showAmountDialog(context),
child: Padding(
padding: EdgeInsets.all(8),
child: Align(
alignment: Alignment.centerRight,
child: Text(
initialAmount.toMoney(),
textAlign: TextAlign.right,
style: Theme.of(context).textTheme.titleLarge,
),
),
),
);
}

void _showAmountDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) => AmountDialog(
amount: initialAmount,
onAmountChanged: (int amount) {
onChange.call(amount);
},
),
);
}
}
104 changes: 104 additions & 0 deletions expense_manager/lib/components/button/custom_button.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import 'package:flutter/material.dart';
import '../../misc/colors.dart';

class CustomButton extends StatelessWidget {
final String text;
final VoidCallback? onPressed;
final BorderRadius borderRadius;
final Color color;
final Color textColor;
final Color borderColor;
final Color? shadowColor;
final Icon? leftIcon;
final Icon? rightIcon;
final EdgeInsetsGeometry? padding;
final double elevation;
final bool disabled;
final bool loading;
final double? height;
final bool useCustomShape;

const CustomButton(
this.text, {
super.key,
this.onPressed,
this.disabled = false,
this.borderRadius = const BorderRadius.all(Radius.circular(8.0)),
this.color = CustomColors.primary,
this.textColor = Colors.white,
this.borderColor = Colors.transparent,
this.leftIcon,
this.rightIcon,
this.padding,
this.shadowColor,
this.elevation = 0,
this.loading = false,
this.height,
this.useCustomShape = true,
});

@override
Widget build(BuildContext context) {
return SizedBox(
height: height ?? 45,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
foregroundColor: textColor,
backgroundColor: color,
elevation: 2,
shadowColor: shadowColor,
shape: useCustomShape
? RoundedRectangleBorder(
side: BorderSide(
color: borderColor,
),
borderRadius: borderRadius,
)
: null,
),
onPressed: disabled
? null
: () {
if (loading) return;
onPressed?.call();
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (leftIcon != null)
Align(
alignment: Alignment.centerLeft,
child: leftIcon,
),
Align(
alignment: Alignment.center,
child: loading
? _renderProgressBar()
: Text(
text,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 16.0),
),
),
if (rightIcon != null)
Align(
alignment: Alignment.centerRight,
child: rightIcon,
)
],
),
),
);
}

Widget _renderProgressBar() {
return const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation(Colors.white),
strokeWidth: 2,
),
);
}
}
Loading

0 comments on commit fb74a7a

Please sign in to comment.