Skip to content

Commit 076f9fe

Browse files
committed
expense_manager
1 parent 80fe9be commit 076f9fe

File tree

100 files changed

+9835
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

100 files changed

+9835
-0
lines changed

expense_manager/.gitignore

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Miscellaneous
2+
*.class
3+
*.log
4+
*.pyc
5+
*.swp
6+
.DS_Store
7+
.atom/
8+
.buildlog/
9+
.history
10+
.svn/
11+
migrate_working_dir/
12+
libisar.dylib
13+
tmp
14+
15+
# IntelliJ related
16+
*.iml
17+
*.ipr
18+
*.iws
19+
.idea/
20+
21+
# The .vscode folder contains launch configuration and tasks you configure in
22+
# VS Code which you may wish to be included in version control, so this line
23+
# is commented out by default.
24+
#.vscode/
25+
26+
# Flutter/Dart/Pub related
27+
**/doc/api/
28+
**/ios/Flutter/.last_build_id
29+
.dart_tool/
30+
.flutter-plugins
31+
.flutter-plugins-dependencies
32+
.packages
33+
.pub-cache/
34+
.pub/
35+
/build/
36+
37+
# Symbolication related
38+
app.*.symbols
39+
40+
# Obfuscation related
41+
app.*.map.json
42+
43+
# Android Studio will place build artifacts here
44+
/android/app/debug
45+
/android/app/profile
46+
/android/app/release

expense_manager/README.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Introduction
2+
3+
`expense_manager` is a simple yet functional personal expense manager app built with Flutter. It allows you to:
4+
5+
- Track your daily expense
6+
- Track your income
7+
- View your expenses and incomes
8+
- Group your expenses into categories
9+
- Tag your expenses easily!
10+
- A dashboard that shows the overall earnings
11+
- Beautiful report pages with charts

expense_manager/analysis_options.yaml

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# This file configures the analyzer, which statically analyzes Dart code to
2+
# check for errors, warnings, and lints.
3+
#
4+
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5+
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6+
# invoked from the command line by running `flutter analyze`.
7+
8+
# The following line activates a set of recommended lints for Flutter apps,
9+
# packages, and plugins designed to encourage good coding practices.
10+
include: package:flutter_lints/flutter.yaml
11+
12+
linter:
13+
# The lint rules applied to this project can be customized in the
14+
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
15+
# included above or to enable additional rules. A list of all available lints
16+
# and their documentation is published at
17+
# https://dart-lang.github.io/linter/lints/index.html.
18+
#
19+
# Instead of disabling a lint rule for the entire project in the
20+
# section below, it can also be suppressed for a single line of code
21+
# or a specific dart file by using the `// ignore: name_of_lint` and
22+
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
23+
# producing the lint.
24+
rules:
25+
# avoid_print: false # Uncomment to disable the `avoid_print` rule
26+
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
27+
file_names: false
28+
prefer_const_constructors: false
29+
prefer_const_literals_to_create_immutables: false
30+
31+
# Additional information about this file can be found at
32+
# https://dart.dev/guides/language/analysis-options
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import 'package:flutter/material.dart';
2+
3+
class ActionDeleteIcon extends StatelessWidget {
4+
final VoidCallback onTap;
5+
6+
const ActionDeleteIcon({
7+
super.key,
8+
required this.onTap,
9+
});
10+
11+
@override
12+
Widget build(BuildContext context) {
13+
return InkWell(
14+
onTap: onTap,
15+
child: Padding(
16+
padding: EdgeInsets.all(12),
17+
child: Icon(Icons.delete, color: Colors.white),
18+
),
19+
);
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import 'package:expense_manager/misc/extensions.dart';
2+
import 'package:expense_manager/components/dialog/custom_dialog.dart';
3+
import 'package:expense_manager/misc/utils.dart';
4+
import 'package:flutter/material.dart';
5+
import 'package:get/get.dart';
6+
7+
class AmountDialog extends StatelessWidget {
8+
final int amount;
9+
final ValueChanged<int> onAmountChanged;
10+
11+
const AmountDialog({
12+
super.key,
13+
this.amount = 0,
14+
required this.onAmountChanged,
15+
});
16+
17+
@override
18+
Widget build(BuildContext context) {
19+
return CustomDialog(
20+
maxWidth: 600,
21+
child: GetBuilder<_AmountDialogController>(
22+
init: _AmountDialogController(amount: amount),
23+
builder: (controller) {
24+
return Column(
25+
mainAxisSize: MainAxisSize.min,
26+
crossAxisAlignment: CrossAxisAlignment.start,
27+
children: [
28+
Padding(
29+
padding:
30+
EdgeInsets.symmetric(horizontal: 16).copyWith(top: 16),
31+
child: Text(
32+
controller.amount.toMoney(),
33+
style: Theme.of(context).textTheme.displaySmall,
34+
),
35+
),
36+
37+
// number pads
38+
GridView.count(
39+
crossAxisCount: 3,
40+
shrinkWrap: true,
41+
childAspectRatio: 2.3,
42+
children: controller.keys.map((numKey) {
43+
return InkWell(
44+
onTap: () {
45+
controller.calculateAmount(numKey);
46+
},
47+
child: Center(
48+
child: numKey == 'back'
49+
? Icon(Icons.backspace)
50+
: Text(numKey, style: TextStyle(fontSize: 21)),
51+
),
52+
);
53+
}).toList(),
54+
),
55+
56+
Padding(
57+
padding: EdgeInsets.all(8),
58+
child: Row(
59+
mainAxisAlignment: MainAxisAlignment.end,
60+
children: [
61+
TextButton(
62+
child: Text(
63+
'CANCEL',
64+
style: TextStyle(color: Colors.grey),
65+
),
66+
onPressed: () => goBack(context),
67+
),
68+
TextButton(
69+
child: Text('OK'),
70+
onPressed: () {
71+
onAmountChanged.call(controller.amount);
72+
goBack(context);
73+
},
74+
),
75+
],
76+
),
77+
),
78+
],
79+
);
80+
}),
81+
);
82+
}
83+
}
84+
85+
class _AmountDialogController extends GetxController {
86+
final List<String> keys = [
87+
'1',
88+
'2',
89+
'3',
90+
'4',
91+
'5',
92+
'6',
93+
'7',
94+
'8',
95+
'9',
96+
'',
97+
'0',
98+
'back'
99+
];
100+
int amount;
101+
102+
_AmountDialogController({required this.amount});
103+
104+
void calculateAmount(String numKey) {
105+
if (numKey.isEmpty) return;
106+
107+
switch (numKey) {
108+
case 'back':
109+
String amountText = amount.toString();
110+
amountText = amountText.length <= 1
111+
? '0'
112+
: amountText.substring(0, amountText.length - 1);
113+
amount = int.parse(amountText);
114+
break;
115+
default:
116+
final amountText = amount.toString() + numKey;
117+
amount = int.parse(amountText);
118+
}
119+
120+
update();
121+
}
122+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import 'package:expense_manager/misc/extensions.dart';
2+
import 'package:flutter/material.dart';
3+
import 'amount_dialog.dart';
4+
5+
class AmountInputView extends StatelessWidget {
6+
final int initialAmount;
7+
final ValueChanged<int> onChange;
8+
9+
const AmountInputView({
10+
super.key,
11+
this.initialAmount = 0,
12+
required this.onChange,
13+
});
14+
15+
@override
16+
Widget build(BuildContext context) {
17+
return InkWell(
18+
onTap: () => _showAmountDialog(context),
19+
child: Padding(
20+
padding: EdgeInsets.all(8),
21+
child: Align(
22+
alignment: Alignment.centerRight,
23+
child: Text(
24+
initialAmount.toMoney(),
25+
textAlign: TextAlign.right,
26+
style: Theme.of(context).textTheme.titleLarge,
27+
),
28+
),
29+
),
30+
);
31+
}
32+
33+
void _showAmountDialog(BuildContext context) {
34+
showDialog(
35+
context: context,
36+
builder: (context) => AmountDialog(
37+
amount: initialAmount,
38+
onAmountChanged: (int amount) {
39+
onChange.call(amount);
40+
},
41+
),
42+
);
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import 'package:flutter/material.dart';
2+
import '../../misc/colors.dart';
3+
4+
class CustomButton extends StatelessWidget {
5+
final String text;
6+
final VoidCallback? onPressed;
7+
final BorderRadius borderRadius;
8+
final Color color;
9+
final Color textColor;
10+
final Color borderColor;
11+
final Color? shadowColor;
12+
final Icon? leftIcon;
13+
final Icon? rightIcon;
14+
final EdgeInsetsGeometry? padding;
15+
final double elevation;
16+
final bool disabled;
17+
final bool loading;
18+
final double? height;
19+
final bool useCustomShape;
20+
21+
const CustomButton(
22+
this.text, {
23+
super.key,
24+
this.onPressed,
25+
this.disabled = false,
26+
this.borderRadius = const BorderRadius.all(Radius.circular(8.0)),
27+
this.color = CustomColors.primary,
28+
this.textColor = Colors.white,
29+
this.borderColor = Colors.transparent,
30+
this.leftIcon,
31+
this.rightIcon,
32+
this.padding,
33+
this.shadowColor,
34+
this.elevation = 0,
35+
this.loading = false,
36+
this.height,
37+
this.useCustomShape = true,
38+
});
39+
40+
@override
41+
Widget build(BuildContext context) {
42+
return SizedBox(
43+
height: height ?? 45,
44+
child: ElevatedButton(
45+
style: ElevatedButton.styleFrom(
46+
foregroundColor: textColor,
47+
backgroundColor: color,
48+
elevation: 2,
49+
shadowColor: shadowColor,
50+
shape: useCustomShape
51+
? RoundedRectangleBorder(
52+
side: BorderSide(
53+
color: borderColor,
54+
),
55+
borderRadius: borderRadius,
56+
)
57+
: null,
58+
),
59+
onPressed: disabled
60+
? null
61+
: () {
62+
if (loading) return;
63+
onPressed?.call();
64+
},
65+
child: Row(
66+
mainAxisAlignment: MainAxisAlignment.center,
67+
children: [
68+
if (leftIcon != null)
69+
Align(
70+
alignment: Alignment.centerLeft,
71+
child: leftIcon,
72+
),
73+
Align(
74+
alignment: Alignment.center,
75+
child: loading
76+
? _renderProgressBar()
77+
: Text(
78+
text,
79+
textAlign: TextAlign.center,
80+
style: const TextStyle(fontSize: 16.0),
81+
),
82+
),
83+
if (rightIcon != null)
84+
Align(
85+
alignment: Alignment.centerRight,
86+
child: rightIcon,
87+
)
88+
],
89+
),
90+
),
91+
);
92+
}
93+
94+
Widget _renderProgressBar() {
95+
return const SizedBox(
96+
width: 20,
97+
height: 20,
98+
child: CircularProgressIndicator(
99+
valueColor: AlwaysStoppedAnimation(Colors.white),
100+
strokeWidth: 2,
101+
),
102+
);
103+
}
104+
}

0 commit comments

Comments
 (0)