Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
Tags
- provider
- changenotifier
- Stream
- Swift
- transform
- changenotifierprovider
- setstate
- divider
- controller
- consumer
- borderRadius
- datetime
- snackbar
- Firebase
- platformexception
- globalkey
- Navigator
- multiprovider
- permission
- user
- reference
- swift 문법
- enum
- runTransaction
- 문법
- switch
- ListView.builder
- Camera
- Snapshot
- signout
Archives
- Today
- Total
코딩하는 제리
[Flutter/MiniProject] 간단한 가계부 앱 본문
소스코드 및 pubspec.yaml
// lib/models/transaction.dart
import 'package:flutter/foundation.dart';
class Transaction {
final String id;
final String title;
final double amount;
final DateTime date;
Transaction({
@required this.id,
@required this.title,
@required this.amount,
@required this.date,
});
}
// lib/main.dart
import 'package:flutter/material.dart';
import './widgets/chart.dart';
import './widgets/new_transaction.dart';
import './widgets/transaction_list.dart';
import './models/transaction.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Personal Expenses',
theme: ThemeData(
primarySwatch: Colors.purple,
accentColor: Colors.amber,
errorColor: Colors.red,
fontFamily: 'Quicksand',
textTheme: ThemeData.light().textTheme.copyWith(
headline6: TextStyle(
fontFamily: 'OpenSans',
fontSize: 18,
fontWeight: FontWeight.bold,
),
button: TextStyle(color: Colors.white),
),
appBarTheme: AppBarTheme(
textTheme: ThemeData.light().textTheme.copyWith(
headline6: TextStyle(
fontFamily: 'OpenSans',
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final List<Transaction> _userTransactions = [
// Transaction(
// id: 't1',
// title: 'New Shoes',
// amount: 66,
// date: DateTime.now(),
// ),
// Transaction(
// id: 't2',
// title: 'Weekly Groceries',
// amount: 50,
// date: DateTime.now(),
// ),
];
List<Transaction> get _recentTransactions {
return _userTransactions.where((tx) {
return tx.date.isAfter(
DateTime.now().subtract(
Duration(days: 7),
),
);
}).toList();
}
void _addNewTransaction(
String txTitle, double txAmount, DateTime chosenDate) {
final newTx = Transaction(
title: txTitle,
amount: txAmount,
date: chosenDate,
id: DateTime.now().toString(),
);
setState(() {
_userTransactions.add(newTx);
});
}
void _startAddNewTransaction(BuildContext context) {
showModalBottomSheet(
context: context,
builder: (_) {
return GestureDetector(
onTap: () {},
behavior: HitTestBehavior.opaque,
child: NewTransaction(_addNewTransaction),
);
},
);
}
void _deleteTransaction(String id) {
setState(() {
_userTransactions.removeWhere((tx) => tx.id == id);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Personal Expenses'),
actions: [
IconButton(
icon: Icon(Icons.add),
onPressed: () {
_startAddNewTransaction(context);
},
),
],
),
body: SingleChildScrollView(
child: Column(
children: [
Chart(_recentTransactions),
TransactionList(_userTransactions, _deleteTransaction),
],
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: FloatingActionButton(
onPressed: () {
_startAddNewTransaction(context);
},
child: Icon(Icons.add),
),
);
}
}
// lib/widgets/transaction_list.dart
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../models/transaction.dart';
class TransactionList extends StatelessWidget {
final List<Transaction> transactions;
final Function deleteTx;
TransactionList(this.transactions, this.deleteTx);
@override
Widget build(BuildContext context) {
return Container(
height: 450,
child: transactions.isEmpty
? Column(
children: [
Text(
'No transactions added yet!',
style: Theme.of(context).textTheme.headline6,
),
SizedBox(
height: 10,
),
Container(
height: 200,
child: Image.asset(
'assets/image/waiting.png',
fit: BoxFit.cover,
),
),
],
)
: ListView.builder(
itemCount: transactions.length,
itemBuilder: (context, index) {
return Card(
margin: EdgeInsets.symmetric(vertical: 8, horizontal: 5),
elevation: 5,
child: ListTile(
leading: CircleAvatar(
radius: 30,
child: Padding(
padding: const EdgeInsets.all(6.0),
child: FittedBox(
child: Text('\$${transactions[index].amount}'),
),
),
),
title: Text(
transactions[index].title,
style: Theme.of(context).textTheme.headline6,
),
subtitle: Text(
DateFormat.yMMMd().format(
transactions[index].date,
),
),
trailing: IconButton(
icon: Icon(Icons.delete),
color: Theme.of(context).errorColor,
onPressed: () => deleteTx(transactions[index].id),
),
),
);
},
),
);
}
}
// lib/widgets/new_transaction.dart
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class NewTransaction extends StatefulWidget {
final Function addTx;
NewTransaction(this.addTx);
@override
_NewTransactionState createState() => _NewTransactionState();
}
class _NewTransactionState extends State<NewTransaction> {
final _titleController = TextEditingController();
final _amountController = TextEditingController();
DateTime _selectedDate;
void _submitData() {
if (_amountController.text.isEmpty) {
return;
}
final enteredTitle = _titleController.text;
final enteredAmount = double.parse(_amountController.text);
if (enteredTitle.isEmpty || enteredAmount <= 0 || _selectedDate == null) {
return;
}
widget.addTx(
enteredTitle,
enteredAmount,
_selectedDate,
);
Navigator.of(context).pop();
}
void _presentDatePicker() {
showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2020),
lastDate: DateTime.now(),
).then((pickedDate) {
if (pickedDate == null) {
return;
}
setState(() {
_selectedDate = pickedDate;
});
});
}
@override
Widget build(BuildContext context) {
return Card(
elevation: 5,
child: Container(
padding: EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
TextField(
controller: _titleController,
onSubmitted: (_) => _submitData(),
decoration: InputDecoration(labelText: 'Title'),
),
TextField(
controller: _amountController,
keyboardType: TextInputType.number,
onSubmitted: (_) => _submitData(),
decoration: InputDecoration(labelText: 'Amount'),
),
Container(
height: 70,
child: Row(
children: [
Expanded(
child: Text(
_selectedDate == null
? 'No Date Chosen'
: 'Picked Date: ${DateFormat.yMd().format(_selectedDate)}',
// style: TextStyle(fontFamily: 'Quicksand'),
),
),
TextButton(
onPressed: _presentDatePicker,
child: Text(
'Choose Date',
style: TextStyle(
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
ElevatedButton(
onPressed: _submitData,
child: Text(
'Add Transaction',
style: TextStyle(
color: Theme.of(context).textTheme.button.color,
),
),
),
],
),
),
);
}
}
// lib/widgets/chart.dart
import 'package:intl/intl.dart';
import '../models/transaction.dart';
import '../widgets/chart_bar.dart';
import 'package:flutter/material.dart';
class Chart extends StatelessWidget {
final List<Transaction> recentTransactions;
Chart(this.recentTransactions);
List<Map<String, Object>> get groupedTransactionValues {
return List.generate(7, (index) {
final weekDay = DateTime.now().subtract(
Duration(days: index),
);
var totalSum = 0.0;
for (var i = 0; i < recentTransactions.length; i++) {
if (recentTransactions[i].date.day == weekDay.day &&
recentTransactions[i].date.month == weekDay.month &&
recentTransactions[i].date.year == weekDay.year) {
totalSum += recentTransactions[i].amount;
}
}
print(DateFormat.E().format(weekDay));
print(totalSum);
return {
'day': DateFormat.E().format(weekDay).substring(0, 1),
'amount': totalSum,
};
}).reversed.toList();
}
double get totalSpending {
return groupedTransactionValues.fold(0.0, (sum, item) {
return sum + item['amount'];
});
}
@override
Widget build(BuildContext context) {
print(groupedTransactionValues);
return Card(
elevation: 6,
margin: EdgeInsets.all(20),
child: Padding(
padding: const EdgeInsets.all(10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: groupedTransactionValues.map((data) {
return Flexible(
fit: FlexFit.tight,
child: ChartBar(
data['day'],
data['amount'],
totalSpending == 0.0
? 0.0
: (data['amount'] as double) / totalSpending,
),
);
}).toList(),
),
),
);
}
}
// lib/widgets/chart_bar.dart
import 'package:flutter/material.dart';
class ChartBar extends StatelessWidget {
final String label;
final double spendingAmount;
final double spendingPctOfTotal;
ChartBar(
this.label,
this.spendingAmount,
this.spendingPctOfTotal,
);
@override
Widget build(BuildContext context) {
return Column(
children: [
Container(
height: 20,
child: FittedBox(
child: Text('\$${spendingAmount.toStringAsFixed(0)}'),
),
),
SizedBox(height: 4),
Container(
height: 60,
width: 10,
child: Stack(
children: [
Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey, width: 1.0),
color: Color.fromRGBO(220, 220, 220, 1),
borderRadius: BorderRadius.circular(10),
),
),
FractionallySizedBox(
heightFactor: spendingPctOfTotal,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
borderRadius: BorderRadius.circular(10),
),
),
),
],
),
),
SizedBox(height: 4),
Text(label),
],
);
}
}
'Flutter > MiniProject' 카테고리의 다른 글
[Flutter/MiniProject] Provider를 이용한 영화 카테고리 앱 (0) | 2021.07.02 |
---|---|
[Flutter/MiniProject] 심플한 날씨앱 (0) | 2021.06.25 |
[Flutter/MiniProject] 퀴즈앱 UI (0) | 2021.03.23 |
[Flutter/MiniProject] 채팅앱 UI (0) | 2021.03.19 |
[Flutter/MiniProject] 간단한 채팅앱 UI (0) | 2021.03.17 |
Comments