Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions lib/presentation/screens/subscriptions_screen.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import 'package:flutter/cupertino.dart';
import 'dart:async';

import 'package:flutter/cupertino.dart';
import 'package:subctrl/application/app_dependencies.dart';
import 'package:subctrl/domain/entities/notification_reminder_option.dart';
import 'package:subctrl/domain/entities/subscription.dart';
import 'package:subctrl/presentation/l10n/app_localizations.dart';
import 'package:subctrl/presentation/theme/app_theme.dart';
import 'package:subctrl/presentation/theme/theme_preference.dart';
import 'package:subctrl/domain/entities/notification_reminder_option.dart';
import 'package:subctrl/presentation/types/settings_callbacks.dart';
import 'package:subctrl/presentation/viewmodels/subscriptions_view_model.dart';
import 'package:subctrl/presentation/widgets/add_subscription_sheet.dart';
Expand Down Expand Up @@ -49,14 +50,16 @@ class SubscriptionsScreen extends StatefulWidget {
State<SubscriptionsScreen> createState() => _SubscriptionsScreenState();
}

class _SubscriptionsScreenState extends State<SubscriptionsScreen> {
class _SubscriptionsScreenState extends State<SubscriptionsScreen>
with WidgetsBindingObserver {
late final SubscriptionsViewModel _viewModel;
late final ScrollController _scrollController;
final TextEditingController _searchController = TextEditingController();

@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_scrollController = ScrollController();
_viewModel = SubscriptionsViewModel(
watchSubscriptionsUseCase: widget.dependencies.watchSubscriptionsUseCase,
Expand Down Expand Up @@ -95,9 +98,17 @@ class _SubscriptionsScreenState extends State<SubscriptionsScreen> {
_scrollController.dispose();
_searchController.dispose();
_viewModel.dispose();
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}

@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
unawaited(_viewModel.refreshOverdueNextPayments());
}
}

@override
void didUpdateWidget(covariant SubscriptionsScreen oldWidget) {
super.didUpdateWidget(oldWidget);
Expand Down
7 changes: 7 additions & 0 deletions lib/presentation/viewmodels/subscriptions_view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,13 @@ class SubscriptionsViewModel extends ChangeNotifier {
return _refreshCurrencyRatesForSubscriptions();
}

Future<void> refreshOverdueNextPayments() async {
if (_isLoadingSubscriptions || _subscriptions.isEmpty) {
return;
}
await _refreshOverdueNextPayments(_subscriptions);
}

Future<void> ensureCurrenciesLoaded() async {
if (!_isLoadingCurrencies && _currencies.isNotEmpty) {
return;
Expand Down
51 changes: 51 additions & 0 deletions test/presentation/viewmodels/subscriptions_view_model_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,57 @@ void main() {
).called(1);
});

test('refreshOverdueNextPayments uses current subscriptions', () async {
final subscription = Subscription(
id: 2,
name: 'HBO',
amount: 12,
currency: 'usd',
cycle: BillingCycle.monthly,
purchaseDate: DateTime(2024, 1, 1),
);
subscriptionsController.add([subscription]);
tagsController.add(const []);
currenciesController.add(const []);
ratesController.add(const []);
await Future<void>.delayed(Duration.zero);

clearInteractions(refreshOverdueNextPaymentsUseCase);
await viewModel.refreshOverdueNextPayments();

verify(
() => refreshOverdueNextPaymentsUseCase(
any(that: predicate<List<Subscription>>((subs) => subs.length == 1)),
),
).called(1);
});

test(
'refreshOverdueNextPayments does nothing when subscriptions are empty',
() async {
subscriptionsController.add(const []);
tagsController.add(const []);
currenciesController.add(const []);
ratesController.add(const []);
await Future<void>.delayed(Duration.zero);

clearInteractions(refreshOverdueNextPaymentsUseCase);
await viewModel.refreshOverdueNextPayments();

verifyNever(() => refreshOverdueNextPaymentsUseCase(any()));
},
);

test(
'refreshOverdueNextPayments does nothing when subscriptions are loading',
() async {
clearInteractions(refreshOverdueNextPaymentsUseCase);
await viewModel.refreshOverdueNextPayments();

verifyNever(() => refreshOverdueNextPaymentsUseCase(any()));
},
);

test('updateBaseCurrencyCode re-listens to currency rates stream', () async {
viewModel.updateBaseCurrencyCode('eur');
await Future<void>.delayed(Duration.zero);
Expand Down
Loading