rluv_client/lib/features/budget/widgets/add_transaction_dialog.dart

305 lines
14 KiB
Dart

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:rluv/global/utils.dart';
import '../../../global/api.dart';
import '../../../global/store.dart';
import '../../../global/styles.dart';
import '../../../global/widgets/ui_button.dart';
import '../../../models/budget_category_model.dart';
import '../../../models/transaction_model.dart';
import '../../../models/user.dart';
class TransactionDialog extends ConsumerStatefulWidget {
const TransactionDialog({super.key, this.transaction});
final Transaction? transaction;
@override
ConsumerState<TransactionDialog> createState() => _AddTransactionDialogState();
}
class _AddTransactionDialogState extends ConsumerState<TransactionDialog> {
late DateTime selectedDate;
late final TextEditingController amountController;
late final TextEditingController memoController;
User? user;
final amountFocusNode = FocusNode();
final memoFocusNode = FocusNode();
TransactionType transactionType = TransactionType.expense;
late BudgetCategory selectedBudgetCategory;
@override
void initState() {
if (widget.transaction != null) {
amountController = TextEditingController(text: widget.transaction!.amount.toString());
memoController = TextEditingController(text: widget.transaction!.memo);
transactionType = widget.transaction!.type;
selectedDate = widget.transaction!.date;
} else {
amountController = TextEditingController();
memoController = TextEditingController();
selectedDate = DateTime.now();
}
final categories = ref.read(budgetCategoriesProvider);
if (categories.isNotEmpty) {
selectedBudgetCategory = categories.first;
}
final u = ref.read(userProvider);
if (u == null) {
WidgetsBinding.instance.addPostFrameCallback((_) => ref.read(jwtProvider.notifier).revokeToken());
} else {
user = u;
}
super.initState();
}
@override
Widget build(BuildContext context) {
if (user == null) return Container();
final List<BudgetCategory> budgetCategories = ref.read(budgetCategoriesProvider);
return SizedBox(
width: MediaQuery.of(context).size.width * Styles.dialogScreenWidthFactor,
child: budgetCategories.isEmpty
? const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'Add budget categories to sort your transactions',
textAlign: TextAlign.center,
),
)
: Padding(
padding: const EdgeInsets.all(18.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.transaction == null)
Padding(
padding: const EdgeInsets.only(bottom: 8.0, left: 8.0),
child: Row(children: [
if (budgetCategories.isNotEmpty)
InkWell(
onTap: transactionType == TransactionType.expense
? null
: () {
setState(() => transactionType = TransactionType.expense);
},
child: AnimatedContainer(
height: 38,
width: 80,
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(8.0), topRight: Radius.circular(8.0)),
color: transactionType == TransactionType.expense
? Styles.lavender
: Styles.washedStone),
duration: const Duration(milliseconds: 300),
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Text('Expense', style: TextStyle(fontSize: 16)),
),
),
),
InkWell(
onTap: transactionType == TransactionType.income
? null
: () {
setState(() => transactionType = TransactionType.income);
},
child: AnimatedContainer(
height: 38,
width: 80,
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(8.0), topRight: Radius.circular(8.0)),
color:
transactionType == TransactionType.income ? Styles.lavender : Styles.washedStone),
duration: const Duration(milliseconds: 300),
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Text('Income', style: TextStyle(fontSize: 16)),
),
),
),
]),
),
Row(
children: [
const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'Amount:',
style: TextStyle(fontSize: 18.0),
),
),
Container(
width: 120,
height: 40,
decoration: Styles.boxLavenderBubble,
child: TextFormField(
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 18.0),
cursorColor: Styles.blushingPink,
decoration: Styles.inputLavenderBubble(),
focusNode: amountFocusNode,
controller: amountController,
onFieldSubmitted: (_) {
memoFocusNode.requestFocus();
},
),
),
],
),
if (budgetCategories.isEmpty)
const Padding(
padding: EdgeInsets.all(18.0),
child: Text(
'Add budget categories to sort your transactions',
textAlign: TextAlign.center,
),
),
if (budgetCategories.isNotEmpty)
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: transactionType == TransactionType.income
? const SizedBox(height: 12.5)
: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Row(
children: [
const Padding(
padding: EdgeInsets.only(right: 28.0),
child: Text(
'Category:',
style: TextStyle(fontSize: 18.0),
),
),
DropdownButton<BudgetCategory>(
dropdownColor: Styles.lavender,
value: selectedBudgetCategory,
items: budgetCategories
.map(
(e) => DropdownMenuItem(
value: e,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: 18,
width: 18,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.black, width: 1.5),
color: e.color,
),
),
const SizedBox(width: 12),
Text(e.name),
],
),
),
)
.toList(),
onChanged: (BudgetCategory? value) {
if (value != null) {
if (kDebugMode) {
print('${value.name} selected');
}
setState(() => selectedBudgetCategory = value);
}
},
),
],
),
),
),
const Padding(
padding: EdgeInsets.all(8.0),
child: Row(
children: [
Text(
'Memo',
style: TextStyle(fontSize: 18.0),
),
],
),
),
Container(
width: MediaQuery.of(context).size.width * 0.65,
height: 100,
decoration: Styles.boxLavenderBubble,
child: TextFormField(
style: const TextStyle(fontSize: 18.0),
maxLines: 4,
focusNode: memoFocusNode,
textAlign: TextAlign.left,
cursorColor: Styles.blushingPink,
controller: memoController,
decoration: Styles.inputLavenderBubble(),
onFieldSubmitted: (_) {},
),
),
UiButton(
showLoading: true,
text: 'ADD',
color: Styles.lavender,
onPressed: () => submitTransaction().then((_) {
if (mounted) {
Navigator.pop(context);
}
showSnack(
ref: ref,
text: widget.transaction != null ? 'Transaction updated!' : 'Transaction added!',
type: SnackType.success,
);
}),
),
],
),
));
}
Future submitTransaction() async {
Map<String, dynamic>? data;
if (widget.transaction != null) {
data = await ref.read(apiProvider.notifier).put(
path: 'transaction',
data: Transaction.copyWith(widget.transaction!, {
'memo': memoController.text.isNotEmpty ? memoController.text : null,
'amount': double.parse(amountController.text),
'budget_category_id': transactionType == TransactionType.income ? null : selectedBudgetCategory.id,
}).toJson());
} else {
data = await ref.read(apiProvider.notifier).post(
path: 'transaction',
data: Transaction(
amount: double.parse(amountController.text),
createdByUserId: user!.id!,
budgetCategoryId: transactionType == TransactionType.income ? null : selectedBudgetCategory.id,
budgetId: user!.budgetId,
date: DateTime.now(),
type: transactionType,
memo: memoController.text.isNotEmpty ? memoController.text : null)
.toJson());
}
final success = data != null ? data['success'] as bool : false;
if (success) {
if (widget.transaction != null) {
ref.read(dashboardProvider.notifier).update({'transactions': data});
} else {
ref.read(dashboardProvider.notifier).add({'transactions': data});
}
} else {
showSnack(
ref: ref,
text: widget.transaction != null ? 'Failed to edit transaction' : 'Failed to add transaction',
type: SnackType.error);
}
}
}