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),
),
),