Skip to content

Commit

Permalink
Add upcoming sip payment page
Browse files Browse the repository at this point in the history
  • Loading branch information
praslnx8 committed Nov 4, 2024
1 parent 9e5e56c commit bd33fca
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 6 deletions.
3 changes: 3 additions & 0 deletions lib/app_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:wealth_wave/ui/pages/expense_tags_page.dart';
import 'package:wealth_wave/ui/pages/goal_page.dart';
import 'package:wealth_wave/ui/pages/investment_page.dart';
import 'package:wealth_wave/ui/pages/main_page.dart';
import 'package:wealth_wave/ui/pages/upcoming_sips_page.dart';

class AppRouter {
static Widget route(String path) {
Expand All @@ -19,6 +20,8 @@ class AppRouter {
return const BasketsPage();
} else if(NavPath.isExpenseTagsPagePath(uri.pathSegments)) {
return const ExpenseTagsPage();
} else if(NavPath.isUpcomingSipPath(uri.pathSegments)) {
return const UpcomingSipsPage();
}
return const MainPage(path: []);
}
Expand Down
29 changes: 25 additions & 4 deletions lib/domain/models/sip.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,36 @@ class Sip {

List<Payment> getFuturePayment({required final DateTime till}) {
final List<Payment> payments = [];
DateTime nextPaymentDate = executedTill ?? startDate;
DateTime nextPaymentDate = _getInitialPaymentDate();

while (nextPaymentDate.compareTo(till) <= 0 &&
(endDate == null || nextPaymentDate.compareTo(endDate!) < 0)) {
while (_shouldAddPayment(nextPaymentDate, till)) {
payments.add(Payment.from(amount: amount, createdOn: nextPaymentDate));
nextPaymentDate = _getNextOccurrenceDateTime(nextPaymentDate, frequency);
}
return payments;
}

Payment? getNextPayment() {
DateTime nextPaymentDate = _getInitialPaymentDate();

if (_shouldAddPayment(nextPaymentDate, endDate)) {
return Payment.from(amount: amount, createdOn: nextPaymentDate);
}

return null;
}

DateTime _getInitialPaymentDate() {
return executedTill != null
? _getNextOccurrenceDateTime(executedTill!, frequency)
: startDate;
}

bool _shouldAddPayment(DateTime nextPaymentDate, DateTime? till) {
return nextPaymentDate.compareTo(till ?? DateTime.now()) <= 0 &&
(endDate == null || nextPaymentDate.compareTo(endDate!) < 0);
}

DateTime _getNextOccurrenceDateTime(DateTime dateTime, Frequency frequency) {
switch (frequency) {
case Frequency.daily:
Expand All @@ -49,7 +69,8 @@ class Sip {
case Frequency.halfYearly:
return DateTime.utc(dateTime.year, dateTime.month + 6, dateTime.day);
case Frequency.yearly:
return DateTime.utc(dateTime.year + 1, dateTime.month + 6, dateTime.day);
return DateTime.utc(
dateTime.year + 1, dateTime.month + 6, dateTime.day);
default:
throw Exception('Invalid frequency');
}
Expand Down
3 changes: 3 additions & 0 deletions lib/ui/nav_path.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class NavPath {
static String goal({required final int id}) => '/goals/$id';
static const String baskets = '/baskets';
static const String expenseTags = '/expense-tags';
static const String upcomingSips = '/upcoming-sips';

static isMainPagePath(List<String> paths) => paths.isEmpty;

Expand All @@ -33,4 +34,6 @@ class NavPath {
static isBasketsPagePath(List<String> paths) => paths.length == 1 && paths[0] == 'baskets';

static isExpenseTagsPagePath(List<String> paths) => paths.length == 1 && paths[0] == 'expense-tags';

static isUpcomingSipPath(List<String> paths) => paths.length == 1 && paths[0] == 'upcoming-sips';
}
10 changes: 8 additions & 2 deletions lib/ui/pages/main_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@ class _MainPageState extends PageState<MainViewState, MainPage, MainPresenter> {
} else if (value == 2) {
Navigator.of(context).pushNamed(NavPath.expenseTags);
} else if (value == 3) {
presenter.performBackup();
Navigator.of(context).pushNamed(NavPath.upcomingSips);
} else if (value == 4) {
presenter.performBackup();
} else if (value == 5) {
presenter.performImportFile();
}
},
Expand All @@ -71,10 +73,14 @@ class _MainPageState extends PageState<MainViewState, MainPage, MainPresenter> {
),
const PopupMenuItem(
value: 3,
child: Text('Export'),
child: Text('Upcoming Sips'),
),
const PopupMenuItem(
value: 4,
child: Text('Export'),
),
const PopupMenuItem(
value: 5,
child: Text('Import'),
),
],
Expand Down
149 changes: 149 additions & 0 deletions lib/ui/pages/upcoming_sips_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import 'package:flutter/material.dart';
import 'package:wealth_wave/core/page_state.dart';
import 'package:wealth_wave/ui/presentation/baskets_presenter.dart';
import 'package:wealth_wave/ui/app_dimen.dart';
import 'package:wealth_wave/ui/presentation/upcoming_sips_presenter.dart';
import 'package:wealth_wave/ui/widgets/create_basket_dialog.dart';
import 'package:wealth_wave/utils/ui_utils.dart';

class UpcomingSipsPage extends StatefulWidget {
const UpcomingSipsPage({super.key});

@override
State<UpcomingSipsPage> createState() => _UpcomingSipsPage();
}

class _UpcomingSipsPage
extends PageState<UpcomingSipsViewState, UpcomingSipsPage, UpcomingSipsPresenter> {
@override
void initState() {
super.initState();
presenter.fetchUpcomingSips();
}

@override
Widget buildWidget(final BuildContext context, final UpcomingSipsViewState snapshot) {
List<UpcomingSipPaymentVO> upcomingSips = snapshot.upcomingSipPayments;
DateTime now = DateTime.now();
DateTime nextMonth = DateTime(now.year, now.month + 1, 1);
DateTime nextYear = DateTime(now.year + 1, 1, 1);

double currentMonthTotal = 0;
double nextMonthTotal = 0;
double currentYearTotal = 0;
double nextYearTotal = 0;

List<UpcomingSipPaymentVO> currentMonthSips = [];
List<UpcomingSipPaymentVO> nextMonthSips = [];
List<UpcomingSipPaymentVO> currentYearSips = [];
List<UpcomingSipPaymentVO> nextYearSips = [];

for (var sip in upcomingSips) {
if (sip.paymentDate.year == now.year) {
currentYearTotal += sip.amount;
currentYearSips.add(sip);
if (sip.paymentDate.month == now.month) {
currentMonthTotal += sip.amount;
currentMonthSips.add(sip);
}
}
if (sip.paymentDate.year == nextYear.year) {
nextYearTotal += sip.amount;
nextYearSips.add(sip);
}
if (sip.paymentDate.year == nextMonth.year && sip.paymentDate.month == nextMonth.month) {
nextMonthTotal += sip.amount;
nextMonthSips.add(sip);
}
}

return Scaffold(
appBar: AppBar(
title: const Text('Upcoming Sips'),
),
body: Padding(
padding: const EdgeInsets.all(AppDimen.defaultPadding),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSummaryCard(
title: 'Current Month',
total: currentMonthTotal,
sips: currentMonthSips,
),
const SizedBox(height: AppDimen.defaultPadding),
_buildSummaryCard(
title: 'Next Month',
total: nextMonthTotal,
sips: nextMonthSips,
),
const SizedBox(height: AppDimen.defaultPadding),
_buildSummaryCard(
title: 'Current Year',
total: currentYearTotal,
sips: currentYearSips,
),
const SizedBox(height: AppDimen.defaultPadding),
_buildSummaryCard(
title: 'Next Year',
total: nextYearTotal,
sips: nextYearSips,
),
],
),
),
),
);
}

Widget _buildSummaryCard({
required String title,
required double total,
required List<UpcomingSipPaymentVO> sips,
}) {
return Card(
margin: const EdgeInsets.all(AppDimen.defaultPadding),
child: Padding(
padding: const EdgeInsets.all(AppDimen.defaultPadding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('$title Summary', style: Theme.of(context).textTheme.headline6),
const SizedBox(height: AppDimen.defaultPadding),
Text('Total Amount: ${formatToCurrency(total)}'),
const SizedBox(height: AppDimen.defaultPadding),
...sips.map((sip) => _upcomingSipWidget(context: context, sipVo: sip)).toList(),
],
),
),
);
}

@override
UpcomingSipsPresenter initializePresenter() {
return UpcomingSipsPresenter();
}

Widget _upcomingSipWidget(
{required final BuildContext context, required final UpcomingSipPaymentVO sipVo}) {
return Card(
margin: const EdgeInsets.all(AppDimen.defaultPadding),
child: Padding(
padding: const EdgeInsets.all(AppDimen.defaultPadding),
child: OverflowBar(
alignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(sipVo.name),
Text('Amount: ${sipVo.amount}'),
Text('Next Payment Date: ${formatDate(sipVo.paymentDate)}')
],
)
],
),
));
}
}
54 changes: 54 additions & 0 deletions lib/ui/presentation/upcoming_sips_presenter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import 'package:wealth_wave/core/presenter.dart';
import 'package:wealth_wave/domain/models/investment.dart';
import 'package:wealth_wave/domain/models/payment.dart';
import 'package:wealth_wave/domain/services/investment_service.dart';
import 'package:wealth_wave/domain/services/sip_service.dart';

class UpcomingSipsPresenter extends Presenter<UpcomingSipsViewState> {
final InvestmentService _investmentService;
final SipService _sipService;

UpcomingSipsPresenter(
{final InvestmentService? investmentService,
final SipService? sipService})
: _investmentService = investmentService ?? InvestmentService(),
_sipService = sipService ?? SipService(),
super(UpcomingSipsViewState());

void fetchUpcomingSips() {
_investmentService.get().then((investments) {
_sipService.getAll().then((sips) {
final List<UpcomingSipPaymentVO> futurePayments = sips.expand((sip) {
Investment investment = investments.firstWhere(
(investment) => investment.id == sip.investmentId);
return sip.getFuturePayment(till: DateTime.now().add(const Duration(days: 365))).map((e) => UpcomingSipPaymentVO.from(investment: investment, payment: e));
}).toList();
futurePayments.sort((a, b) => a.paymentDate.compareTo(b.paymentDate));
updateViewState((viewState) {
viewState.upcomingSipPayments = futurePayments;
});
});
});
}
}

class UpcomingSipsViewState {
List<UpcomingSipPaymentVO> upcomingSipPayments = [];
}

class UpcomingSipPaymentVO {
final String name;
final double amount;
final DateTime paymentDate;

UpcomingSipPaymentVO._(
{required this.name, required this.amount, required this.paymentDate});

factory UpcomingSipPaymentVO.from(
{required final Investment investment, required final Payment payment}) {
return UpcomingSipPaymentVO._(
name: investment.name,
amount: payment.amount,
paymentDate: payment.createdOn);
}
}

0 comments on commit bd33fca

Please sign in to comment.