From 62c3b4dcb19f44c039a4d0c9f6bea21f66096452 Mon Sep 17 00:00:00 2001 From: Koushik Naskar Date: Mon, 15 Nov 2021 01:09:15 +0530 Subject: [PATCH] =?UTF-8?q?bug=20fixes,=20month=20selctor,=20export=20as?= =?UTF-8?q?=20file=20=F0=9F=98=8E=F0=9F=A4=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 +-- lib/pages/addUser_page.dart | 5 +- lib/pages/daily_page.dart | 10 +-- lib/pages/newentry_page.dart | 1 + lib/pages/profile_page.dart | 115 ++++++++++++++++------------- lib/pages/root_app.dart | 5 ++ lib/pages/stats_page.dart | 98 ++++++++++++++++-------- lib/scoped_model/expenseScope.dart | 93 ++++++++++++++--------- lib/theme/colors.dart | 5 -- 9 files changed, 206 insertions(+), 135 deletions(-) diff --git a/README.md b/README.md index 234917e..c5578b5 100644 --- a/README.md +++ b/README.md @@ -56,15 +56,14 @@ Built with [Flutter](https://flutter.dev/) framework, this app can be compiled f 11. - [x] Import/Export data (JSON ?) also as an Excel sheet? 12. - [x] Add custom icon for production app. 13. - [x] App signing for distribution. -14. - [ ] Sort the data according to time at modification. +14. - [x] Sort the data according to time at modification. 13. - [x] Mocks for presentation 13. - [x] First sharable production build. 14. - [ ] Introduce search functionality to the log page. -15. - [ ] Separate data into months and a provide a option to set it. +15. - [x] Separate data into months and a provide a option to set it. 15. - [ ] Firebase authentication to sync between multiple devices. -* - [ ] Bring the share page in the main entry page. -* - [ ] Add new entry without leaving the page. -* - [ ] Share image white background with top bar +2. - [ ] Add new entry without leaving the page. +1. - [x] Share image white background with top bar diff --git a/lib/pages/addUser_page.dart b/lib/pages/addUser_page.dart index e2fd71d..f63baa7 100644 --- a/lib/pages/addUser_page.dart +++ b/lib/pages/addUser_page.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:shared_expenses/theme/colors.dart'; import 'package:shared_expenses/scoped_model/expenseScope.dart'; -import 'package:shared_expenses/pages/newentry_page.dart'; -import 'package:animations/animations.dart'; import 'package:scoped_model/scoped_model.dart'; + +// add an option to remove or edit users class AddUserCat extends StatefulWidget { final BuildContext context; final int type; diff --git a/lib/pages/daily_page.dart b/lib/pages/daily_page.dart index 29234a7..5e2bd4f 100644 --- a/lib/pages/daily_page.dart +++ b/lib/pages/daily_page.dart @@ -15,19 +15,11 @@ class DailyPage extends StatelessWidget { List> _expenses = model.getExpenses; return Scaffold( - // backgroundColor: grey.withOpacity(0.05), body: Column( children: [ Container( decoration: BoxDecoration( - // color: white, - // boxShadow: [ - // BoxShadow( - // color: grey.withOpacity(0.01), - // spreadRadius: 10, - // blurRadius: 3, - // ), - // ], + gradient: LinearGradient( colors: myColors[0], begin: Alignment.bottomCenter, diff --git a/lib/pages/newentry_page.dart b/lib/pages/newentry_page.dart index 2717911..a4a9a6c 100644 --- a/lib/pages/newentry_page.dart +++ b/lib/pages/newentry_page.dart @@ -182,6 +182,7 @@ class _NewEntryLogState extends State { ), SizedBox(height: 15), DateTimePicker( + controller: _dateEditor, type: DateTimePickerType.date, dateMask: 'd MMM, yyyy', diff --git a/lib/pages/profile_page.dart b/lib/pages/profile_page.dart index 678e6d0..d62d6f0 100644 --- a/lib/pages/profile_page.dart +++ b/lib/pages/profile_page.dart @@ -12,6 +12,7 @@ import 'package:shared_expenses/pages/addUser_page.dart'; import 'package:intl/intl.dart'; import 'dart:convert'; import 'package:animations/animations.dart'; +import 'package:share/share.dart'; class ProfilePage extends StatefulWidget { final ExpenseModel model; @@ -94,7 +95,7 @@ class _ProfilePageState extends State { ), ), Text( - "v 0.1.0", + "v 0.1.1", style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold, @@ -184,25 +185,63 @@ class _ProfilePageState extends State { height: 15, ), SizedBox( - height: 21, + height: 13, ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - IconButton( - iconSize: 35, - tooltip: "Download the data", - icon: Icon(FlutterIcons.download_faw5s), - onPressed: showChangeDialog, - ), - IconButton( - iconSize: 35, - tooltip: "Upload the data", - icon: Icon(FlutterIcons.upload_faw5s), - onPressed: saveUserData, - ) - ], + InkWell( + onTap: exportData, + child: Row( + children: [ + Icon(FlutterIcons.file_export_faw5s), + SizedBox(width: 10), + Text( + "Export Data", + style: TextStyle( + fontSize: 21, + ), + ) + ], + ), + ), + Divider( + indent: 30, + thickness: 1.0, + height: 15, + ), + SizedBox( + height: 13, + ), + InkWell( + onTap: importData, + child: Row( + children: [ + Icon(FlutterIcons.file_import_faw5s), + SizedBox(width: 10), + Text( + "Import Data", + style: TextStyle( + fontSize: 21, + ), + ) + ], + ), ), + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceEvenly, + // children: [ + // IconButton( + // iconSize: 35, + // tooltip: "Download the data", + // icon: Icon(FlutterIcons.download_faw5s), + // onPressed: importData, + // ), + // IconButton( + // iconSize: 35, + // tooltip: "Upload the data", + // icon: Icon(FlutterIcons.upload_faw5s), + // onPressed: exportData, + // ) + // ], + // ), ], ), ), @@ -370,7 +409,7 @@ class _ProfilePageState extends State { ); } - void showChangeDialog() { + void importData() { showDialog( context: context, barrierDismissible: false, // user must tap button! @@ -404,34 +443,6 @@ class _ProfilePageState extends State { ); } - // void showWarningDialog(String param) { - // showDialog( - // context: context, - // barrierDismissible: false, // user must tap button! - // builder: (BuildContext context) { - // return AlertDialog( - // title: Text('Alert !'), - // content: SingleChildScrollView( - // child: ListBody( - // children: [ - // Text('Can not modify existing $param'), - // ], - // ), - // ), - // actions: [ - // TextButton( - // child: Text('OK'), - // onPressed: () { - // widget.model.resetAll(); - // Navigator.of(context).pop(); - // }, - // ), - // ], - // ); - // }, - // ); - // } - void loadUserData() async { FilePickerResult result = await FilePicker.platform.pickFiles(); if (result == null) { @@ -450,7 +461,7 @@ class _ProfilePageState extends State { widget.model.newDataLoaded(_uList, _cList, _exList); } - void saveUserData() async { + void exportData() async { // await Permission.storage.request(); // var status = await Permission.storage.request(); @@ -461,14 +472,16 @@ class _ProfilePageState extends State { // it returns permission denied, so the file is saved in a given storage only for now. final directory = await getExternalStorageDirectory(); String fileName = "${directory.path}/Expenses_$timeStamp.txt"; + File file = File(fileName); - File(fileName).writeAsString(json.encode({ + file.writeAsString(json.encode({ "users": widget.model.getUsers, "categories": widget.model.getCategories, "expenses": widget.model.getExpenses })); - final snackBar = SnackBar(content: Text('File Saved')); - ScaffoldMessenger.of(context).showSnackBar(snackBar); + await Share.shareFiles([file.path]); + // final snackBar = SnackBar(content: Text('File Saved')); + // ScaffoldMessenger.of(context).showSnackBar(snackBar); } } diff --git a/lib/pages/root_app.dart b/lib/pages/root_app.dart index 4e0b081..45968ae 100644 --- a/lib/pages/root_app.dart +++ b/lib/pages/root_app.dart @@ -94,6 +94,11 @@ class _RootAppState extends State { setState(() { index ??= 2; pageIndex = index; + controller.animateToPage( + pageIndex, + duration: const Duration(milliseconds: 400), + curve: Curves.easeInOut, + ); }); } } diff --git a/lib/pages/stats_page.dart b/lib/pages/stats_page.dart index 204dd5d..0009183 100644 --- a/lib/pages/stats_page.dart +++ b/lib/pages/stats_page.dart @@ -6,6 +6,7 @@ import 'package:screenshot/screenshot.dart'; import 'package:share/share.dart'; import 'dart:io'; import 'package:path_provider/path_provider.dart'; +import 'package:select_form_field/select_form_field.dart'; class StatsPage extends StatefulWidget { final ExpenseModel model; @@ -38,6 +39,22 @@ class _StatsPageState extends State { } Widget getBody() { + Map months = { + "1": "January", + "2": "February", + "3": "March", + "4": "April", + "5": "May", + "6": "June", + "7": "July", + "8": "August", + "9": "September", + "10": "October", + "11": "November", + "12": "December", + "13": "All" + }; + return Column( children: [ Container( @@ -90,48 +107,71 @@ class _StatsPageState extends State { ), ), ), - Expanded( - child: SingleChildScrollView( - child: Screenshot( - controller: _screenShotController, - child: Container( - color: Colors.white, - child: Column( - children: [ - (widget.model.getUsers.length == 0 || widget.model.getCategories.length == 0) - ? Column( + (widget.model.getUsers.length == 0 || widget.model.getCategories.length == 0) + ? Column( + children: [ + SizedBox( + height: 30, + ), + Text( + widget.model.getUsers.length == 0 ? "No users added" : "No categories added", + style: TextStyle(fontSize: 21), + ), + TextButton( + onPressed: () { + widget.callback(2); + }, + child: Text("Go to settings")) + ], + ) + : Expanded( + child: SingleChildScrollView( + child: Screenshot( + controller: _screenShotController, + child: Container( + color: Colors.white, + child: Column( + children: [ + Column( children: [ - SizedBox( - height: 30, - ), - Text( - "No users added", - style: TextStyle(fontSize: 21), + Padding( + padding: EdgeInsets.symmetric(horizontal: 15, vertical: 5), + child: SelectFormField( + initialValue: widget.model.getCurrentMonth, + labelText: 'Select Month', + style: TextStyle( + fontSize: 19, + fontWeight: FontWeight.bold, + ), + items: [ + for (MapEntry e in months.entries) {'value': e.key, 'label': e.value} + ], + validator: (value) => value.isEmpty ? "Required filed *" : null, + onChanged: (v) => _updateMonth(v), + ), ), - TextButton( - onPressed: () { - widget.callback(2); - }, - child: Text("Go to settings")) - ], - ) - : Column( - children: [ makeStatCrad("Total Spends", Colors.pink, MaterialCommunityIcons.shopping), makeStatCrad("Total Owe", Colors.green, MaterialIcons.account_balance), makeStatCrad("Net Owe", Colors.purple, MaterialIcons.payment), ], ) - ], + ], + ), + ), + ), ), ), - ), - ), - ), ], ); } + void _updateMonth(String v) { + widget.model.setCurrentMonth(v); + setState(() { + expenseShares = widget.model.calculateShares(); + }); + } + void _takeScreenShot() async { final imageFile = await _screenShotController.capture(); String tempPath = (await getTemporaryDirectory()).path; diff --git a/lib/scoped_model/expenseScope.dart b/lib/scoped_model/expenseScope.dart index cc5f081..1cffb80 100644 --- a/lib/scoped_model/expenseScope.dart +++ b/lib/scoped_model/expenseScope.dart @@ -2,6 +2,7 @@ import 'package:scoped_model/scoped_model.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'dart:convert'; import 'package:flutter/foundation.dart'; +import 'package:intl/intl.dart'; class ExpenseModel extends Model { ExpenseModel() { @@ -9,28 +10,27 @@ class ExpenseModel extends Model { } // convert this to a object approach. List _categories = []; - List _users = []; - - // Map> _expenseStats = {}; - List> _expenses = []; + // for now, only month is added, no year, so a particular month for several years will be considered same + String _currentMonth = '1'; // Future mySharedPref = SharedPreferences.getInstance(); + List> get getExpenses => _expenses; List get getCategories => _categories; List get getUsers => _users; - // Map> get getexpenseStats => _expenseStats; + String get getCurrentMonth => _currentMonth; void setUsers(List userList) { _users = userList; - upDateUserData(true, false, false); + upDateUserData(true, false, false, false); notifyListeners(); } void setCategories(List categoryList) { _categories = categoryList; - upDateUserData(false, true, false); + upDateUserData(false, true, false, false); notifyListeners(); } @@ -43,31 +43,44 @@ class ExpenseModel extends Model { void addExpense(Map newExpenseEntry) { _expenses.insert(0, newExpenseEntry); - upDateUserData(false, false, true); + sortExpenses(); + upDateUserData(false, false, true, false); notifyListeners(); } void deleteExpense(int index) { _expenses.removeAt(index); - upDateUserData(false, false, true); + sortExpenses(); + upDateUserData(false, false, true, false); notifyListeners(); } void editExpense(int index, Map updatedExpenseEntry) { _expenses[index] = updatedExpenseEntry; - upDateUserData(false, false, true); + sortExpenses(); + + upDateUserData(false, false, true, false); + notifyListeners(); + } + + void setCurrentMonth(String cMonth) { + _currentMonth = cMonth; + upDateUserData(false, false, false, true); + calculateShares(); notifyListeners(); } void setInitValues() { if (!kReleaseMode) { // in case of debug mode use test data. + testData(); return; } SharedPreferences.getInstance().then((prefs) { _users = prefs.getStringList('users') ?? []; _categories = prefs.getStringList('categories') ?? []; + _currentMonth = prefs.getString('currentMonth') ?? '1'; if (_users.length != 0 && _categories.length != 0) { _expenses = (json.decode(prefs.getString('expenses')) as Iterable).map((e) => Map.from(e))?.toList(); @@ -78,11 +91,12 @@ class ExpenseModel extends Model { }); } - void upDateUserData(bool u, bool c, bool e) async { + void upDateUserData(bool u, bool c, bool e, bool d) async { SharedPreferences.getInstance().then((prefs) => { if (e) prefs.setString('expenses', json.encode(_expenses)), if (u) prefs.setStringList('users', _users), - if (c) prefs.setStringList('categories', _categories) + if (c) prefs.setStringList('categories', _categories), + if (d) prefs.setString('currentMonth', _currentMonth) }); } @@ -90,7 +104,7 @@ class ExpenseModel extends Model { _users = uList; _categories = cList; _expenses = exList; - upDateUserData(true, true, true); + upDateUserData(true, true, true, false); notifyListeners(); } @@ -101,23 +115,36 @@ class ExpenseModel extends Model { "Net Owe": {for (var v in _users) v: 0} }; - _expenses.forEach( - (entry) { - double amount = double.parse(entry["amount"]); - tmpStats["Total Spends"][entry["person"]] += amount; - for (MapEntry val in entry["shareBy"].entries) { - // do it with map.foreach - tmpStats["Total Owe"][val.key] += double.parse(val.value); - tmpStats["Net Owe"][val.key] += double.parse(val.value); - } - }, - ); - _users.forEach((u) { + for (Map entry in _expenses) { + // get the current month + String month = int.parse(entry['date'].split('-')[1]).toString(); + + if (_currentMonth != '13' && _currentMonth != month) { + continue; + } + double amount = double.parse(entry["amount"]); + tmpStats["Total Spends"][entry["person"]] += amount; + for (MapEntry val in entry["shareBy"].entries) { + tmpStats["Total Owe"][val.key] += double.parse(val.value); + tmpStats["Net Owe"][val.key] += double.parse(val.value); + } + } + + for (String u in _users) { tmpStats["Net Owe"][u] = tmpStats["Total Owe"][u] - tmpStats["Total Spends"][u]; - }); + } + return tmpStats; } + void sortExpenses() { + _expenses + .sort((a, b) => DateFormat("dd-MM-yyyy").parse(a['date']).compareTo(DateFormat("dd-MM-yyyy").parse(b['date']))); + for (var v in _expenses) { + print(v); + } + } + testData() { // used for debug _categories = ["Bills", "Food", "Misc"]; @@ -165,7 +192,7 @@ class ExpenseModel extends Model { "shareBy": {"Sam": "22", "Will": "22", "John": "22"} }, { - "date": "02-03-2021", + "date": "02-02-2021", "person": "John", "item": "Rent", "category": "Bills", @@ -173,7 +200,7 @@ class ExpenseModel extends Model { "shareBy": {"Sam": "22", "Will": "22", "John": "22"} }, { - "date": "02-03-2021", + "date": "02-02-2021", "person": "John", "item": "Rent", "category": "Bills", @@ -181,7 +208,7 @@ class ExpenseModel extends Model { "shareBy": {"Sam": "22", "Will": "22", "John": "22"} }, { - "date": "02-03-2021", + "date": "02-02-2021", "person": "John", "item": "Rent", "category": "Bills", @@ -189,7 +216,7 @@ class ExpenseModel extends Model { "shareBy": {"Sam": "22", "Will": "22", "John": "22"} }, { - "date": "02-03-2021", + "date": "02-02-2021", "person": "John", "item": "Rent", "category": "Bills", @@ -197,7 +224,7 @@ class ExpenseModel extends Model { "shareBy": {"Sam": "22", "Will": "22", "John": "22"} }, { - "date": "02-03-2021", + "date": "02-01-2021", "person": "John", "item": "Rent", "category": "Bills", @@ -205,7 +232,7 @@ class ExpenseModel extends Model { "shareBy": {"Sam": "22", "Will": "22", "John": "22"} }, { - "date": "02-03-2021", + "date": "02-01-2021", "person": "John", "item": "Rent", "category": "Bills", @@ -213,7 +240,7 @@ class ExpenseModel extends Model { "shareBy": {"Sam": "22", "Will": "22", "John": "22"} }, { - "date": "02-03-2021", + "date": "02-01-2021", "person": "John", "item": "Rent", "category": "Bills", diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index c01e4a6..c2eb560 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -16,8 +16,3 @@ List> myColors = [ [Color(0xffDA0959), Color(0xffF47DCB)] ]; -// Color(0xff2b32b2), Color(0xff1488cc) -// secondary, Color(0xfff953c6) -// Color(0xff016D18), Color(0xff93bb3c) -//Color(0xffDA0959), Color(0xffF47DCB) -//Color(0xff008F7A), Color(0xffAA7BF8)