305 lines
14 KiB
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);
|
|
}
|
|
}
|
|
}
|