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}