Add budgets
This commit is contained in:
@@ -111,20 +111,6 @@ class AccountListPageState extends State<AccountListPage> {
|
||||
child: UpcomingTransactionsCard(),
|
||||
),
|
||||
|
||||
/*
|
||||
BlocBuilder<CoreCubit, CoreState>(
|
||||
builder:
|
||||
(context, state) => Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: AccountBalanceGraphCard(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),*/
|
||||
Row(
|
||||
children: [
|
||||
Padding(padding: EdgeInsets.all(16), child: BreakdownCard()),
|
||||
|
||||
@@ -28,15 +28,17 @@ class TotalBalanceCard extends StatelessWidget {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text("Total balance"),
|
||||
Text(
|
||||
"Total balance",
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
FutureBuilder(
|
||||
future: _getTotalBalance(state.accounts),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return Text("...");
|
||||
}
|
||||
|
||||
return Text(formatCurrency(snapshot.data!));
|
||||
return Text(
|
||||
snapshot.hasData ? formatCurrency(snapshot.data!) : "...",
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:okane/database/collections/recurrent.dart';
|
||||
@@ -29,7 +27,12 @@ class UpcomingTransactionsCard extends StatelessWidget {
|
||||
: upcomingRaw.sublist(0, min(upcomingRaw.length, 3));
|
||||
final transactions =
|
||||
upcoming.isEmpty
|
||||
? [Text("No upcoming transactions")]
|
||||
? [
|
||||
Text(
|
||||
"No upcoming transactions",
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
]
|
||||
: upcoming
|
||||
.map(
|
||||
(t) => ListTile(
|
||||
@@ -59,7 +62,14 @@ class UpcomingTransactionsCard extends StatelessWidget {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: <Widget>[Text("Upcoming Transactions")] + transactions,
|
||||
children:
|
||||
<Widget>[
|
||||
Text(
|
||||
"Upcoming Transactions",
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
] +
|
||||
transactions,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
65
lib/ui/pages/budgets/add_budget.dart
Normal file
65
lib/ui/pages/budgets/add_budget.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
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/ui/state/core.dart';
|
||||
|
||||
class AddBudgetPopup extends StatefulWidget {
|
||||
final VoidCallback onDone;
|
||||
|
||||
const AddBudgetPopup({super.key, required this.onDone});
|
||||
|
||||
@override
|
||||
AddBudgetState createState() => AddBudgetState();
|
||||
}
|
||||
|
||||
class AddBudgetState extends State<AddBudgetPopup> {
|
||||
final _budgetNameEditController = TextEditingController();
|
||||
final _budgetIncomeEditController = TextEditingController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
decoration: InputDecoration(
|
||||
hintText: "Budget name",
|
||||
),
|
||||
controller: _budgetNameEditController,
|
||||
),
|
||||
|
||||
TextField(
|
||||
decoration: InputDecoration(
|
||||
hintText: "Income",
|
||||
),
|
||||
controller: _budgetIncomeEditController,
|
||||
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||
),
|
||||
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
OutlinedButton(
|
||||
onPressed: () async {
|
||||
if (_budgetNameEditController.text.isEmpty || _budgetIncomeEditController.text.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final bloc = GetIt.I.get<CoreCubit>();
|
||||
final budget = Budget()
|
||||
..name = _budgetNameEditController.text
|
||||
..period = BudgetPeriod.month
|
||||
..income = double.parse(_budgetIncomeEditController.text)
|
||||
..account.value = bloc.activeAccount!;
|
||||
await upsertBudget(budget);
|
||||
widget.onDone();
|
||||
},
|
||||
child: Text("Add"),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
85
lib/ui/pages/budgets/add_budget_item.dart
Normal file
85
lib/ui/pages/budgets/add_budget_item.dart
Normal file
@@ -0,0 +1,85 @@
|
||||
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/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;
|
||||
|
||||
const AddBudgetItemPopup({super.key, required this.onDone, required this.budget});
|
||||
|
||||
@override
|
||||
AddBudgetItemState createState() => AddBudgetItemState();
|
||||
}
|
||||
|
||||
class AddBudgetItemState extends State<AddBudgetItemPopup> {
|
||||
final _budgetItemAmountEditController = TextEditingController();
|
||||
ExpenseCategory? _expenseCategory;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text("Expense category"),
|
||||
|
||||
OutlinedButton(
|
||||
onPressed: () async {
|
||||
final category = await showDialogOrModal(
|
||||
context: context,
|
||||
builder: (_) => AddExpenseCategory(),
|
||||
);
|
||||
if (category == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() => _expenseCategory = category);
|
||||
},
|
||||
child: Text(_expenseCategory?.name ?? "None"),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
TextField(
|
||||
decoration: InputDecoration(
|
||||
hintText: "Amount",
|
||||
),
|
||||
controller: _budgetItemAmountEditController,
|
||||
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||
),
|
||||
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
OutlinedButton(
|
||||
onPressed: () async {
|
||||
if (_budgetItemAmountEditController.text.isEmpty || _expenseCategory == null) {
|
||||
return;
|
||||
}
|
||||
if (widget.budget.items.where((i) => i.expenseCategory.value!.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);
|
||||
widget.onDone();
|
||||
},
|
||||
child: Text("Add"),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
374
lib/ui/pages/budgets/budget_details.dart
Normal file
374
lib/ui/pages/budgets/budget_details.dart
Normal file
@@ -0,0 +1,374 @@
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.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/ui/pages/account/breakdown_card.dart';
|
||||
import 'package:okane/ui/pages/budgets/add_budget_item.dart';
|
||||
import 'package:okane/ui/state/core.dart';
|
||||
import 'package:okane/ui/utils.dart';
|
||||
|
||||
class BudgetDetailsPage extends StatelessWidget {
|
||||
final bool isPage;
|
||||
|
||||
const BudgetDetailsPage({this.isPage = false, super.key});
|
||||
|
||||
static MaterialPageRoute<void> get mobileRoute =>
|
||||
MaterialPageRoute(builder: (_) => BudgetDetailsPage(isPage: true));
|
||||
|
||||
void _addBudgetItem(BuildContext context, CoreState state) {
|
||||
showDialogOrModal(
|
||||
context: context,
|
||||
builder:
|
||||
(_) => AddBudgetItemPopup(
|
||||
budget: state.activeBudget!,
|
||||
onDone: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Column(
|
||||
children: [
|
||||
if (isPage)
|
||||
SizedBox(
|
||||
height: 50,
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.arrow_back),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
BlocBuilder<CoreCubit, CoreState>(
|
||||
builder: (context, state) {
|
||||
if (state.activeBudget == null) {
|
||||
return Text("No budget selected");
|
||||
}
|
||||
|
||||
if (state.activeBudget!.items.isEmpty) {
|
||||
return Row(
|
||||
children: [
|
||||
Text("No budget items added"),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 16),
|
||||
child: IconButton(
|
||||
onPressed: () => _addBudgetItem(context, state),
|
||||
icon: Icon(Icons.add),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
final bloc = GetIt.I.get<CoreCubit>();
|
||||
final today = DateTime.now();
|
||||
return FutureBuilder(
|
||||
future: getTransactionsInTimeframe(
|
||||
bloc.activeAccount!,
|
||||
today,
|
||||
TransactionQueryDateOption.thisMonth,
|
||||
),
|
||||
builder: (context, snapshot) {
|
||||
final daysLeft = switch (state.activeBudget!.period) {
|
||||
BudgetPeriod.month =>
|
||||
monthEnding(today).difference(today).inDays,
|
||||
};
|
||||
|
||||
if (!snapshot.hasData) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
"Budget items",
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: state.activeBudget!.items.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = state.activeBudget!.items.elementAt(
|
||||
index,
|
||||
);
|
||||
final amount = formatCurrency(item.amount);
|
||||
return ListTile(
|
||||
title: Text(
|
||||
"${item.expenseCategory.value!.name} ($amount)",
|
||||
),
|
||||
subtitle: Text("..."),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
final spending = <String, double>{};
|
||||
for (final t in snapshot.data!) {
|
||||
if (t.expenseCategory.value == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
spending.update(
|
||||
t.expenseCategory.value!.name,
|
||||
(value) => value + t.amount,
|
||||
ifAbsent: () => t.amount,
|
||||
);
|
||||
}
|
||||
|
||||
final budgetTotal = state.activeBudget!.items
|
||||
.map((i) => i.amount)
|
||||
.reduce((acc, val) => acc + val);
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 100,
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: SizedBox(
|
||||
height: 100,
|
||||
width: 150,
|
||||
child: Card(
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"Days left",
|
||||
textAlign: TextAlign.center,
|
||||
style:
|
||||
Theme.of(
|
||||
context,
|
||||
).textTheme.titleLarge,
|
||||
),
|
||||
Text(
|
||||
daysLeft.toString(),
|
||||
textAlign: TextAlign.center,
|
||||
style:
|
||||
Theme.of(
|
||||
context,
|
||||
).textTheme.bodyLarge,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: SizedBox(
|
||||
height: 100,
|
||||
width: 150,
|
||||
child: Card(
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"Budget total",
|
||||
textAlign: TextAlign.center,
|
||||
style:
|
||||
Theme.of(
|
||||
context,
|
||||
).textTheme.titleLarge,
|
||||
),
|
||||
Text(
|
||||
formatCurrency(budgetTotal),
|
||||
textAlign: TextAlign.center,
|
||||
style:
|
||||
Theme.of(
|
||||
context,
|
||||
).textTheme.bodyLarge,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: SizedBox(
|
||||
child: Card(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 8),
|
||||
child: Text(
|
||||
"Budget breakdown",
|
||||
style:
|
||||
Theme.of(context).textTheme.titleLarge,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: SizedBox(
|
||||
width: 150,
|
||||
height: 150,
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: PieChart(
|
||||
PieChartData(
|
||||
borderData: FlBorderData(
|
||||
show: false,
|
||||
),
|
||||
sectionsSpace: 0,
|
||||
centerSpaceRadius: 35,
|
||||
sections:
|
||||
state.activeBudget!.items
|
||||
.map(
|
||||
(
|
||||
i,
|
||||
) => PieChartSectionData(
|
||||
value: i.amount.abs(),
|
||||
title: formatCurrency(
|
||||
i.amount.abs(),
|
||||
),
|
||||
titleStyle: TextStyle(
|
||||
fontWeight:
|
||||
FontWeight.bold,
|
||||
),
|
||||
radius: 40,
|
||||
color: colorHash(
|
||||
i
|
||||
.expenseCategory
|
||||
.value!
|
||||
.name,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children:
|
||||
state.activeBudget!.items
|
||||
.map(
|
||||
(i) => LegendItem(
|
||||
text:
|
||||
i
|
||||
.expenseCategory
|
||||
.value!
|
||||
.name,
|
||||
color: colorHash(
|
||||
i
|
||||
.expenseCategory
|
||||
.value!
|
||||
.name,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
"Budget items",
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 16),
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.add),
|
||||
onPressed: () => _addBudgetItem(context, state),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: state.activeBudget!.items.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = state.activeBudget!.items.elementAt(
|
||||
index,
|
||||
);
|
||||
final amount = formatCurrency(item.amount);
|
||||
final spent =
|
||||
spending[item.expenseCategory.value!.name];
|
||||
final left =
|
||||
spent == null
|
||||
? item.amount
|
||||
: item.amount + spent;
|
||||
final subtitleText =
|
||||
left < 0
|
||||
? "${formatCurrency(left)} over"
|
||||
: "${formatCurrency(left)} left";
|
||||
return ListTile(
|
||||
title: Text(
|
||||
"${item.expenseCategory.value!.name} ($amount)",
|
||||
),
|
||||
subtitle: Text(
|
||||
subtitleText,
|
||||
style: TextStyle(
|
||||
color: left < 0 ? Colors.red : Colors.green,
|
||||
),
|
||||
),
|
||||
trailing: IconButton(
|
||||
icon: Icon(Icons.delete),
|
||||
onPressed: () {},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
67
lib/ui/pages/budgets/budgets.dart
Normal file
67
lib/ui/pages/budgets/budgets.dart
Normal file
@@ -0,0 +1,67 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:okane/screen.dart';
|
||||
import 'package:okane/ui/pages/budgets/add_budget.dart';
|
||||
import 'package:okane/ui/state/core.dart';
|
||||
import 'package:okane/ui/utils.dart';
|
||||
|
||||
class BudgetListPage extends StatelessWidget {
|
||||
const BudgetListPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<CoreCubit, CoreState>(
|
||||
builder: (context, state) {
|
||||
if (state.budgets.isEmpty) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text("No budgets"),
|
||||
OutlinedButton(
|
||||
onPressed: () {
|
||||
showDialogOrModal(
|
||||
context: context,
|
||||
builder:
|
||||
(_) => AddBudgetPopup(
|
||||
onDone: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text("Add"),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
itemCount: state.budgets.length,
|
||||
itemBuilder:
|
||||
(context, index) => ListTile(
|
||||
title: Text(state.budgets[index].name),
|
||||
selected: state.budgets[index] == state.activeBudget,
|
||||
trailing: IconButton(
|
||||
icon: Icon(
|
||||
Icons.delete,
|
||||
color: Colors.redAccent,
|
||||
),
|
||||
onPressed: () {
|
||||
// TODO
|
||||
},
|
||||
),
|
||||
onTap: () {
|
||||
GetIt.I.get<CoreCubit>().setActiveBudget(
|
||||
state.budgets[index],
|
||||
);
|
||||
if (getScreenSize(context) == ScreenSize.small) {
|
||||
Navigator.of(context).pushNamed("/budgets/details");
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -47,72 +47,89 @@ class TransactionDetailsPage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final widget = BlocBuilder<CoreCubit, CoreState>(
|
||||
builder: (context, state) {
|
||||
if (state.activeTransaction == null) {
|
||||
return Text("No transaction selected");
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: ListView(
|
||||
children: [
|
||||
Row(
|
||||
return Scaffold(
|
||||
body: Column(
|
||||
children: [
|
||||
if (isPage)
|
||||
SizedBox(
|
||||
height: 50,
|
||||
child: Row(
|
||||
children: [
|
||||
StreamBuilder(
|
||||
stream: watchBeneficiaryObject(
|
||||
state.activeTransaction!.beneficiary.value!.id,
|
||||
),
|
||||
builder: (context, snapshot) {
|
||||
final obj =
|
||||
snapshot.data ??
|
||||
state.activeTransaction!.beneficiary.value!;
|
||||
return ImageWrapper(
|
||||
title: obj.name,
|
||||
path: obj.imagePath,
|
||||
onTap: () => _updateBeneficiaryIcon(obj),
|
||||
);
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 8),
|
||||
child: Text(
|
||||
state.activeTransaction!.beneficiary.value!.name,
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
IconButton(
|
||||
icon: Icon(Icons.edit),
|
||||
icon: Icon(Icons.arrow_back),
|
||||
onPressed: () {
|
||||
// TODO: Implement
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
children:
|
||||
state.activeTransaction!.tags
|
||||
.map((tag) => Chip(label: Text(tag)))
|
||||
.toList(),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
state.activeTransaction!.amount > 0
|
||||
? Icon(Icons.add)
|
||||
: Icon(Icons.remove),
|
||||
Text(formatCurrency(state.activeTransaction!.amount)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
),
|
||||
Expanded(
|
||||
child: BlocBuilder<CoreCubit, CoreState>(
|
||||
builder: (context, state) {
|
||||
if (state.activeTransaction == null) {
|
||||
return Text("No transaction selected");
|
||||
}
|
||||
|
||||
if (isPage) {
|
||||
return Scaffold(body: widget);
|
||||
}
|
||||
return widget;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: ListView(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
StreamBuilder(
|
||||
stream: watchBeneficiaryObject(
|
||||
state.activeTransaction!.beneficiary.value!.id,
|
||||
),
|
||||
builder: (context, snapshot) {
|
||||
final obj =
|
||||
snapshot.data ??
|
||||
state.activeTransaction!.beneficiary.value!;
|
||||
return ImageWrapper(
|
||||
title: obj.name,
|
||||
path: obj.imagePath,
|
||||
onTap: () => _updateBeneficiaryIcon(obj),
|
||||
);
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 8),
|
||||
child: Text(
|
||||
state.activeTransaction!.beneficiary.value!.name,
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
IconButton(
|
||||
icon: Icon(Icons.edit),
|
||||
onPressed: () {
|
||||
// TODO: Implement
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
children:
|
||||
state.activeTransaction!.tags
|
||||
.map((tag) => Chip(label: Text(tag)))
|
||||
.toList(),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
state.activeTransaction!.amount > 0
|
||||
? Icon(Icons.add)
|
||||
: Icon(Icons.remove),
|
||||
Text(formatCurrency(state.activeTransaction!.amount)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ 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/database.dart';
|
||||
import 'package:okane/screen.dart';
|
||||
import 'package:okane/ui/pages/account/balance_graph_card.dart';
|
||||
import 'package:okane/ui/state/core.dart';
|
||||
|
||||
Reference in New Issue
Block a user