Post Detail
Post #98
Flutter Jour Quatre Part 3
Flutter Jour Quatre
Fin PDF 4 (Fini)
Le Fichier main.dart
1/// main.dart
2
3import 'package:depenses/widgets/transaction_list.dart';
4import 'package:flutter/material.dart';
5
6import './models/transaction.dart';
7import './widgets/chart.dart';
8import './widgets/new_transaction.dart';
9
10void main() {
11 runApp(MyApp());
12}
13
14class MyApp extends StatelessWidget {
15 @override
16 Widget build(BuildContext context) {
17 return MaterialApp(
18 theme: ThemeData(
19 primarySwatch: Colors.purple,
20 accentColor: Colors.amber,
21 errorColor: Colors.red,
22 fontFamily: 'Quicksand',
23 appBarTheme: AppBarTheme(
24 textTheme: ThemeData.light().textTheme.copyWith(
25 title: TextStyle(
26 fontFamily: 'OpenSans',
27 fontWeight: FontWeight.bold,
28 fontSize: 20,
29 ),
30 button: TextStyle(
31 color: Colors.white,
32 ),
33 ),
34 ),
35 ),
36 home: MyHomePage(),
37 );
38 }
39}
40
41class MyHomePage extends StatefulWidget {
42 @override
43 _MyHomePageState createState() => _MyHomePageState();
44}
45
46class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
47 final titleController = TextEditingController();
48 final amountController = TextEditingController();
49
50 bool _showChart = false;
51
52 List<Transaction> get _recentTransactions {
53 return _userTransactions.where((transaction) {
54 return transaction.date.isAfter(
55 DateTime.now().subtract(
56 Duration(days: 7),
57 ),
58 );
59 }).toList();
60 }
61
62 final List<Transaction> _userTransactions = [
63 Transaction(
64 id: 't1',
65 title: 'Chaussures',
66 amount: 169.99,
67 date: DateTime.now(),
68 ),
69 Transaction(
70 id: 't2',
71 title: 'Courses',
72 amount: 128.50,
73 date: DateTime.now(),
74 ),
75 ];
76
77 void _addNewTransaction(
78 String newTitle, double newAmount, DateTime chosenDate) {
79 final newTransaction = Transaction(
80 title: newTitle,
81 amount: newAmount,
82 date: chosenDate,
83 id: DateTime.now().toString());
84
85 setState(() {
86 _userTransactions.add(newTransaction);
87 });
88 }
89
90 void _deleteTransaction(String id) {
91 setState(() {
92 _userTransactions.removeWhere((transaction) => transaction.id == id);
93 });
94 }
95
96 void _startAddNewTransaction(BuildContext context) {
97 showModalBottomSheet(
98 context: context,
99 builder: (_) {
100 return NewTransaction(_addNewTransaction);
101 });
102 }
103
104 List<Widget> _buildPortraitContent(
105 MediaQueryData mediaQuery, AppBar appBar, Widget textListWidget) {
106 return [
107 Container(
108 height: (mediaQuery.size.height -
109 appBar.preferredSize.height -
110 mediaQuery.padding.top) *
111 0.3,
112 child: Chart(_recentTransactions),
113 ),
114 textListWidget
115 ];
116 }
117
118 List<Widget> _buildLandscapeContent(
119 MediaQueryData mediaQuery, AppBar appBar, Widget textListWidget) {
120 return [
121 Row(
122 mainAxisAlignment: MainAxisAlignment.center,
123 children: <Widget>[
124 Text(
125 'Show Chart',
126 style: Theme.of(context).textTheme.title,
127 ),
128 Switch.adaptive(
129 activeColor: Theme.of(context).accentColor,
130 value: _showChart,
131 onChanged: (value) {
132 setState(() {
133 _showChart = value;
134 });
135 },
136 ),
137 ],
138 ),
139 _showChart
140 ? Container(
141 height: (mediaQuery.size.height -
142 appBar.preferredSize.height -
143 mediaQuery.padding.top) *
144 0.7,
145 child: Chart(_recentTransactions),
146 )
147 : textListWidget
148 ];
149 }
150
151 @override
152 Widget build(BuildContext context) {
153 print('build() MyHomePageState');
154 final mediaQuery = MediaQuery.of(context);
155 final isLandscape = mediaQuery.orientation == Orientation.landscape;
156
157 final appBar = AppBar(
158 title: const Text('Personal Expenses'),
159 actions: <Widget>[
160 IconButton(
161 icon: const Icon(Icons.add),
162 onPressed: () => _startAddNewTransaction(context)),
163 ],
164 );
165
166 final textListWidget = Container(
167 height: (MediaQuery.of(context).size.height -
168 appBar.preferredSize.height -
169 MediaQuery.of(context).padding.top) *
170 0.7,
171 child: TransactionList(_userTransactions, _deleteTransaction),
172 );
173
174 return Scaffold(
175 appBar: appBar,
176 body: SingleChildScrollView(
177 child: Column(
178 mainAxisAlignment: MainAxisAlignment.start,
179 crossAxisAlignment: CrossAxisAlignment.stretch,
180 children: <Widget>[
181 if (isLandscape)
182 ..._buildLandscapeContent(mediaQuery, appBar, textListWidget),
183 if (!isLandscape)
184 ..._buildPortraitContent(mediaQuery, appBar, textListWidget),
185 ],
186 ),
187 ),
188 floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
189 floatingActionButton: FloatingActionButton(
190 child: Icon(
191 Icons.add,
192 ),
193 onPressed: () => _startAddNewTransaction(context),
194 ),
195 );
196 }
197
198 @override
199 void initState() {
200 WidgetsBinding.instance.addObserver(this);
201 super.initState();
202 }
203
204 @override
205 void dispose() {
206 WidgetsBinding.instance.removeObserver(this);
207 super.dispose();
208 }
209
210 @override
211 void didChangeAppLifecycleState(AppLifecycleState state) {
212 print(state);
213 }
214}
Le Fichier new_transaction.dart
1/// ./widgets/new_transaction.dart
2
3import 'package:flutter/material.dart';
4import 'package:intl/intl.dart';
5
6class NewTransaction extends StatefulWidget {
7 final Function addNewTransaction;
8
9 NewTransaction(this.addNewTransaction) {
10 print('Constructor NewTransaction Widget');
11 }
12
13 @override
14 _NewTransactionState createState() {
15 print('createState NewTransaction Widget');
16 return _NewTransactionState();
17 }
18}
19
20class _NewTransactionState extends State<NewTransaction> {
21 _NewTransactionState() {
22 print('Constructor NewTransaction State');
23 }
24
25 final _titleController = TextEditingController();
26 final _amountController = TextEditingController();
27
28 DateTime _selectedDate;
29
30 void submitData() {
31 if (_amountController.text.isEmpty) {
32 return;
33 }
34 final enteredTitle = _titleController.text;
35 final enteredAmount = double.parse(_amountController.text);
36 if (enteredTitle.isEmpty || enteredAmount <= 0 || _selectedDate == null) {
37 return;
38 }
39 widget.addNewTransaction(
40 enteredTitle,
41 enteredAmount,
42 _selectedDate,
43 );
44 Navigator.of(context).pop();
45 }
46
47 void _presentDatePicker() {
48 showDatePicker(
49 context: context,
50 initialDate: DateTime.now(),
51 firstDate: DateTime(DateTime.now().year),
52 lastDate: DateTime.now(),
53 ).then((pickedDate) {
54 if (pickedDate == null) {
55 return;
56 }
57 setState(() {
58 _selectedDate = pickedDate;
59 });
60 });
61 }
62
63 @override
64 void initState() {
65 print('initState()');
66 super.initState();
67 }
68
69 @override
70 void didUpdateWidget(NewTransaction oldWidget) {
71 print('didUpdateWidget()');
72 super.didUpdateWidget(oldWidget);
73 }
74
75 @override
76 void dispose() {
77 print('dispose()');
78 super.dispose();
79 }
80
81 @override
82 Widget build(BuildContext context) {
83 return SingleChildScrollView(
84 child: Card(
85 child: Container(
86 padding: EdgeInsets.only(
87 top: 10,
88 left: 10,
89 right: 10,
90 bottom: MediaQuery.of(context).viewInsets.bottom + 10,
91 ),
92 child: Column(
93 crossAxisAlignment: CrossAxisAlignment.end,
94 children: <Widget>[
95 TextField(
96 decoration: InputDecoration(
97 labelText: 'Titre',
98 ),
99 controller: _titleController,
100 onSubmitted: (_) => submitData(),
101 ),
102 TextField(
103 decoration: InputDecoration(
104 labelText: 'Montant',
105 ),
106 controller: _amountController,
107 keyboardType: TextInputType.number,
108 onSubmitted: (_) => submitData(),
109 ),
110 Container(
111 height: 70,
112 child: Row(
113 children: <Widget>[
114 Text(
115 _selectedDate == null
116 ? 'Aucune date choisie!'
117 : 'Date sélectionnée: ${DateFormat('d.M.y').format(_selectedDate)}',
118 ),
119 FlatButton(
120 textColor: Theme.of(context).primaryColor,
121 child: Text(
122 'Choisir une date',
123 style: TextStyle(
124 fontWeight: FontWeight.bold,
125 ),
126 ),
127 onPressed: _presentDatePicker,
128 ),
129 ],
130 ),
131 ),
132 RaisedButton(
133 child: Text(
134 'Add Transaction',
135 ),
136 color: Theme.of(context).primaryColor,
137 textColor: Theme.of(context).textTheme.button.color,
138 onPressed: submitData,
139 ),
140 ],
141 ),
142 ),
143 ),
144 );
145 }
146}
Le Fichier transaction_list.dart
1/// ./widgets/transaction_list.dart
2
3import 'package:flutter/material.dart';
4
5import './transaction_item.dart';
6import '../models/transaction.dart';
7
8class TransactionList extends StatelessWidget {
9 final List<Transaction> transactions;
10 final Function deleteTransaction;
11
12 TransactionList(this.transactions, this.deleteTransaction);
13
14 @override
15 Widget build(BuildContext context) {
16 print('build() TransactionList');
17
18 return ListView(
19 children: transactions
20 .map((tx) => TransactionItem(
21 key: ValueKey(tx.id),
22 transaction: tx,
23 deleteTransaction: deleteTransaction,
24 ))
25 .toList(),
26 );
27 }
28}
Le Fichier transaction_item.dart
1/// ./widgets/transaction_item.dart
2
3import 'dart:math';
4
5import 'package:flutter/material.dart';
6import 'package:intl/intl.dart';
7
8class TransactionItem extends StatefulWidget {
9 const TransactionItem({
10 Key key,
11 @required this.transaction,
12 @required this.deleteTransaction,
13 }) : super(key: key);
14
15 final transaction;
16 final Function deleteTransaction;
17
18 @override
19 _TransactionItemState createState() => _TransactionItemState();
20}
21
22class _TransactionItemState extends State<TransactionItem> {
23 Color _bgColor;
24 @override
25 void initState() {
26 const availableColors = [
27 Colors.red,
28 Colors.black,
29 Colors.blue,
30 Colors.purple,
31 ];
32 _bgColor = availableColors[Random().nextInt(4)];
33 super.initState();
34 }
35
36 @override
37 Widget build(BuildContext context) {
38 return Card(
39 elevation: 5,
40 margin: EdgeInsets.symmetric(
41 vertical: 8,
42 horizontal: 5,
43 ),
44 child: ListTile(
45 leading: CircleAvatar(
46 backgroundColor: _bgColor,
47 radius: 30,
48 child: Padding(
49 padding: EdgeInsets.all(6),
50 child: FittedBox(
51 child: Text(
52 '\$${widget.transaction.amount.toStringAsFixed(2)}',
53 ),
54 ),
55 ),
56 ),
57 title: Text(
58 widget.transaction.title,
59 style: Theme.of(context).textTheme.title,
60 ),
61 subtitle: Text(
62 DateFormat('d.M.y').format(widget.transaction.date),
63 ),
64 trailing: MediaQuery.of(context).size.width > 460
65 ? FlatButton.icon(
66 icon: const Icon(Icons.delete),
67 label: const Text('Delete'),
68 textColor: Theme.of(context).errorColor,
69 onPressed: () =>
70 widget.deleteTransaction(widget.transaction.id),
71 )
72 : IconButton(
73 icon: const Icon(Icons.delete),
74 color: Theme.of(context).errorColor,
75 onPressed: () =>
76 widget.deleteTransaction(widget.transaction.id),
77 ),
78 ),
79 );
80 }
81}
Le Fichier char.dart
1/// ./widgets/char.dart
2
3import 'package:flutter/material.dart';
4import 'package:intl/intl.dart';
5
6import './char_bar.dart';
7import '../models/transaction.dart';
8
9class Chart extends StatelessWidget {
10 final List<Transaction> recentTransactions;
11
12 Chart(this.recentTransactions) {
13 print('Constructor Chart');
14 }
15
16 List<Map<String, Object>> get groupedTransactionValues {
17 return List.generate(7, (index) {
18 final weekDay = DateTime.now().subtract(
19 Duration(days: index),
20 );
21
22 var totalSum = 0.0;
23 for (var i = 0; i < recentTransactions.length; i++) {
24 if (recentTransactions[i].date.day == weekDay.day &&
25 recentTransactions[i].date.month == weekDay.month &&
26 recentTransactions[i].date.year == weekDay.year) {
27 totalSum += recentTransactions[i].amount;
28 }
29 }
30
31 return {
32 'day': DateFormat.E().format(weekDay).substring(0, 1),
33 'amount': totalSum
34 };
35 }).reversed.toList();
36 }
37
38 double get totalSpending {
39 return groupedTransactionValues.fold(0.0, (sum, item) {
40 return sum + item['amount'];
41 });
42 }
43
44 @override
45 Widget build(BuildContext context) {
46 print('build() Chart');
47
48 return Card(
49 elevation: 6,
50 margin: EdgeInsets.all(20),
51 child: Padding(
52 padding: EdgeInsets.all(10),
53 child: Row(
54 children: groupedTransactionValues.map((data) {
55 return Flexible(
56 fit: FlexFit.tight,
57 child: ChartBar(
58 data['day'],
59 data['amount'],
60 (data['amount'] as double) / totalSpending,
61 ),
62 );
63 }).toList(),
64 ),
65 ),
66 );
67 }
68}
Le Fichier char_bar.dart
1/// ./widgets/char_bar.dart
2
3import 'package:flutter/material.dart';
4
5class ChartBar extends StatelessWidget {
6 final String label;
7 final double spendingAmount;
8 final double spendingPercentageOfTotal;
9
10 ChartBar(this.label, this.spendingAmount, this.spendingPercentageOfTotal);
11
12 @override
13 Widget build(BuildContext context) {
14 print('build() ChartBar');
15 return LayoutBuilder(builder: (ctx, constraints) {
16 return Column(
17 children: <Widget>[
18 Container(
19 height: constraints.maxHeight * 0.15,
20 child: FittedBox(
21 child: Text('\$${spendingAmount.toStringAsFixed(0)}'),
22 ),
23 ),
24 SizedBox(
25 height: constraints.maxHeight * 0.05,
26 ),
27 Container(
28 height: constraints.maxHeight * 0.6,
29 width: 10,
30 child: Stack(
31 children: <Widget>[
32 Container(
33 decoration: BoxDecoration(
34 border: Border.all(color: Colors.grey, width: 1.0),
35 color: Color.fromRGBO(220, 220, 220, 1),
36 borderRadius: BorderRadius.circular(10),
37 ),
38 ),
39 Container(
40 alignment: Alignment.bottomLeft,
41 child: FractionallySizedBox(
42 heightFactor: spendingPercentageOfTotal,
43 child: Container(
44 decoration: BoxDecoration(
45 color: Theme.of(context).primaryColor,
46 borderRadius: BorderRadius.circular(10),
47 ),
48 ),
49 ),
50 ),
51 ],
52 ),
53 ),
54 SizedBox(
55 height: constraints.maxHeight * 0.05,
56 ),
57 Container(
58 height: constraints.maxHeight * 0.15,
59 child: FittedBox(
60 child: Text(label),
61 ),
62 ),
63 ],
64 );
65 });
66 }
67}
No comments here yet.