diff --git a/CHANGELOG.md b/CHANGELOG.md index 7789c02a..7e575fd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Changelog -## Version 1.1.4 +## Version 1.2.1 + +- FEATURE: Payees are sorted by alphabetical order +- FEATURE: By default, the transactions of the most recently used account are used + +## Version 1.2.0 - BUGFIX: Fix bug where you couldn't delete last character when setting budgeted value for a subcategory - BUGFIX: Changing the name of a subcategory takes effect immediately diff --git a/lib/appState.dart b/lib/appState.dart index 23e9209c..f8d56464 100644 --- a/lib/appState.dart +++ b/lib/appState.dart @@ -25,6 +25,7 @@ class AppState extends ChangeNotifier { List _budgetValues; List _budgets; Queries queryContext; + Account _mostRecentAccount; double toBeBudgeted = 0; @@ -48,6 +49,7 @@ class AppState extends ChangeNotifier { UnmodifiableListView get transactions => UnmodifiableListView(_transactions); UnmodifiableListView get budgets => UnmodifiableListView(_budgets); UnmodifiableListView get goals => UnmodifiableListView(_goals); + Account get mostRecentAccount => _mostRecentAccount ?? _accounts[0]; AppState({@required Queries queryContext}) { this.queryContext = queryContext; @@ -67,9 +69,12 @@ class AppState extends ChangeNotifier { _budgetValues = await queryContext.getBudgetValues(); _goals = await queryContext.getGoals(); + _mostRecentAccount = await getMostRecentAccountUsed(); + currentBudgetDate = getDateFromMonthStart(DateTime.now()); currentBudget = _getBudgetByDate(currentBudgetDate); + await computeToBeBudgeted(); notifyListeners(); @@ -221,6 +226,8 @@ class AppState extends ChangeNotifier { ); _transactions.add(transaction); + setMostRecentAccountUsed(accountId); + if (transaction.subcatID == Constants.TO_BE_BUDGETED_ID_IN_MONEYTRANSACTION) { print("Is to be budgeted money transaction"); // Update balance of the account @@ -677,4 +684,14 @@ class AppState extends ChangeNotifier { DateTime storedMaxBudgetDate = await queryContext.getMaxBudgetDateConstant(); return getMonthDifference(currentMaxBudgetDate, storedMaxBudgetDate); } + + void setMostRecentAccountUsed(int accountId){ + queryContext.updateMostRecentAccountUsed(accountId); + _mostRecentAccount = accounts.singleWhere((account) => account.id == accountId, orElse: () => null); + } + + Future getMostRecentAccountUsed() async { + int accountId = await queryContext.getMostRecentAccountUsed(); + return accounts.singleWhere((account) => account.id == accountId, orElse: () => null); + } } diff --git a/lib/main.dart b/lib/main.dart index 14e81a90..efb053c1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,7 +10,6 @@ import 'package:your_budget/screens/addTransaction/addTransaction.dart'; import 'package:your_budget/screens/addTransaction/addTransactionState.dart'; import 'package:your_budget/screens/budget/budgetPage.dart'; -import 'package:your_budget/models/database_provider.dart'; import 'package:your_budget/screens/budget/budgetPageState.dart'; import 'package:your_budget/screens/deleteCategories/DeleteCategoriesState.dart'; import 'package:your_budget/screens/showTransactions/showTransactionsState.dart'; diff --git a/lib/models/SQLQueries.dart b/lib/models/SQLQueries.dart index 86316195..4cbb40f0 100644 --- a/lib/models/SQLQueries.dart +++ b/lib/models/SQLQueries.dart @@ -473,7 +473,7 @@ class SQLQueryClass implements Queries{ Future getStartingBudgetDateConstant() async { final sql = '''SELECT ${DatabaseConstants.CONSTANT_VALUE} FROM ${DatabaseConstants.constantsTable} - WHERE ${DatabaseConstants.CONSTANT_NAME} == 'STARTING_BUDGET_DATE';'''; + WHERE ${DatabaseConstants.CONSTANT_NAME} == '${DatabaseConstants.STARTING_BUDGET_DATE}';'''; final data = await database.rawQuery(sql); int startingBudgetDateMillisecondsSinceEpoch = int.parse(data[0]['value'].toString()); @@ -482,7 +482,7 @@ class SQLQueryClass implements Queries{ Future getMaxBudgetDateConstant() async { final sql = '''SELECT ${DatabaseConstants.CONSTANT_VALUE} FROM ${DatabaseConstants.constantsTable} - WHERE ${DatabaseConstants.CONSTANT_NAME} == 'MAX_BUDGET_DATE';'''; + WHERE ${DatabaseConstants.CONSTANT_NAME} == '${DatabaseConstants.MAX_BUDGET_DATE}';'''; final data = await database.rawQuery(sql); int maxBudgetDateMillisecondsSinceEpoch = int.parse(data[0]['value'].toString()); @@ -492,11 +492,37 @@ class SQLQueryClass implements Queries{ Future setMaxBudgetDateConstant(DateTime newMaxBudgetDate) async { final sql = '''UPDATE ${DatabaseConstants.constantsTable} SET ${DatabaseConstants.CONSTANT_VALUE} = ? - WHERE ${DatabaseConstants.CONSTANT_NAME} == 'MAX_BUDGET_DATE' + WHERE ${DatabaseConstants.CONSTANT_NAME} == '${DatabaseConstants.MAX_BUDGET_DATE}' ;'''; List params = [newMaxBudgetDate.millisecondsSinceEpoch]; final result = await database.rawUpdate(sql, params); DatabaseProvider.databaseLog('Update maxBudgetDate', sql, null, result, params); } + + @override + Future updateMostRecentAccountUsed(int accountId) async{ + // TODO: implement setMostRecentAccountUsed + + final sql = '''UPDATE ${DatabaseConstants.constantsTable} + SET ${DatabaseConstants.CONSTANT_VALUE} = ? + WHERE ${DatabaseConstants.CONSTANT_NAME} == '${DatabaseConstants.MOST_RECENT_ACCOUNT}' + ;'''; + + List params = [accountId.toString()]; + final result = await database.rawUpdate(sql, params); + DatabaseProvider.databaseLog('Update most recent account used', sql, null, result, params); + + } + + @override + Future getMostRecentAccountUsed() async { + final sql = '''SELECT ${DatabaseConstants.CONSTANT_VALUE} FROM ${DatabaseConstants.constantsTable} + WHERE ${DatabaseConstants.CONSTANT_NAME} == '${DatabaseConstants.MOST_RECENT_ACCOUNT}';'''; + + final data = await database.rawQuery(sql); + int accountId = int.parse(data[0]['value'].toString()); + print(accountId); + return accountId; + } } diff --git a/lib/models/constants.dart b/lib/models/constants.dart index 7e5be73e..642bac0b 100644 --- a/lib/models/constants.dart +++ b/lib/models/constants.dart @@ -88,4 +88,10 @@ class DatabaseConstants { static const String BUDGET_VALUE_OUTSIDE = 'budgetvalues_id'; static const String GOAL_ID_OUTSIDE = 'goal_id'; + + /// Constants + static const String STARTING_BUDGET_DATE = "STARTING_BUDGET_DATE"; + static const String MAX_BUDGET_DATE = "MAX_BUDGET_DATE"; + static const String MOST_RECENT_ACCOUNT = "MOST_RECENT_ACCOUNT"; + } diff --git a/lib/models/database_provider.dart b/lib/models/database_provider.dart index a463b1e0..fa1e4ff5 100644 --- a/lib/models/database_provider.dart +++ b/lib/models/database_provider.dart @@ -57,8 +57,9 @@ class DatabaseProvider { final path = await getDatabasePath('budgetDB'); db = await openDatabase( path, - version: 1, + version: 2, onCreate: _onCreate, + onUpgrade: _onUpgrade, ); return db; @@ -203,11 +204,27 @@ class DatabaseProvider { /// Create the starting budget date based on the first time the user uses the app String startingDateMillisecondsSinceEpoch = getDateYMD(DateTime.now()).millisecondsSinceEpoch.toString(); - db.rawInsert(CREATE_CONSTANT, ["STARTING_BUDGET_DATE", startingDateMillisecondsSinceEpoch]); + db.rawInsert(CREATE_CONSTANT, [DatabaseConstants.STARTING_BUDGET_DATE, startingDateMillisecondsSinceEpoch]); /// Create the maximum budget date based on current date + Constants.MAX_NB_MONTHS_AHEAD String maxBudgetDateMillisecondsSinceEpoch = getMaxBudgetDate().millisecondsSinceEpoch.toString(); - db.rawInsert(CREATE_CONSTANT, ["MAX_BUDGET_DATE", maxBudgetDateMillisecondsSinceEpoch]); + db.rawInsert(CREATE_CONSTANT, [DatabaseConstants.MAX_BUDGET_DATE, maxBudgetDateMillisecondsSinceEpoch]); + + ///Save account most recently used. + db.rawInsert(CREATE_CONSTANT, [DatabaseConstants.MOST_RECENT_ACCOUNT, "0"]); + + } + + FutureOr _onUpgrade(Database db, int oldVersion, int newVersion) { + const String CREATE_CONSTANT = '''INSERT INTO ${DatabaseConstants.constantsTable} + (${DatabaseConstants.CONSTANT_NAME}, ${DatabaseConstants.CONSTANT_VALUE}) + VALUES(?, ?);'''; + + if(oldVersion == 1){ + ///Save account most recently used. + db.rawInsert(CREATE_CONSTANT, [DatabaseConstants.MOST_RECENT_ACCOUNT, "0"]); + print("Upgrading from version 1 to version 2"); + } } } diff --git a/lib/models/queries.dart b/lib/models/queries.dart index 595dd30d..8cd86f27 100644 --- a/lib/models/queries.dart +++ b/lib/models/queries.dart @@ -142,4 +142,8 @@ abstract class Queries { Future setMaxBudgetDateConstant(DateTime newMaxBudgetDate); + Future updateMostRecentAccountUsed(int accountId); + + Future getMostRecentAccountUsed(); + } diff --git a/lib/screens/addTransaction/selectValue.dart b/lib/screens/addTransaction/selectValue.dart index 730692ed..56278b5d 100644 --- a/lib/screens/addTransaction/selectValue.dart +++ b/lib/screens/addTransaction/selectValue.dart @@ -132,6 +132,10 @@ class SelectValuePageState extends State { } else listEntries = widget.listEntries; + if (isPayee){ + listEntries.sort((a, b) => a.name.compareTo(b.name)); + } + return Scaffold( resizeToAvoidBottomInset: false, appBar: AppBar( diff --git a/lib/screens/budget/budgetPage.dart b/lib/screens/budget/budgetPage.dart index d1a82575..68fd4d18 100644 --- a/lib/screens/budget/budgetPage.dart +++ b/lib/screens/budget/budgetPage.dart @@ -1,14 +1,13 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:your_budget/appState.dart'; -import 'package:your_budget/models/categories.dart'; import 'package:your_budget/models/constants.dart'; import 'package:your_budget/screens/modifyCategories/ModifyCategories.dart'; import 'package:your_budget/screens/about/aboutScreen.dart'; import 'package:your_budget/screens/budget/budgetPageState.dart'; -import 'package:your_budget/screens/budget/components/MainCategoryRow.dart'; -import 'package:your_budget/screens/budget/components/SubCategoryRow.dart'; import 'package:your_budget/screens/budget/components/buttonDial.dart'; +import 'package:your_budget/screens/budget/components/toBeBudgeted.dart'; +import 'package:your_budget/screens/budget/components/dateButtons.dart'; +import 'package:your_budget/screens/budget/components/categoriesList.dart'; import 'package:provider/provider.dart'; class BudgetPage extends StatefulWidget { @@ -24,8 +23,7 @@ class _BudgetPageState extends State { bool showMenu = true; //TODO: Settings - void handleSettings() { - } + void handleSettings() {} void handlePopUpMenuButtonSelected(String selectedItem) async { if (selectedItem == "About") { @@ -35,12 +33,18 @@ class _BudgetPageState extends State { } void handleModifyCategories() { - Navigator.push(context, MaterialPageRoute(builder: (context) => ModifyCategories())); + Navigator.push( + context, MaterialPageRoute(builder: (context) => ModifyCategories())); } @override Widget build(BuildContext context) { BudgetPageState buttonDialState = Provider.of(context); + ButtonDial buttonDial = buttonDialState.showButtonDial + ? ButtonDial(MediaQuery.of(context).size.height * 0.3, + MediaQuery.of(context).size.width * 0.6) + : null; + print("Budget page build"); return Scaffold( appBar: AppBar( @@ -57,133 +61,25 @@ class _BudgetPageState extends State { icon: Icon(FontAwesomeIcons.checkSquare), onPressed: handleModifyCategories, ), - PopupMenuButton( - onSelected: handlePopUpMenuButtonSelected, - itemBuilder: (context) => [ - PopupMenuItem( - value: "About", - child: Text("About"), - ), - ], - ), + PopupMenuButton( + onSelected: handlePopUpMenuButtonSelected, + itemBuilder: (context) => [ + PopupMenuItem( + value: "About", + child: Text("About"), + ), + ], + ), ], ), ) ], ), body: Column(children: [ - _DateButtons(), // - _ToBeBudgeted(), - Expanded(child: _CategoriesList()), - - buttonDialState.showButtonDial - ? ButtonDial( - MediaQuery.of(context).size.height * 0.3, MediaQuery.of(context).size.width * 0.6) - : ButtonDial(0, 0), + DateButtons(), // + ToBeBudgeted(), + Expanded(child: CategoriesList()), + if (buttonDial != null) buttonDial ])); } } - -class _CategoriesList extends StatefulWidget { - @override - __CategoriesListState createState() => __CategoriesListState(); -} - -class __CategoriesListState extends State<_CategoriesList> { - final ScrollController _scrollController = ScrollController(); - - @override - void dispose() { - _scrollController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final AppState appState = Provider.of(context); - final List categories = appState.allCategories; - - if (categories.isEmpty) { - return Center( - child: CircularProgressIndicator(), - ); - } - return Scrollbar( - isAlwaysShown: true, - controller: _scrollController, - child: ListView.separated( - controller: _scrollController, - itemCount: categories.length, - separatorBuilder: (BuildContext context, int index) => - Divider(height: 1, color: Colors.black12), - itemBuilder: (context, index) { - var item = categories[index]; - return (item is MainCategory) ? MainCategoryRow(cat: item) : SubcategoryRow(subcat: item); - }, - ), - ); - } -} - -class _ToBeBudgeted extends StatelessWidget { - final TextStyle _textStyle = - TextStyle(color: Colors.black, fontWeight: FontWeight.bold, fontSize: 25.0); - - final TextStyle _positiveAmountTextStyle = - new TextStyle(color: Constants.GREEN_COLOR, fontSize: 32.0); - final TextStyle _negativeAmountTextStyle = - new TextStyle(color: Constants.RED_COLOR, fontSize: 32.0); - - @override - Widget build(BuildContext context) { - return Container( - height: 50, - child: Row( - children: [ - Expanded( - child: Text( - "To be budgeted", - style: _textStyle, - )), - Consumer( - builder: (context, appState, child) { - return Text( - appState.toBeBudgeted.toStringAsFixed(2) + " €" ?? "0.00" + " €", - style: appState.toBeBudgeted >= 0 - ? _positiveAmountTextStyle - : _negativeAmountTextStyle, - ); - }, - ) - ], - )); - } -} - -class _DateButtons extends StatelessWidget { - void handleButtonOnPressed(BuildContext context, AppState appState, bool increment) { - increment ? appState.incrementMonth() : appState.decrementMonth(); - BudgetPageState buttonDialState = Provider.of(context, listen: false); - buttonDialState.toggleButtonDial(-1); - } - - @override - Widget build(BuildContext context) { - return Consumer(builder: (_, appState, __) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - IconButton( - icon: Icon(Icons.arrow_back), - onPressed: () => handleButtonOnPressed(context, appState, false)), - Text("${appState.currentBudget.monthAsString} ${appState.currentBudget.year}", - style: TextStyle(fontSize: 20)), - IconButton( - icon: Icon(Icons.arrow_forward), - onPressed: () => handleButtonOnPressed(context, appState, true)) - ], - ); - }); - } -} diff --git a/lib/screens/budget/components/categoriesList.dart b/lib/screens/budget/components/categoriesList.dart new file mode 100644 index 00000000..1db9be7f --- /dev/null +++ b/lib/screens/budget/components/categoriesList.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:your_budget/appState.dart'; +import 'package:your_budget/models/categories.dart'; +import 'package:your_budget/screens/budget/components/MainCategoryRow.dart'; +import 'package:your_budget/screens/budget/components/SubCategoryRow.dart'; + +class CategoriesList extends StatefulWidget { + @override + _CategoriesListState createState() => _CategoriesListState(); +} + +class _CategoriesListState extends State { + ScrollController _scrollController; + + @override + void initState() { + super.initState(); + _scrollController = ScrollController(); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scrollbar( + isAlwaysShown: true, + controller: _scrollController, + child: SingleChildScrollView( + controller: _scrollController, + child: Consumer( + builder: (_, appState, __) => Column( + children: _buildList(appState), + ), + ), + ), + ); + } +} + +List _buildList(AppState appState) { + List categories = appState.allCategories; + List widgetList = []; + + Divider divider = Divider(height: 1, color: Colors.black12); + + for (final Category category in categories) { + var categoryWidget = (category is MainCategory) + ? MainCategoryRow(cat: category) + : SubcategoryRow(subcat: category); + widgetList.add(categoryWidget); + widgetList.add(divider); + } + + return widgetList; +} diff --git a/lib/screens/budget/components/dateButtons.dart b/lib/screens/budget/components/dateButtons.dart new file mode 100644 index 00000000..4a2cac7f --- /dev/null +++ b/lib/screens/budget/components/dateButtons.dart @@ -0,0 +1,34 @@ + + +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:your_budget/appState.dart'; +import 'package:your_budget/screens/budget/budgetPageState.dart'; + +class DateButtons extends StatelessWidget { + void handleButtonOnPressed(BuildContext context, AppState appState, bool increment) { + increment ? appState.incrementMonth() : appState.decrementMonth(); + BudgetPageState buttonDialState = Provider.of(context, listen: false); + buttonDialState.toggleButtonDial(-1); + } + + @override + Widget build(BuildContext context) { + return Consumer(builder: (_, appState, __) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + IconButton( + icon: Icon(Icons.arrow_back), + onPressed: () => handleButtonOnPressed(context, appState, false)), + Text("${appState.currentBudget.monthAsString} ${appState.currentBudget.year}", + style: TextStyle(fontSize: 20)), + IconButton( + icon: Icon(Icons.arrow_forward), + onPressed: () => handleButtonOnPressed(context, appState, true)) + ], + ); + }); + } +} diff --git a/lib/screens/budget/components/toBeBudgeted.dart b/lib/screens/budget/components/toBeBudgeted.dart new file mode 100644 index 00000000..2c10c148 --- /dev/null +++ b/lib/screens/budget/components/toBeBudgeted.dart @@ -0,0 +1,40 @@ + +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:your_budget/appState.dart'; +import 'package:your_budget/models/constants.dart'; + +class ToBeBudgeted extends StatelessWidget { + final TextStyle _textStyle = + TextStyle(color: Colors.black, fontWeight: FontWeight.bold, fontSize: 25.0); + + final TextStyle _positiveAmountTextStyle = + new TextStyle(color: Constants.GREEN_COLOR, fontSize: 32.0); + final TextStyle _negativeAmountTextStyle = + new TextStyle(color: Constants.RED_COLOR, fontSize: 32.0); + + @override + Widget build(BuildContext context) { + return Container( + height: 50, + child: Row( + children: [ + Expanded( + child: Text( + "To be budgeted", + style: _textStyle, + )), + Consumer( + builder: (context, appState, child) { + return Text( + appState.toBeBudgeted.toStringAsFixed(2) + " €" ?? "0.00" + " €", + style: appState.toBeBudgeted >= 0 + ? _positiveAmountTextStyle + : _negativeAmountTextStyle, + ); + }, + ) + ], + )); + } +} diff --git a/lib/screens/showTransactions/showTransactionsPage.dart b/lib/screens/showTransactions/showTransactionsPage.dart index efb9a519..25081334 100644 --- a/lib/screens/showTransactions/showTransactionsPage.dart +++ b/lib/screens/showTransactions/showTransactionsPage.dart @@ -30,7 +30,7 @@ class _ShowTransactionPageController extends State { void initState() { AppState appState = Provider.of(context, listen: false); if (appState.accounts.isNotEmpty) { - account = appState.accounts[0]; + account = appState.mostRecentAccount; } isEditable = false; super.initState(); diff --git a/pubspec.yaml b/pubspec.yaml index 97ba35c0..460c9aed 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.2.0+14 +version: 1.2.1+15 environment: sdk: ">=2.7.0 <3.0.0" diff --git a/version.txt b/version.txt index b25194fd..db008051 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.2.0+14 +1.2.1+15