Start migration to sqlite using drift

This commit is contained in:
2025-05-17 23:51:51 +02:00
parent 4d267eff88
commit 5dc474407c
50 changed files with 9549 additions and 6972 deletions

View File

@@ -1,11 +1,9 @@
import 'package:drift/drift.dart' show Value;
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/collections/account.dart';
import 'package:okane/database/collections/beneficiary.dart';
import 'package:okane/database/database.dart';
import 'package:okane/database/sqlite.dart';
import 'package:okane/ui/pages/account/breakdown_card.dart';
import 'package:okane/ui/pages/account/delete_account.dart';
import 'package:okane/ui/pages/account/loan_card.dart';
import 'package:okane/ui/pages/account/total_balance_card.dart';
import 'package:okane/ui/pages/account/upcoming_transactions_card.dart';
@@ -27,7 +25,6 @@ class AccountListPageState extends State<AccountListPage> {
@override
Widget build(BuildContext context) {
final bloc = GetIt.I.get<CoreCubit>();
return Stack(
children: [
ListView(
@@ -66,9 +63,12 @@ class AccountListPageState extends State<AccountListPage> {
children: [
Text(state.accounts[index].name!),
FutureBuilder(
future: getTotalBalance(
state.accounts[index],
),
future: GetIt.I
.get<OkaneDatabase>()
.transactionsDao
.getTotalBalance([
state.accounts[index].id,
]),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Container();
@@ -141,16 +141,21 @@ class AccountListPageState extends State<AccountListPage> {
onPressed: () async {
if (_accountNameController.text.isEmpty) return;
final a =
Account()..name = _accountNameController.text;
final b =
Beneficiary()
..name = _accountNameController.text
..account.value =
GetIt.I.get<CoreCubit>().activeAccount
..type = BeneficiaryType.account;
await upsertAccount(a);
await upsertBeneficiary(b);
final db = GetIt.I.get<OkaneDatabase>();
print("Adding account");
final accountId = await db.accountsDao
.upsertAccount(
AccountsCompanion(
name: Value(_accountNameController.text),
),
);
print("Adding beneficiary");
final b = BeneficiariesCompanion(
name: Value(_accountNameController.text),
accountId: Value(accountId),
type: Value(BeneficiaryType.account),
);
await db.beneficiariesDao.upsertBeneficiary(b);
_accountNameController.text = "";
Navigator.of(context).pop();
},

View File

@@ -2,7 +2,7 @@ import 'dart:collection';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/database.dart';
import 'package:okane/database/sqlite.dart';
import 'package:okane/i18n/strings.g.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/utils.dart';
@@ -26,21 +26,27 @@ class AccountBalanceGraphCard extends StatelessWidget {
Future<List<FlSpot>> getAccountBalance() async {
final coreCubit = GetIt.I.get<CoreCubit>();
final today = toMidnight(DateTime.now());
final transactions = await getLastTransactions(
final db = GetIt.I.get<OkaneDatabase>();
print("Getting transactions");
final transactions = await db.transactionsDao.getLastTransactions(
coreCubit.activeAccount!,
today,
30,
);
final totalBalance = await getTotalBalance(coreCubit.activeAccount!);
print("Got transactions. Getting balance");
final totalBalance = await db.transactionsDao.getTotalBalance([
coreCubit.activeAccount!.id,
]);
print("Got balance");
// Compute the differences per day
Map<int, double> differences = Map.fromEntries(
List.generate(30, (i) => i).map((i) => MapEntry(i, 0)),
);
for (final item in transactions) {
final diff = today.difference(toMidnight(item.date)).inDays;
final diff = today.difference(toMidnight(item.transaction.date)).inDays;
final balance = differences[diff]!;
differences[diff] = balance + item.amount;
differences[diff] = balance + item.transaction.amount;
}
// Compute the balance
@@ -74,6 +80,7 @@ class AccountBalanceGraphCard extends StatelessWidget {
child: FutureBuilder(
future: getAccountBalance(),
builder: (context, snapshot) {
print("SNAPSHOT: ${snapshot.data}");
if (!snapshot.hasData) {
return CircularProgressIndicator();
}

View File

@@ -1,11 +1,8 @@
import 'dart:math';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/collections/transaction.dart';
import 'package:okane/database/database.dart';
import 'package:okane/database/sqlite.dart';
import 'package:okane/i18n/strings.g.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/utils.dart';
@@ -45,31 +42,31 @@ class LegendItem extends StatelessWidget {
class BreakdownCard extends StatelessWidget {
const BreakdownCard({super.key});
LegendData _computeSections(List<Transaction> transactions) {
LegendData _computeSections(List<TransactionDto> transactions) {
Map<String, double> expenses = {};
Map<String, Color> colors = {};
double usableMoney = 0;
transactions.forEach((t) {
String category;
if (t.amount > 0) {
if (t.transaction.amount > 0) {
category = CATEGORY_INCOME;
colors[CATEGORY_INCOME] = Colors.green;
} else {
if (t.expenseCategory.value?.name == null) {
if (t.expenseCategory?.name == null) {
category = CATEGORY_OTHER;
colors[category] = Colors.red;
} else {
category = t.expenseCategory.value!.name;
colors[category] = colorHash(t.expenseCategory.value!.name);
category = t.expenseCategory!.name;
colors[category] = colorHash(t.expenseCategory!.name);
}
}
expenses.update(
category,
(value) => value + t.amount.abs(),
ifAbsent: () => t.amount.abs(),
(value) => value + t.transaction.amount.abs(),
ifAbsent: () => t.transaction.amount.abs().toDouble(),
);
usableMoney += t.amount;
usableMoney += t.transaction.amount;
});
return (expenses: expenses, colors: colors, usable: usableMoney);
}
@@ -97,8 +94,13 @@ class BreakdownCard extends StatelessWidget {
);
}
final db = GetIt.I.get<OkaneDatabase>();
return FutureBuilder(
future: getLastTransactions(bloc.activeAccount!, DateTime.now(), 30),
future: db.transactionsDao.getLastTransactions(
bloc.activeAccount!,
DateTime.now(),
30,
),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return _buildCard(

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/collections/account.dart';
import 'package:okane/database/sqlite.dart';
import 'package:okane/i18n/strings.g.dart';
import 'package:okane/ui/state/core.dart';
@@ -39,7 +39,7 @@ class DeleteAccountPopup extends StatelessWidget {
)
: Text(
t.pages.accounts.deleteAccount.content(
name: account.name!,
name: account.name,
),
),
actions: [

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:okane/database/database.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/sqlite.dart';
import 'package:okane/i18n/strings.g.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/utils.dart';
@@ -18,7 +19,7 @@ class TotalLoanCard extends StatelessWidget {
child: Padding(
padding: EdgeInsets.all(16),
child: FutureBuilder(
future: getTotalLoanSum(),
future: GetIt.I.get<OkaneDatabase>().loansDao.getTotalLoanSum(),
builder: (context, snapshot) {
return Text(
snapshot.hasData

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:okane/database/collections/account.dart';
import 'package:okane/database/database.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/sqlite.dart';
import 'package:okane/i18n/strings.g.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/utils.dart';
@@ -15,10 +15,13 @@ class TotalBalanceCard extends StatelessWidget {
return 0;
}
final results = await Future.wait(accounts.map(getTotalBalance).toList());
final loanSum = await getTotalLoanSum();
final db = GetIt.I.get<OkaneDatabase>();
final totalBalance = await db.transactionsDao.getTotalBalance(
accounts.map((a) => a.id),
);
return results.reduce((acc, val) => acc + val) + loanSum;
final loanSum = await db.loansDao.getTotalLoanSum();
return totalBalance + loanSum;
}
@override
@@ -32,6 +35,7 @@ class TotalBalanceCard extends StatelessWidget {
child: FutureBuilder(
future: _getTotalBalance(state.accounts),
builder: (context, snapshot) {
print("SNAPSHOT: ${snapshot.data}");
return Text(
snapshot.hasData
? formatCurrency(snapshot.data!)

View File

@@ -1,9 +1,9 @@
import 'dart:math';
import 'package:drift/drift.dart' show Value;
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/collections/recurrent.dart';
import 'package:okane/database/database.dart';
import 'package:okane/database/sqlite.dart';
import 'package:okane/i18n/strings.g.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/utils.dart';
@@ -20,8 +20,10 @@ class UpcomingTransactionsCard extends StatelessWidget {
builder: (context, state) {
final today = DateTime.now();
final upcomingRaw =
state.recurringTransactions.where((t) => t.isDue(today)).toList();
final List<RecurringTransaction> upcoming =
state.recurringTransactions
.where((t) => isTransactionDue(t.recurring, today))
.toList();
final List<RecurringTransactionDto> upcoming =
upcomingRaw.isEmpty
? List.empty()
: upcomingRaw.sublist(0, min(upcomingRaw.length, 3));
@@ -42,9 +44,9 @@ class UpcomingTransactionsCard extends StatelessWidget {
(transaction) => ListTile(
title: Text(
t.pages.accounts.upcomingTransactions.items.title(
name: transaction.template.value!.name,
name: transaction.template.name,
amount:
"${formatCurrency(transaction.template.value!.amount)}",
"${formatCurrency(transaction.template.amount)}",
),
),
subtitle: Text(
@@ -52,17 +54,18 @@ class UpcomingTransactionsCard extends StatelessWidget {
number:
today
.difference(
transaction.lastExecution ?? today,
transaction.recurring.lastExecution ??
today,
)
.inDays,
),
),
leading: Icon(
transaction.template.value!.amount < 0
transaction.template.amount < 0
? Icons.remove
: Icons.add,
color:
transaction.template.value!.amount < 0
transaction.template.amount < 0
? Colors.red
: Colors.green,
),
@@ -74,14 +77,27 @@ class UpcomingTransactionsCard extends StatelessWidget {
builder:
(context) => AddTransactionWidget(
activeAccountItem: bloc.activeAccount!,
template: transaction.template.value!,
template: (
template: transaction.template,
beneficiary: transaction.beneficiary,
expenseCategory: null,
),
onAdd: (newTransaction) async {
// Update the recurring template
transaction.lastExecution =
newTransaction.date;
await upsertRecurringTransaction(
transaction,
);
await GetIt.I
.get<OkaneDatabase>()
.recurringTransactionsDao
.upsertRecurringTransaction(
transaction.recurring
.copyWith(
lastExecution: Value(
newTransaction
.transaction
.date,
),
)
.toCompanion(true),
);
Navigator.of(context).pop();
},
),

View File

@@ -1,7 +1,7 @@
import 'package:drift/drift.dart' show Value;
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/collections/budget.dart';
import 'package:okane/database/database.dart';
import 'package:okane/database/sqlite.dart';
import 'package:okane/i18n/strings.g.dart';
import 'package:okane/ui/state/core.dart';
@@ -52,14 +52,17 @@ class AddBudgetState extends State<AddBudgetPopup> {
}
final bloc = GetIt.I.get<CoreCubit>();
final budget =
Budget()
..name = _budgetNameEditController.text
..period = BudgetPeriod.month
..includeOtherSpendings = false
..income = double.parse(_budgetIncomeEditController.text)
..account.value = bloc.activeAccount!;
await upsertBudget(budget);
await GetIt.I.get<OkaneDatabase>().budgetsDao.upsertBudget(
BudgetsCompanion(
name: Value(_budgetNameEditController.text),
period: Value(BudgetPeriod.month),
includeOtherSpendings: Value(false),
income: Value(
double.parse(_budgetIncomeEditController.text),
),
accountId: Value(bloc.activeAccount!.id),
),
);
widget.onDone();
},
child: Text(t.modals.add),

View File

@@ -1,16 +1,14 @@
import 'package:drift/drift.dart' show Value;
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/collections/budget.dart';
import 'package:okane/database/collections/expense_category.dart';
import 'package:okane/database/database.dart';
import 'package:okane/database/sqlite.dart';
import 'package:okane/i18n/strings.g.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/utils.dart';
import 'package:okane/ui/widgets/add_expense_category.dart';
class AddBudgetItemPopup extends StatefulWidget {
final VoidCallback onDone;
final Budget budget;
final BudgetsDto budget;
const AddBudgetItemPopup({
super.key,
@@ -74,26 +72,24 @@ class AddBudgetItemState extends State<AddBudgetItemPopup> {
_expenseCategory == null) {
return;
}
if (widget.budget.items
if (widget.budget.budgetItems
.where(
(i) =>
i.expenseCategory.value!.name ==
_expenseCategory!.name,
i.expenseCategory.name == _expenseCategory!.name,
)
.firstOrNull !=
null) {
return;
}
final item =
BudgetItem()
..expenseCategory.value = _expenseCategory
..amount = double.parse(
_budgetItemAmountEditController.text,
);
await upsertBudgetItem(item);
widget.budget.items.add(item);
await upsertBudget(widget.budget);
await GetIt.I.get<OkaneDatabase>().budgetsDao.upsertBudgetItem(
BudgetItemsCompanion(
expenseCategoryId: Value(_expenseCategory!.id),
amount: Value(
double.parse(_budgetItemAmountEditController.text),
),
),
);
widget.onDone();
},
child: Text(t.modals.add),

View File

@@ -1,8 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/collections/budget.dart';
import 'package:okane/database/database.dart';
import 'package:okane/database/sqlite.dart';
import 'package:okane/i18n/strings.g.dart';
import 'package:okane/ui/pages/budgets/add_budget_item.dart';
import 'package:okane/ui/state/core.dart';
@@ -56,7 +55,7 @@ class BudgetDetailsPage extends StatelessWidget {
return Text(t.pages.budgets.details.noBudgetSelected);
}
if (state.activeBudget!.items.isEmpty) {
if (state.activeBudget!.budgetItems.isEmpty) {
return Row(
children: [
Text(t.pages.budgets.details.noBudgetItems),
@@ -73,14 +72,15 @@ class BudgetDetailsPage extends StatelessWidget {
final bloc = GetIt.I.get<CoreCubit>();
final today = DateTime.now();
final db = GetIt.I.get<OkaneDatabase>();
return FutureBuilder(
future: getTransactionsInTimeframe(
future: db.transactionsDao.getTransactionsInTimeframe(
bloc.activeAccount!,
today,
TransactionQueryDateOption.thisMonth,
),
builder: (context, snapshot) {
final daysLeft = switch (state.activeBudget!.period) {
final daysLeft = switch (state.activeBudget!.budget.period) {
BudgetPeriod.month =>
monthEnding(today).difference(today).inDays,
};
@@ -95,16 +95,17 @@ class BudgetDetailsPage extends StatelessWidget {
),
ListView.builder(
shrinkWrap: true,
itemCount: state.activeBudget!.items.length,
itemCount: state.activeBudget!.budgetItems.length,
itemBuilder: (context, index) {
final item = state.activeBudget!.items.elementAt(
index,
);
final amount = formatCurrency(item.amount);
final item = state.activeBudget!.budgetItems
.elementAt(index);
final amount = formatCurrency(item.item.amount);
return ListTile(
title: Text(
t.pages.budgets.details.items.title(
name: item.expenseCategory.value!.name,
// TODO
name: "lol",
//name: item.expenseCategory.value!.name,
amount: amount,
),
),
@@ -119,26 +120,28 @@ class BudgetDetailsPage extends StatelessWidget {
}
final categories =
state.activeBudget!.items
.map((i) => i.expenseCategory.value!.name)
state.activeBudget!.budgetItems
// TODO
//.map((i) => i.expenseCategory.value!.name)
.map((i) => "lol")
.toList();
final spending = <String, double>{};
for (final t in snapshot.data!) {
String categoryName;
if (!categories.contains(t.expenseCategory.value?.name)) {
if (!state.activeBudget!.includeOtherSpendings) {
if (!categories.contains(t.expenseCategory?.name)) {
if (!state.activeBudget!.budget.includeOtherSpendings) {
continue;
}
categoryName = "Other";
} else {
categoryName = t.expenseCategory.value!.name;
categoryName = t.expenseCategory!.name;
}
spending.update(
categoryName,
(value) => value + t.amount,
ifAbsent: () => t.amount,
(value) => value + t.transaction.amount,
ifAbsent: () => t.transaction.amount,
);
}
@@ -146,8 +149,8 @@ class BudgetDetailsPage extends StatelessWidget {
spending.isEmpty
? 0
: spending.values.reduce((acc, val) => acc + val);
final budgetTotal = state.activeBudget!.items
.map((i) => i.amount)
final budgetTotal = state.activeBudget!.budgetItems
.map((i) => i.item.amount)
.reduce((acc, val) => acc + val);
return Column(
mainAxisSize: MainAxisSize.min,
@@ -268,13 +271,13 @@ class BudgetDetailsPage extends StatelessWidget {
fallbackText: "",
valueConverter: formatCurrency,
items:
state.activeBudget!.items
state.activeBudget!.budgetItems
.map(
(i) => (
title: i.expenseCategory.value!.name,
value: i.amount,
title: i.expenseCategory.name,
value: i.item.amount,
color: colorHash(
i.expenseCategory.value!.name,
i.expenseCategory.name,
),
),
)
@@ -339,18 +342,16 @@ class BudgetDetailsPage extends StatelessWidget {
padding: EdgeInsets.all(8),
child: ListView.builder(
shrinkWrap: true,
itemCount: state.activeBudget!.items.length,
itemCount: state.activeBudget!.budgetItems.length,
itemBuilder: (context, index) {
final item = state.activeBudget!.items.elementAt(
index,
);
final amount = formatCurrency(item.amount);
final spent =
spending[item.expenseCategory.value!.name];
final item = state.activeBudget!.budgetItems
.elementAt(index);
final amount = formatCurrency(item.item.amount);
final spent = spending[item.expenseCategory.name];
final left =
spent == null
? item.amount
: item.amount + spent;
? item.item.amount
: item.item.amount + spent;
final subtitleText =
left < 0
? t.pages.budgets.details.items.over(
@@ -361,7 +362,7 @@ class BudgetDetailsPage extends StatelessWidget {
);
return ListTile(
title: Text(
"${item.expenseCategory.value!.name} ($amount)",
"${item.expenseCategory.name} ($amount)",
),
subtitle: Text(
subtitleText,

View File

@@ -28,7 +28,7 @@ class BudgetListPage extends StatelessWidget {
itemCount: state.budgets.length,
itemBuilder:
(context, index) => ListTile(
title: Text(state.budgets[index].name),
title: Text(state.budgets[index].budget.name),
selected: state.budgets[index] == state.activeBudget,
trailing: Row(
mainAxisSize: MainAxisSize.min,

View File

@@ -1,10 +1,10 @@
import 'package:flutter/material.dart';
import 'package:okane/database/collections/budget.dart';
import 'package:okane/database/database.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/sqlite.dart';
import 'package:okane/i18n/strings.g.dart';
class EditBudgetPopup extends StatefulWidget {
final Budget budget;
final BudgetsDto budget;
final VoidCallback onDone;
@@ -27,8 +27,8 @@ class EditBudgetState extends State<EditBudgetPopup> {
void initState() {
super.initState();
_budgetNameEditController.text = widget.budget.name;
_includeOtherSpendings = widget.budget.includeOtherSpendings;
_budgetNameEditController.text = widget.budget.budget.name;
_includeOtherSpendings = widget.budget.budget.includeOtherSpendings;
}
@override
@@ -61,17 +61,22 @@ class EditBudgetState extends State<EditBudgetPopup> {
if (_budgetNameEditController.text.isEmpty) {
return;
}
if (_budgetNameEditController.text == widget.budget.name &&
if (_budgetNameEditController.text ==
widget.budget.budget.name &&
_includeOtherSpendings ==
widget.budget.includeOtherSpendings) {
widget.budget.budget.includeOtherSpendings) {
widget.onDone();
return;
}
widget.budget
..name = _budgetNameEditController.text
..includeOtherSpendings = _includeOtherSpendings;
await upsertBudget(widget.budget);
await GetIt.I.get<OkaneDatabase>().budgetsDao.upsertBudget(
widget.budget.budget
.copyWith(
name: _budgetNameEditController.text,
includeOtherSpendings: _includeOtherSpendings,
)
.toCompanion(false),
);
widget.onDone();
},
child: Text(t.modals.save),

View File

@@ -1,10 +1,8 @@
import 'package:drift/drift.dart' show Value;
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/collections/beneficiary.dart';
import 'package:okane/database/collections/budget.dart';
import 'package:okane/database/collections/loan.dart';
import 'package:okane/database/database.dart';
import 'package:okane/database/sqlite.dart';
import 'package:okane/i18n/strings.g.dart';
import 'package:okane/ui/state/core.dart';
import 'package:searchfield/searchfield.dart';
@@ -47,7 +45,7 @@ class AddBudgetState extends State<AddLoanPopup> {
.where((el) {
final bloc = GetIt.I.get<CoreCubit>();
if (el.type == BeneficiaryType.account) {
return el.account.value?.id.toInt() ==
return el.accountId ==
bloc.activeAccount?.id.toInt();
}
return true;
@@ -79,6 +77,7 @@ class AddBudgetState extends State<AddLoanPopup> {
return;
}
final db = GetIt.I.get<OkaneDatabase>();
Beneficiary? beneficiary = _selectedBeneficiary?.item;
if (beneficiary == null ||
getBeneficiaryName(beneficiary) != beneficiaryName) {
@@ -87,42 +86,49 @@ class AddBudgetState extends State<AddLoanPopup> {
context: context,
builder:
(context) => AlertDialog(
title: Text(t.common.beneficiary.addBeneficiary.title),
content: Text(
t.common.beneficiary.addBeneficiary.body(name: beneficiaryName),
),
actions: [
TextButton(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge,
title: Text(
t.common.beneficiary.addBeneficiary.title,
),
child: Text(t.modals.add),
onPressed: () => Navigator.of(context).pop(true),
),
TextButton(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge,
content: Text(
t.common.beneficiary.addBeneficiary.body(
name: beneficiaryName,
),
),
child: Text(t.modals.cancel),
onPressed: () => Navigator.of(context).pop(false),
actions: [
TextButton(
style: TextButton.styleFrom(
textStyle:
Theme.of(context).textTheme.labelLarge,
),
child: Text(t.modals.add),
onPressed: () => Navigator.of(context).pop(true),
),
TextButton(
style: TextButton.styleFrom(
textStyle:
Theme.of(context).textTheme.labelLarge,
),
child: Text(t.modals.cancel),
onPressed: () => Navigator.of(context).pop(false),
),
],
),
],
),
);
if (result == null || !result) {
return;
}
beneficiary =
Beneficiary()
..name = beneficiaryName
..type = BeneficiaryType.other;
await upsertBeneficiary(beneficiary);
beneficiary = await db.beneficiariesDao.upsertBeneficiary(
BeneficiariesCompanion(
name: Value(beneficiaryName),
type: Value(BeneficiaryType.other),
),
);
}
final loan =
Loan()..beneficiary.value = beneficiary;
await upsertLoan(loan);
await db.loansDao.upsertLoan(
LoansCompanion(beneficiaryId: Value(beneficiary.id)),
);
widget.onDone();
},
child: Text(t.modals.add),

View File

@@ -1,6 +1,7 @@
import 'package:drift/drift.dart' show Value;
import 'package:flutter/material.dart';
import 'package:okane/database/collections/loan.dart';
import 'package:okane/database/database.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/sqlite.dart';
import 'package:okane/i18n/strings.g.dart';
import 'package:okane/ui/utils.dart';
@@ -9,7 +10,7 @@ enum LoanChangeType { owe, loan }
class AddLoanChangePopup extends StatefulWidget {
final VoidCallback onDone;
final Loan loan;
final LoanDto loan;
const AddLoanChangePopup({
super.key,
@@ -84,13 +85,14 @@ class AddLoanPopupState extends State<AddLoanChangePopup> {
LoanChangeType.owe => -1,
LoanChangeType.loan => 1,
};
final loanChange =
LoanChange()
..amount = sign * double.parse(_amountController.text).abs()
..date = DateTime.now();
await upsertLoanChange(loanChange);
widget.loan.changes.add(loanChange);
await upsertLoan(widget.loan);
await GetIt.I.get<OkaneDatabase>().loansDao.upsertLoanChange(
LoanChangesCompanion(
amount: Value(
sign * double.parse(_amountController.text).abs(),
),
date: Value(DateTime.now()),
),
);
widget.onDone();
},
child: Text(t.modals.add),

View File

@@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:grouped_list/grouped_list.dart';
import 'package:okane/database/collections/loan.dart';
import 'package:okane/database/database.dart';
import 'package:okane/database/sqlite.dart';
import 'package:okane/ui/pages/loans/add_loan_change.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/utils.dart';
@@ -35,6 +35,7 @@ class LoanDetailsPage extends StatelessWidget {
],
),
),
Expanded(
child: BlocBuilder<CoreCubit, CoreState>(
builder: (context, state) {
@@ -55,11 +56,10 @@ class LoanDetailsPage extends StatelessWidget {
child: Row(
children: [
ImageWrapper(
title: state.activeLoan!.beneficiary.value!.name,
path:
state.activeLoan!.beneficiary.value!.imagePath,
title: state.activeLoan!.beneficiary.name,
path: state.activeLoan!.beneficiary.imagePath,
),
Text(state.activeLoan!.beneficiary.value!.name),
Text(state.activeLoan!.beneficiary.name),
],
),
),
@@ -145,11 +145,10 @@ class LoanDetailsPage extends StatelessWidget {
color: Colors.red,
),
onPressed: () async {
state.activeLoan!.changes.remove(
item,
);
await deleteLoanChange(item);
await upsertLoan(state.activeLoan!);
await GetIt.I
.get<OkaneDatabase>()
.loansDao
.deleteLoanChange(item.id);
},
),
),

View File

@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/database.dart';
import 'package:okane/screen.dart';
import 'package:okane/ui/pages/loans/add_loan.dart';
import 'package:okane/ui/state/core.dart';
@@ -21,7 +20,7 @@ class LoanListPage extends StatelessWidget {
itemCount: state.loans.length,
itemBuilder: (context, index) {
final item = state.loans[index];
final beneficiary = item.beneficiary.value!;
final beneficiary = item.beneficiary;
return ListTile(
leading: ImageWrapper(
title: beneficiary.name,
@@ -45,7 +44,8 @@ class LoanListPage extends StatelessWidget {
return;
}
await deleteLoan(item);
// TODO
// await deleteLoan(item);
},
icon: Icon(Icons.delete, color: Colors.red),
),

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/database.dart';
import 'package:okane/database/sqlite.dart';
import 'package:okane/i18n/strings.g.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/utils.dart';
@@ -21,7 +21,9 @@ class TemplateListState extends State<TemplateListPage> {
builder: (context, state) {
final account = GetIt.I.get<CoreCubit>().activeAccount;
final nonRecurringTemplates =
state.transactionTemplates.where((t) => !t.recurring).toList();
state.transactionTemplates
.where((t) => !t.template.recurring)
.toList();
return Stack(
children: [
CustomScrollView(
@@ -34,7 +36,7 @@ class TemplateListState extends State<TemplateListPage> {
itemBuilder: (context, index) {
final template = nonRecurringTemplates[index];
return ListTile(
title: Text(template.name),
title: Text(template.template.name),
trailing: IconButton(
icon: Icon(Icons.delete),
color: Colors.red,
@@ -43,14 +45,17 @@ class TemplateListState extends State<TemplateListPage> {
context,
t.pages.templates.removeTemplate.title,
t.pages.templates.removeTemplate.body(
name: template.name,
name: template.template.name,
),
);
if (!result) {
return;
}
await deleteTransactionTemplate(template);
await GetIt.I
.get<OkaneDatabase>()
.transactionTemplatesDao
.deleteTemplate(template.template);
},
),
);
@@ -64,7 +69,7 @@ class TemplateListState extends State<TemplateListPage> {
itemBuilder: (context, index) {
final template = state.recurringTransactions[index];
return ListTile(
title: Text(template.template.value!.name),
title: Text(template.template.name),
trailing: IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () async {
@@ -72,14 +77,17 @@ class TemplateListState extends State<TemplateListPage> {
context,
t.pages.templates.removeTemplate.title,
t.pages.templates.removeTemplate.body(
name: template.template.value!.name,
name: template.template.name,
),
);
if (!result) {
return;
}
await deleteRecurringTransactionTemplate(template);
await GetIt.I
.get<OkaneDatabase>()
.recurringTransactionsDao
.deleteTemplate(template);
},
),
);

View File

@@ -1,11 +1,11 @@
import 'dart:io';
import 'package:drift/drift.dart' show Value;
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/collections/beneficiary.dart';
import 'package:okane/database/database.dart';
import 'package:okane/database/sqlite.dart';
import 'package:okane/i18n/strings.g.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/utils.dart';
@@ -43,8 +43,9 @@ class TransactionDetailsPage extends StatelessWidget {
await File(file.path!).copy(imagePath);
print("Updating DB");
beneficiary.imagePath = imagePath;
await upsertBeneficiary(beneficiary);
await GetIt.I.get<OkaneDatabase>().beneficiariesDao.upsertBeneficiary(
beneficiary.copyWith(imagePath: Value(imagePath)).toCompanion(false),
);
}
@override
@@ -83,13 +84,16 @@ class TransactionDetailsPage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
StreamBuilder(
stream: watchBeneficiaryObject(
state.activeTransaction!.beneficiary.value!.id,
),
stream: GetIt.I
.get<OkaneDatabase>()
.beneficiariesDao
.watchBeneficiary(
state.activeTransaction!.beneficiary.id,
),
builder: (context, snapshot) {
final obj =
snapshot.data ??
state.activeTransaction!.beneficiary.value!;
state.activeTransaction!.beneficiary;
return ImageWrapper(
title: obj.name,
path: obj.imagePath,
@@ -116,7 +120,6 @@ class TransactionDetailsPage extends StatelessWidget {
state
.activeTransaction!
.beneficiary
.value!
.name,
),
),
@@ -150,16 +153,16 @@ class TransactionDetailsPage extends StatelessWidget {
],
),
// TODO
/*
Wrap(
spacing: 8,
children:
state.activeTransaction!.tags
.map((tag) => Chip(label: Text(tag)))
.toList(),
),
if (state.activeTransaction!.expenseCategory.value !=
null)
),*/
if (state.activeTransaction!.expenseCategory != null)
Padding(
padding: EdgeInsets.symmetric(vertical: 8),
child: Row(
@@ -172,8 +175,7 @@ class TransactionDetailsPage extends StatelessWidget {
label: Text(
state
.activeTransaction!
.expenseCategory
.value!
.expenseCategory!
.name,
),
),
@@ -186,11 +188,13 @@ class TransactionDetailsPage extends StatelessWidget {
padding: EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
state.activeTransaction!.amount > 0
state.activeTransaction!.transaction.amount > 0
? Icon(Icons.add)
: Icon(Icons.remove),
Text(
formatCurrency(state.activeTransaction!.amount),
formatCurrency(
state.activeTransaction!.transaction.amount,
),
),
],
),

View File

@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:grouped_list/grouped_list.dart';
import 'package:okane/database/collections/transaction.dart';
import 'package:okane/database/sqlite.dart';
import 'package:okane/screen.dart';
import 'package:okane/ui/pages/account/balance_graph_card.dart';
import 'package:okane/ui/state/core.dart';
@@ -41,7 +41,9 @@ class TransactionListState extends State<TransactionListPage> {
physics: NeverScrollableScrollPhysics(),
elements: state.transactions,
reverse: true,
groupBy: (Transaction item) => formatDateTime(item.date),
groupBy:
(TransactionDto item) =>
formatDateTime(item.transaction.date),
groupHeaderBuilder:
(item) => Row(
mainAxisAlignment: MainAxisAlignment.center,
@@ -54,7 +56,7 @@ class TransactionListState extends State<TransactionListPage> {
child: Padding(
padding: EdgeInsets.all(4),
child: Text(
formatDateTime(item.date),
formatDateTime(item.transaction.date),
style: TextStyle(color: Colors.white),
),
),

View File

@@ -1,15 +1,8 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:okane/database/collections/account.dart';
import 'package:okane/database/collections/beneficiary.dart';
import 'package:okane/database/collections/budget.dart';
import 'package:okane/database/collections/expense_category.dart';
import 'package:okane/database/collections/loan.dart';
import 'package:okane/database/collections/recurrent.dart';
import 'package:okane/database/collections/template.dart';
import 'package:okane/database/collections/transaction.dart';
import 'package:okane/database/database.dart' as db;
import 'package:get_it/get_it.dart';
import 'package:okane/database/sqlite.dart';
import 'package:okane/ui/navigation.dart';
part 'core.freezed.dart';
@@ -19,17 +12,17 @@ abstract class CoreState with _$CoreState {
const factory CoreState({
@Default(OkanePage.accounts) OkanePage activePage,
int? activeAccountIndex,
@Default(null) Transaction? activeTransaction,
@Default(null) TransactionDto? activeTransaction,
@Default([]) List<Account> accounts,
@Default([]) List<RecurringTransaction> recurringTransactions,
@Default([]) List<Transaction> transactions,
@Default([]) List<TransactionTemplate> transactionTemplates,
@Default([]) List<RecurringTransactionDto> recurringTransactions,
@Default([]) List<TransactionDto> transactions,
@Default([]) List<TransactionTemplateDto> transactionTemplates,
@Default([]) List<Beneficiary> beneficiaries,
@Default([]) List<ExpenseCategory> expenseCategories,
@Default([]) List<Budget> budgets,
@Default(null) Budget? activeBudget,
@Default([]) List<Loan> loans,
@Default(null) Loan? activeLoan,
@Default([]) List<BudgetsDto> budgets,
@Default(null) BudgetsDto? activeBudget,
@Default([]) List<LoanDto> loans,
@Default(null) LoanDto? activeLoan,
@Default(false) bool isDeletingAccount,
}) = _CoreState;
}
@@ -48,80 +41,65 @@ class CoreCubit extends Cubit<CoreState> {
void setupAccountStream() {
_accountsStreamSubscription?.cancel();
_accountsStreamSubscription = db.watchAccounts().listen((_) async {
final resetStreams = state.activeAccountIndex == null;
final accounts = await db.getAccounts();
emit(
state.copyWith(
accounts: accounts,
activeAccountIndex: state.activeAccountIndex ?? 0,
),
);
if (resetStreams) {
setupStreams(accounts[0]);
}
});
_accountsStreamSubscription = GetIt.I
.get<OkaneDatabase>()
.accountsDao
.accountsStream()
.listen((accounts) {
emit(
state.copyWith(
accounts: accounts,
activeAccountIndex:
accounts.isNotEmpty ? state.activeAccountIndex ?? 0 : null,
),
);
});
}
void setupStreams(Account account) {
final db = GetIt.I.get<OkaneDatabase>();
setupAccountStream();
_recurringTransactionStreamSubscription?.cancel();
_recurringTransactionStreamSubscription = db
.watchRecurringTransactions(activeAccount!)
.listen((_) async {
print("RECURRING UPDATE");
emit(
state.copyWith(
recurringTransactions: await db.getRecurringTransactions(
activeAccount!,
),
),
);
_recurringTransactionStreamSubscription = db.recurringTransactionsDao
.recurringTransactionsStream(account)
.listen((recurring) async {
emit(state.copyWith(recurringTransactions: recurring));
});
_transactionTemplatesStreamSubcription?.cancel();
_transactionTemplatesStreamSubcription = db
.watchTransactionTemplates(activeAccount!)
.listen((_) async {
emit(
state.copyWith(
transactionTemplates: await db.getTransactionTemplates(
activeAccount!,
),
),
);
_transactionTemplatesStreamSubcription = db.transactionTemplatesDao
.transactionTemplatesStream(account)
.listen((templates) async {
emit(state.copyWith(transactionTemplates: templates));
});
_transactionsStreamSubscription?.cancel();
_transactionsStreamSubscription = db
.watchTransactions(activeAccount!)
.listen((_) async {
emit(
state.copyWith(
transactions: await db.getTransactions(activeAccount!),
),
);
_transactionsStreamSubscription = db.transactionsDao
.transactionsStream(activeAccount!)
.listen((transactions) async {
emit(state.copyWith(transactions: transactions));
});
_beneficiariesStreamSubscription?.cancel();
_beneficiariesStreamSubscription = db.watchBeneficiaries().listen((
_,
) async {
emit(state.copyWith(beneficiaries: await db.getBeneficiaries()));
});
_beneficiariesStreamSubscription = db.beneficiariesDao
.beneficiariesStream()
.listen((beneficiaries) async {
emit(state.copyWith(beneficiaries: beneficiaries));
});
_expenseCategoryStreamSubscription?.cancel();
_expenseCategoryStreamSubscription = db.watchExpenseCategory().listen((
_,
) async {
emit(state.copyWith(expenseCategories: await db.getExpenseCategories()));
});
_expenseCategoryStreamSubscription = db.expenseCategoriesDao
.expenseCategoriesStream(account)
.listen((expenseCategories) async {
emit(state.copyWith(expenseCategories: expenseCategories));
});
_budgetsStreamSubscription?.cancel();
_budgetsStreamSubscription = db.watchBudgets(activeAccount!).listen((
_,
) async {
emit(state.copyWith(budgets: await db.getBudgets(activeAccount!)));
});
_budgetsStreamSubscription = db.budgetsDao
.budgetsStream(activeAccount!)
.listen((budgets) async {
emit(state.copyWith(budgets: budgets));
});
_loanStreamSubscription?.cancel();
_loanStreamSubscription = db.watchLoans().listen((_) async {
emit(state.copyWith(loans: await db.getLoans()));
_loanStreamSubscription = db.loansDao.loansStream(account).listen((
loans,
) async {
emit(state.copyWith(loans: loans));
});
}
@@ -137,23 +115,29 @@ class CoreCubit extends Cubit<CoreState> {
}
Future<void> init() async {
final accounts = await db.getAccounts();
final db = GetIt.I.get<OkaneDatabase>();
final accounts = await db.accountsDao.getAccounts();
final account = accounts.isEmpty ? null : accounts[0];
emit(
state.copyWith(
accounts: accounts,
activeAccountIndex: accounts.isEmpty ? null : 0,
transactions: await db.getTransactions(account),
beneficiaries: await db.getBeneficiaries(),
transactionTemplates: await db.getTransactionTemplates(account),
recurringTransactions: await db.getRecurringTransactions(account),
expenseCategories: await db.getExpenseCategories(),
budgets: await db.getBudgets(account),
loans: await db.getLoans(),
transactions: await db.transactionsDao.getTransactions(account),
beneficiaries: await db.beneficiariesDao.getBeneficiaries(),
transactionTemplates: await db.transactionTemplatesDao
.getTransactionTemplates(account),
recurringTransactions: await db.recurringTransactionsDao
.getRecurringTransactions(account),
expenseCategories: await db.expenseCategoriesDao.getExpenseCategories(
account,
),
budgets: await db.budgetsDao.getBudgets(account),
loans: await db.loansDao.getLoans(account),
),
);
if (account != null) {
setupAccountStream();
setupStreams(account);
} else {
setupAccountStream();
@@ -166,15 +150,18 @@ class CoreCubit extends Cubit<CoreState> {
}
Future<void> setActiveAccountIndex(int index) async {
final db = GetIt.I.get<OkaneDatabase>();
final account = state.accounts[index];
emit(
state.copyWith(
activeAccountIndex: index,
transactions: await db.getTransactions(account),
beneficiaries: await db.getBeneficiaries(),
transactionTemplates: await db.getTransactionTemplates(account),
recurringTransactions: await db.getRecurringTransactions(account),
budgets: await db.getBudgets(account),
transactions: await db.transactionsDao.getTransactions(account),
beneficiaries: await db.beneficiariesDao.getBeneficiaries(),
transactionTemplates: await db.transactionTemplatesDao
.getTransactionTemplates(account),
recurringTransactions: await db.recurringTransactionsDao
.getRecurringTransactions(account),
budgets: await db.budgetsDao.getBudgets(account),
activeBudget: null,
activeTransaction: null,
activeLoan: null,
@@ -183,7 +170,7 @@ class CoreCubit extends Cubit<CoreState> {
setupStreams(account);
}
void setActiveTransaction(Transaction? item) {
void setActiveTransaction(TransactionDto? item) {
emit(state.copyWith(activeTransaction: item));
}
@@ -191,7 +178,7 @@ class CoreCubit extends Cubit<CoreState> {
emit(state.copyWith(accounts: accounts));
}
void setActiveBudget(Budget? budget) {
void setActiveBudget(BudgetsDto? budget) {
emit(state.copyWith(activeBudget: budget));
}
@@ -203,14 +190,15 @@ class CoreCubit extends Cubit<CoreState> {
cancelStreams();
try {
await db.deleteAccount(account);
// TODO
//await db.deleteAccount(account);
} finally {
emit(state.copyWith(isDeletingAccount: false));
}
await init();
}
void setActiveLoan(Loan loan) {
void setActiveLoan(LoanDto loan) {
emit(state.copyWith(activeLoan: loan));
}

View File

@@ -19,20 +19,20 @@ final _privateConstructorUsedError = UnsupportedError(
mixin _$CoreState {
OkanePage get activePage => throw _privateConstructorUsedError;
int? get activeAccountIndex => throw _privateConstructorUsedError;
Transaction? get activeTransaction => throw _privateConstructorUsedError;
TransactionDto? get activeTransaction => throw _privateConstructorUsedError;
List<Account> get accounts => throw _privateConstructorUsedError;
List<RecurringTransaction> get recurringTransactions =>
List<RecurringTransactionDto> get recurringTransactions =>
throw _privateConstructorUsedError;
List<Transaction> get transactions => throw _privateConstructorUsedError;
List<TransactionTemplate> get transactionTemplates =>
List<TransactionDto> get transactions => throw _privateConstructorUsedError;
List<TransactionTemplateDto> get transactionTemplates =>
throw _privateConstructorUsedError;
List<Beneficiary> get beneficiaries => throw _privateConstructorUsedError;
List<ExpenseCategory> get expenseCategories =>
throw _privateConstructorUsedError;
List<Budget> get budgets => throw _privateConstructorUsedError;
Budget? get activeBudget => throw _privateConstructorUsedError;
List<Loan> get loans => throw _privateConstructorUsedError;
Loan? get activeLoan => throw _privateConstructorUsedError;
List<BudgetsDto> get budgets => throw _privateConstructorUsedError;
BudgetsDto? get activeBudget => throw _privateConstructorUsedError;
List<LoanDto> get loans => throw _privateConstructorUsedError;
LoanDto? get activeLoan => throw _privateConstructorUsedError;
bool get isDeletingAccount => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
@@ -48,17 +48,17 @@ abstract class $CoreStateCopyWith<$Res> {
$Res call({
OkanePage activePage,
int? activeAccountIndex,
Transaction? activeTransaction,
TransactionDto? activeTransaction,
List<Account> accounts,
List<RecurringTransaction> recurringTransactions,
List<Transaction> transactions,
List<TransactionTemplate> transactionTemplates,
List<RecurringTransactionDto> recurringTransactions,
List<TransactionDto> transactions,
List<TransactionTemplateDto> transactionTemplates,
List<Beneficiary> beneficiaries,
List<ExpenseCategory> expenseCategories,
List<Budget> budgets,
Budget? activeBudget,
List<Loan> loans,
Loan? activeLoan,
List<BudgetsDto> budgets,
BudgetsDto? activeBudget,
List<LoanDto> loans,
LoanDto? activeLoan,
bool isDeletingAccount,
});
}
@@ -107,7 +107,7 @@ class _$CoreStateCopyWithImpl<$Res, $Val extends CoreState>
freezed == activeTransaction
? _value.activeTransaction
: activeTransaction // ignore: cast_nullable_to_non_nullable
as Transaction?,
as TransactionDto?,
accounts:
null == accounts
? _value.accounts
@@ -117,17 +117,17 @@ class _$CoreStateCopyWithImpl<$Res, $Val extends CoreState>
null == recurringTransactions
? _value.recurringTransactions
: recurringTransactions // ignore: cast_nullable_to_non_nullable
as List<RecurringTransaction>,
as List<RecurringTransactionDto>,
transactions:
null == transactions
? _value.transactions
: transactions // ignore: cast_nullable_to_non_nullable
as List<Transaction>,
as List<TransactionDto>,
transactionTemplates:
null == transactionTemplates
? _value.transactionTemplates
: transactionTemplates // ignore: cast_nullable_to_non_nullable
as List<TransactionTemplate>,
as List<TransactionTemplateDto>,
beneficiaries:
null == beneficiaries
? _value.beneficiaries
@@ -142,22 +142,22 @@ class _$CoreStateCopyWithImpl<$Res, $Val extends CoreState>
null == budgets
? _value.budgets
: budgets // ignore: cast_nullable_to_non_nullable
as List<Budget>,
as List<BudgetsDto>,
activeBudget:
freezed == activeBudget
? _value.activeBudget
: activeBudget // ignore: cast_nullable_to_non_nullable
as Budget?,
as BudgetsDto?,
loans:
null == loans
? _value.loans
: loans // ignore: cast_nullable_to_non_nullable
as List<Loan>,
as List<LoanDto>,
activeLoan:
freezed == activeLoan
? _value.activeLoan
: activeLoan // ignore: cast_nullable_to_non_nullable
as Loan?,
as LoanDto?,
isDeletingAccount:
null == isDeletingAccount
? _value.isDeletingAccount
@@ -181,17 +181,17 @@ abstract class _$$CoreStateImplCopyWith<$Res>
$Res call({
OkanePage activePage,
int? activeAccountIndex,
Transaction? activeTransaction,
TransactionDto? activeTransaction,
List<Account> accounts,
List<RecurringTransaction> recurringTransactions,
List<Transaction> transactions,
List<TransactionTemplate> transactionTemplates,
List<RecurringTransactionDto> recurringTransactions,
List<TransactionDto> transactions,
List<TransactionTemplateDto> transactionTemplates,
List<Beneficiary> beneficiaries,
List<ExpenseCategory> expenseCategories,
List<Budget> budgets,
Budget? activeBudget,
List<Loan> loans,
Loan? activeLoan,
List<BudgetsDto> budgets,
BudgetsDto? activeBudget,
List<LoanDto> loans,
LoanDto? activeLoan,
bool isDeletingAccount,
});
}
@@ -239,7 +239,7 @@ class __$$CoreStateImplCopyWithImpl<$Res>
freezed == activeTransaction
? _value.activeTransaction
: activeTransaction // ignore: cast_nullable_to_non_nullable
as Transaction?,
as TransactionDto?,
accounts:
null == accounts
? _value._accounts
@@ -249,17 +249,17 @@ class __$$CoreStateImplCopyWithImpl<$Res>
null == recurringTransactions
? _value._recurringTransactions
: recurringTransactions // ignore: cast_nullable_to_non_nullable
as List<RecurringTransaction>,
as List<RecurringTransactionDto>,
transactions:
null == transactions
? _value._transactions
: transactions // ignore: cast_nullable_to_non_nullable
as List<Transaction>,
as List<TransactionDto>,
transactionTemplates:
null == transactionTemplates
? _value._transactionTemplates
: transactionTemplates // ignore: cast_nullable_to_non_nullable
as List<TransactionTemplate>,
as List<TransactionTemplateDto>,
beneficiaries:
null == beneficiaries
? _value._beneficiaries
@@ -274,22 +274,22 @@ class __$$CoreStateImplCopyWithImpl<$Res>
null == budgets
? _value._budgets
: budgets // ignore: cast_nullable_to_non_nullable
as List<Budget>,
as List<BudgetsDto>,
activeBudget:
freezed == activeBudget
? _value.activeBudget
: activeBudget // ignore: cast_nullable_to_non_nullable
as Budget?,
as BudgetsDto?,
loans:
null == loans
? _value._loans
: loans // ignore: cast_nullable_to_non_nullable
as List<Loan>,
as List<LoanDto>,
activeLoan:
freezed == activeLoan
? _value.activeLoan
: activeLoan // ignore: cast_nullable_to_non_nullable
as Loan?,
as LoanDto?,
isDeletingAccount:
null == isDeletingAccount
? _value.isDeletingAccount
@@ -308,14 +308,14 @@ class _$CoreStateImpl implements _CoreState {
this.activeAccountIndex,
this.activeTransaction = null,
final List<Account> accounts = const [],
final List<RecurringTransaction> recurringTransactions = const [],
final List<Transaction> transactions = const [],
final List<TransactionTemplate> transactionTemplates = const [],
final List<RecurringTransactionDto> recurringTransactions = const [],
final List<TransactionDto> transactions = const [],
final List<TransactionTemplateDto> transactionTemplates = const [],
final List<Beneficiary> beneficiaries = const [],
final List<ExpenseCategory> expenseCategories = const [],
final List<Budget> budgets = const [],
final List<BudgetsDto> budgets = const [],
this.activeBudget = null,
final List<Loan> loans = const [],
final List<LoanDto> loans = const [],
this.activeLoan = null,
this.isDeletingAccount = false,
}) : _accounts = accounts,
@@ -334,7 +334,7 @@ class _$CoreStateImpl implements _CoreState {
final int? activeAccountIndex;
@override
@JsonKey()
final Transaction? activeTransaction;
final TransactionDto? activeTransaction;
final List<Account> _accounts;
@override
@JsonKey()
@@ -344,29 +344,29 @@ class _$CoreStateImpl implements _CoreState {
return EqualUnmodifiableListView(_accounts);
}
final List<RecurringTransaction> _recurringTransactions;
final List<RecurringTransactionDto> _recurringTransactions;
@override
@JsonKey()
List<RecurringTransaction> get recurringTransactions {
List<RecurringTransactionDto> get recurringTransactions {
if (_recurringTransactions is EqualUnmodifiableListView)
return _recurringTransactions;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_recurringTransactions);
}
final List<Transaction> _transactions;
final List<TransactionDto> _transactions;
@override
@JsonKey()
List<Transaction> get transactions {
List<TransactionDto> get transactions {
if (_transactions is EqualUnmodifiableListView) return _transactions;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_transactions);
}
final List<TransactionTemplate> _transactionTemplates;
final List<TransactionTemplateDto> _transactionTemplates;
@override
@JsonKey()
List<TransactionTemplate> get transactionTemplates {
List<TransactionTemplateDto> get transactionTemplates {
if (_transactionTemplates is EqualUnmodifiableListView)
return _transactionTemplates;
// ignore: implicit_dynamic_type
@@ -392,10 +392,10 @@ class _$CoreStateImpl implements _CoreState {
return EqualUnmodifiableListView(_expenseCategories);
}
final List<Budget> _budgets;
final List<BudgetsDto> _budgets;
@override
@JsonKey()
List<Budget> get budgets {
List<BudgetsDto> get budgets {
if (_budgets is EqualUnmodifiableListView) return _budgets;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_budgets);
@@ -403,11 +403,11 @@ class _$CoreStateImpl implements _CoreState {
@override
@JsonKey()
final Budget? activeBudget;
final List<Loan> _loans;
final BudgetsDto? activeBudget;
final List<LoanDto> _loans;
@override
@JsonKey()
List<Loan> get loans {
List<LoanDto> get loans {
if (_loans is EqualUnmodifiableListView) return _loans;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_loans);
@@ -415,7 +415,7 @@ class _$CoreStateImpl implements _CoreState {
@override
@JsonKey()
final Loan? activeLoan;
final LoanDto? activeLoan;
@override
@JsonKey()
final bool isDeletingAccount;
@@ -497,17 +497,17 @@ abstract class _CoreState implements CoreState {
const factory _CoreState({
final OkanePage activePage,
final int? activeAccountIndex,
final Transaction? activeTransaction,
final TransactionDto? activeTransaction,
final List<Account> accounts,
final List<RecurringTransaction> recurringTransactions,
final List<Transaction> transactions,
final List<TransactionTemplate> transactionTemplates,
final List<RecurringTransactionDto> recurringTransactions,
final List<TransactionDto> transactions,
final List<TransactionTemplateDto> transactionTemplates,
final List<Beneficiary> beneficiaries,
final List<ExpenseCategory> expenseCategories,
final List<Budget> budgets,
final Budget? activeBudget,
final List<Loan> loans,
final Loan? activeLoan,
final List<BudgetsDto> budgets,
final BudgetsDto? activeBudget,
final List<LoanDto> loans,
final LoanDto? activeLoan,
final bool isDeletingAccount,
}) = _$CoreStateImpl;
@@ -516,27 +516,27 @@ abstract class _CoreState implements CoreState {
@override
int? get activeAccountIndex;
@override
Transaction? get activeTransaction;
TransactionDto? get activeTransaction;
@override
List<Account> get accounts;
@override
List<RecurringTransaction> get recurringTransactions;
List<RecurringTransactionDto> get recurringTransactions;
@override
List<Transaction> get transactions;
List<TransactionDto> get transactions;
@override
List<TransactionTemplate> get transactionTemplates;
List<TransactionTemplateDto> get transactionTemplates;
@override
List<Beneficiary> get beneficiaries;
@override
List<ExpenseCategory> get expenseCategories;
@override
List<Budget> get budgets;
List<BudgetsDto> get budgets;
@override
Budget? get activeBudget;
BudgetsDto? get activeBudget;
@override
List<Loan> get loans;
List<LoanDto> get loans;
@override
Loan? get activeLoan;
LoanDto? get activeLoan;
@override
bool get isDeletingAccount;
@override

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:okane/database/collections/template.dart';
import 'package:okane/database/sqlite.dart';
import 'package:okane/screen.dart';
import 'package:okane/ui/state/core.dart';
@@ -47,8 +47,10 @@ Future<T?> showDialogOrModal<T>({
};
}
Future<TransactionTemplate?> selectTransactionTemplate(BuildContext context) {
return showDialogOrModal<TransactionTemplate>(
Future<TransactionTemplateDto?> selectTransactionTemplate(
BuildContext context,
) {
return showDialogOrModal<TransactionTemplateDto>(
context: context,
builder: (context) {
return BlocBuilder<CoreCubit, CoreState>(
@@ -64,7 +66,7 @@ Future<TransactionTemplate?> selectTransactionTemplate(BuildContext context) {
itemCount: state.transactionTemplates.length,
itemBuilder:
(context, index) => ListTile(
title: Text(state.transactionTemplates[index].name),
title: Text(state.transactionTemplates[index].template.name),
onTap: () {
Navigator.of(
context,
@@ -142,3 +144,17 @@ Future<bool> confirm(BuildContext context, String title, String body) async {
);
return result ?? false;
}
bool isTransactionDue(RecurringTransaction r, DateTime now) {
if (r.lastExecution == null) {
return true;
}
final expectedNextExecution = r.lastExecution!.add(Duration(days: r.days));
if (now.isAfter(expectedNextExecution)) {
return true;
}
return now.difference(expectedNextExecution).inDays.abs() <=
(r.days * 0.5).toInt();
}

View File

@@ -1,7 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:okane/database/collections/expense_category.dart';
import 'package:okane/database/database.dart';
import 'package:okane/i18n/strings.g.dart';
import 'package:okane/ui/state/core.dart';
@@ -50,12 +48,14 @@ class AddExpenseCategoryState extends State<AddExpenseCategory> {
Spacer(),
OutlinedButton(
onPressed: () async {
// TODO
/*
final category =
ExpenseCategory()
..name = _categoryNameController.text;
await upsertExpenseCategory(category);
_categoryNameController.text = "";
Navigator.of(context).pop(category);
Navigator.of(context).pop(category);*/
},
child: Text(t.modals.add),
),

View File

@@ -1,12 +1,8 @@
import 'package:drift/drift.dart' show Value;
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/collections/account.dart';
import 'package:okane/database/collections/beneficiary.dart';
import 'package:okane/database/collections/expense_category.dart';
import 'package:okane/database/collections/recurrent.dart';
import 'package:okane/database/collections/template.dart';
import 'package:okane/database/database.dart';
import 'package:okane/database/sqlite.dart';
import 'package:okane/i18n/strings.g.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/transaction.dart';
@@ -68,6 +64,7 @@ class _AddTransactionTemplateWidgetState
return;
}
final db = GetIt.I.get<OkaneDatabase>();
Beneficiary? beneficiary = _selectedBeneficiary?.item;
if (beneficiary == null ||
getBeneficiaryName(beneficiary) != beneficiaryName) {
@@ -101,12 +98,12 @@ class _AddTransactionTemplateWidgetState
if (result == null || !result) {
return;
}
beneficiary =
Beneficiary()
..name = beneficiaryName
..type = BeneficiaryType.other;
await upsertBeneficiary(beneficiary);
beneficiary = await db.beneficiariesDao.upsertBeneficiary(
BeneficiariesCompanion(
name: Value(beneficiaryName),
type: Value(BeneficiaryType.other),
),
);
}
final factor = switch (_selectedDirection) {
@@ -114,15 +111,16 @@ class _AddTransactionTemplateWidgetState
TransactionDirection.receive => 1,
};
final amount = factor * double.parse(_amountTextController.text).abs();
final template =
TransactionTemplate()
..name = _templateNameController.text
..account.value = widget.activeAccountItem
..beneficiary.value = beneficiary
..expenseCategory.value = _expenseCategory
..recurring = _isRecurring
..amount = amount;
await upsertTransactionTemplate(template);
final template = await db.transactionTemplatesDao.upsertTemplate(
TransactionTemplatesCompanion(
name: Value(_templateNameController.text),
accountId: Value(widget.activeAccountItem.id),
beneficiaryId: Value(beneficiary.id),
expenseCategoryId: Value(_expenseCategory?.id),
recurring: Value(_isRecurring),
amount: Value(amount),
),
);
if (_isRecurring) {
final days = switch (_selectedPeriod) {
@@ -131,13 +129,14 @@ class _AddTransactionTemplateWidgetState
Period.months => _periodSize * 31,
Period.years => _periodSize * 365,
};
final recurringTransaction =
RecurringTransaction()
..account.value = widget.activeAccountItem
..template.value = template
..lastExecution = null
..days = days;
await upsertRecurringTransaction(recurringTransaction);
await db.recurringTransactionsDao.upsertRecurringTransaction(
RecurringTransactionsCompanion(
accountId: Value(widget.activeAccountItem.id),
templateId: Value(template.id),
lastExecution: Value(null),
days: Value(days),
),
);
}
widget.onAdd();
}
@@ -190,8 +189,7 @@ class _AddTransactionTemplateWidgetState
.where((el) {
final bloc = GetIt.I.get<CoreCubit>();
if (el.type == BeneficiaryType.account) {
return el.account.value?.id !=
bloc.activeAccount?.id;
return el.accountId != bloc.activeAccount?.id;
}
return true;
})

View File

@@ -1,12 +1,8 @@
import 'package:drift/drift.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/collections/account.dart';
import 'package:okane/database/collections/beneficiary.dart';
import 'package:okane/database/collections/expense_category.dart';
import 'package:okane/database/collections/template.dart';
import 'package:okane/database/collections/transaction.dart';
import 'package:okane/database/database.dart';
import 'package:okane/database/sqlite.dart';
import 'package:okane/i18n/strings.g.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/transaction.dart';
@@ -14,14 +10,14 @@ import 'package:okane/ui/utils.dart';
import 'package:okane/ui/widgets/add_expense_category.dart';
import 'package:searchfield/searchfield.dart';
typedef AddTransactionCallback = void Function(Transaction);
typedef AddTransactionCallback = void Function(TransactionDto);
class AddTransactionWidget extends StatefulWidget {
final AddTransactionCallback onAdd;
final Account activeAccountItem;
final TransactionTemplate? template;
final TransactionTemplateDto? template;
const AddTransactionWidget({
super.key,
@@ -51,6 +47,8 @@ class _AddTransactionWidgetState extends State<AddTransactionWidget> {
void initState() {
super.initState();
// TODO
/*
if (widget.template != null) {
_selectedDirection =
widget.template!.amount > 0
@@ -58,12 +56,12 @@ class _AddTransactionWidgetState extends State<AddTransactionWidget> {
: TransactionDirection.send;
_amountTextController.text = widget.template!.amount.toString();
_beneficiaryTextController.text =
widget.template!.beneficiary.value!.name;
widget.template!;
_selectedBeneficiary = SearchFieldListItem(
getBeneficiaryName(widget.template!.beneficiary.value!),
item: widget.template!.beneficiary.value!,
);
}
}*/
}
String getBeneficiaryName(Beneficiary item) {
@@ -81,6 +79,7 @@ class _AddTransactionWidgetState extends State<AddTransactionWidget> {
return;
}
final db = GetIt.I.get<OkaneDatabase>();
Beneficiary? beneficiary = _selectedBeneficiary?.item;
if (beneficiary == null ||
getBeneficiaryName(beneficiary) != beneficiaryName) {
@@ -115,11 +114,12 @@ class _AddTransactionWidgetState extends State<AddTransactionWidget> {
return;
}
beneficiary =
Beneficiary()
..name = beneficiaryName
..type = BeneficiaryType.other;
await upsertBeneficiary(beneficiary);
beneficiary = await db.beneficiariesDao.upsertBeneficiary(
BeneficiariesCompanion(
name: Value(beneficiaryName),
type: Value(BeneficiaryType.other),
),
);
}
final factor = switch (_selectedDirection) {
@@ -127,30 +127,38 @@ class _AddTransactionWidgetState extends State<AddTransactionWidget> {
TransactionDirection.receive => 1,
};
final amount = factor * double.parse(_amountTextController.text).abs();
final transaction =
Transaction()
..account.value = widget.activeAccountItem
..beneficiary.value = beneficiary
..amount = amount
..tags = []
..expenseCategory.value = _expenseCategory
..date = _selectedDate;
await upsertTransaction(transaction);
final rawTransaction = TransactionsCompanion(
accountId: Value(widget.activeAccountItem.id),
beneficiaryId: Value(beneficiary.id),
amount: Value(amount),
// tags: [],
expenseCategoryId: Value(_expenseCategory?.id),
date: Value(_selectedDate),
);
final transaction = await db.transactionsDao.upsertTransaction(
rawTransaction,
);
if (beneficiary.type == BeneficiaryType.account) {
final otherTransaction =
Transaction()
..account.value = beneficiary.account.value!
..beneficiary.value = await getAccountBeneficiary(
widget.activeAccountItem,
)
..date = _selectedDate
..expenseCategory.value = _expenseCategory
..amount = -1 * amount;
await upsertTransaction(otherTransaction);
final otherTransaction = rawTransaction.copyWith(
accountId: Value(beneficiary.accountId!),
beneficiaryId: Value(
(await db.beneficiariesDao.getAccountBeneficiary(
widget.activeAccountItem,
)).id,
),
amount: Value(-1 * rawTransaction.amount.value),
);
await db.transactionsDao.upsertTransaction(otherTransaction);
}
widget.onAdd(transaction);
widget.onAdd(
TransactionDto(
transaction: transaction,
beneficiary: beneficiary,
expenseCategory: _expenseCategory,
),
);
}
@override
@@ -167,17 +175,17 @@ class _AddTransactionWidgetState extends State<AddTransactionWidget> {
return;
}
_amountTextController.text = template.amount.toString();
_amountTextController.text = template.template.amount.toString();
_selectedDirection =
template.amount > 0
template.template.amount > 0
? TransactionDirection.receive
: TransactionDirection.send;
_selectedBeneficiary = SearchFieldListItem(
getBeneficiaryName(template.beneficiary.value!),
item: template.beneficiary.value!,
getBeneficiaryName(template.beneficiary),
item: template.beneficiary,
);
_beneficiaryTextController.text = getBeneficiaryName(
template.beneficiary.value!,
template.beneficiary,
);
},
child: Text(t.pages.transactions.addTransaction.useTemplate),
@@ -216,7 +224,7 @@ class _AddTransactionWidgetState extends State<AddTransactionWidget> {
.where((el) {
final bloc = GetIt.I.get<CoreCubit>();
if (el.type == BeneficiaryType.account) {
return el.account.value?.id.toInt() ==
return el.accountId ==
bloc.activeAccount?.id.toInt();
}
return true;

View File

@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:okane/database/collections/transaction.dart';
import 'package:okane/database/sqlite.dart';
import 'package:okane/ui/utils.dart';
import 'package:okane/ui/widgets/image_wrapper.dart';
@@ -13,7 +13,7 @@ class TransactionCard extends StatelessWidget {
this.subtitle,
});
final Transaction transaction;
final TransactionDto transaction;
final VoidCallback onTap;
@@ -24,19 +24,22 @@ class TransactionCard extends StatelessWidget {
child: ListTile(
onTap: onTap,
leading: ImageWrapper(
title: transaction.beneficiary.value!.name,
path: transaction.beneficiary.value!.imagePath,
title: transaction.beneficiary.name,
path: transaction.beneficiary.imagePath,
),
trailing: Text(formatDateTime(transaction.date)),
title: Text(transaction.beneficiary.value!.name),
trailing: Text(formatDateTime(transaction.transaction.date)),
title: Text(transaction.beneficiary.name),
subtitle: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
formatCurrency(transaction.amount),
formatCurrency(transaction.transaction.amount),
style: TextStyle(
color: transaction.amount < 0 ? Colors.red : Colors.green,
color:
transaction.transaction.amount < 0
? Colors.red
: Colors.green,
),
),
if (subtitle != null) subtitle!,