Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: top up screen (kids-843) #763

Merged
merged 7 commits into from
May 23, 2024
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
3 changes: 3 additions & 0 deletions assets/images/calendar_clock.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions lib/core/enums/amplitude_events.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ enum AmplitudeEvents {
memberCreatedSuccesfully('member_created_succesfully'),
failedToCreateMember('failed_to_create_member'),
failedToGetVpc('failed_to_get_vpc'),
failedTopUpNoFunds('failed_top_up_no_funds'),
allowanceNotSuccessful('allowance_not_successful'),
backClicked('back_clicked'),
createChildProfileClicked('create_child_profile_clicked'),
Expand All @@ -25,6 +26,7 @@ enum AmplitudeEvents {
'child_details_edit_card_clicked',
),
childTopUpCardClicked('child_top_up_card_clicked'),
childTopUpSubmitted('child_top_up_submitted'),
childEditSaveClicked('child_edit_save_clicked'),
childEditMonthlyAllowanceSaveClicked(
'child_edit_monthly_allowance_save_clicked'),
Expand Down
1 change: 1 addition & 0 deletions lib/core/failures/failure.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class GivtServerFailure extends Equatable implements Exception {

enum FailureType {
ALLOWANCE_NOT_SUCCESSFUL,
TOPUP_NOT_SUCCESSFUL,
VPC_NOT_SUCCESSFUL,
UNKNOWN,
;
Expand Down
32 changes: 32 additions & 0 deletions lib/core/network/api_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,38 @@ class APIService {
}
}

Future<bool> topUpChild(String childGUID, int amount) async {
final url = Uri.https(_apiURL, '/givtservice/v1/profiles/$childGUID/topup');

final response = await client.post(
url,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: jsonEncode({'amount': amount}),
);

if (response.statusCode >= 300) {
throw GivtServerFailure(
statusCode: response.statusCode,
body: response.body.isNotEmpty
? jsonDecode(response.body) as Map<String, dynamic>
: null,
);
}
final decodedBody = jsonDecode(response.body) as Map<String, dynamic>;
final isError = decodedBody['isError'] as bool;

if (response.statusCode == 200 && isError) {
throw GivtServerFailure(
statusCode: response.statusCode,
body: decodedBody,
);
}
return response.statusCode == 200;
}

Future<bool> acceptGroupInvite(
String groupId,
) async {
Expand Down
55 changes: 55 additions & 0 deletions lib/features/children/details/cubit/child_details_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:givt_app/core/enums/amplitude_events.dart';
import 'package:givt_app/core/failures/failure.dart';
import 'package:givt_app/core/logging/logging.dart';
import 'package:givt_app/features/children/details/models/profile_ext.dart';
import 'package:givt_app/features/children/details/repositories/child_details_repository.dart';
Expand Down Expand Up @@ -77,13 +78,67 @@ class ChildDetailsCubit extends Cubit<ChildDetailsState> {
}
}

Future<void> topUp(int amount) async {
unawaited(_logTopUpEvent(amount));

try {
_emitLoading();
final isSuccess = await _editChildRepository.topUpChild(
_profile.id,
amount,
);
if (isSuccess) {
_emitData();
emit(ChildTopUpSuccessState(amount: amount));
//retrieve updated profile after topping up
unawaited(fetchChildDetails());
} else {
emit(
const ChildDetailsErrorState(
errorMessage: 'Failed to top up',
),
);
}
} catch (e, s) {
_emitData();
await _handleTopUpApiError(e, s);
}
}

Future<void> _handleEditAllowanceApiError(Object e, StackTrace s) async {
await LoggingInfo.instance.error(e.toString(), methodName: s.toString());
emit(
ChildDetailsErrorState(errorMessage: e.toString()),
);
}

Future<void> _handleTopUpApiError(Object e, StackTrace s) async {
await LoggingInfo.instance.error(e.toString(), methodName: s.toString());
if (e is GivtServerFailure && e.type == FailureType.TOPUP_NOT_SUCCESSFUL) {
unawaited(
AnalyticsHelper.logEvent(
eventName: AmplitudeEvents.failedTopUpNoFunds,
),
);
emit(
const ChildTopUpFundsErrorState(),
);
}
emit(
ChildDetailsErrorState(errorMessage: e.toString()),
);
}

Future<void> _logTopUpEvent(int amount) async {
await AnalyticsHelper.logEvent(
eventName: AmplitudeEvents.childTopUpSubmitted,
eventProperties: {
'child_name': _profile.firstName,
'top_up_amount': amount,
},
);
}

Future<void> _logConfirmUpdateAllowanceEvent(int allowance) async {
await AnalyticsHelper.logEvent(
eventName: AmplitudeEvents.childEditMonthlyAllowanceSaveClicked,
Expand Down
15 changes: 15 additions & 0 deletions lib/features/children/details/cubit/child_details_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ final class ChildDetailsFetchingState extends ChildDetailsState {
const ChildDetailsFetchingState();
}

final class ChildTopUpFundsErrorState extends ChildDetailsState {
const ChildTopUpFundsErrorState();
}

final class ChildDetailsErrorState extends ChildDetailsState {
const ChildDetailsErrorState({
required this.errorMessage,
Expand Down Expand Up @@ -47,3 +51,14 @@ final class ChildEditGivingAllowanceSuccessState extends ChildDetailsState {
@override
List<Object> get props => [allowance];
}

final class ChildTopUpSuccessState extends ChildDetailsState {
const ChildTopUpSuccessState({
required this.amount,
});

final int amount;

@override
List<Object> get props => [amount];
}
19 changes: 17 additions & 2 deletions lib/features/children/details/pages/child_details_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:givt_app/features/children/details/widgets/child_details_item.da
import 'package:givt_app/features/children/details/widgets/child_giving_allowance_card.dart';
import 'package:givt_app/features/children/details/widgets/child_top_up_card.dart';
import 'package:givt_app/features/children/overview/cubit/family_overview_cubit.dart';
import 'package:givt_app/features/children/overview/pages/add_top_up_page.dart';
import 'package:givt_app/features/children/overview/pages/edit_allowance_page.dart';
import 'package:givt_app/features/children/overview/pages/edit_allowance_success_page.dart';
import 'package:givt_app/features/children/overview/pages/models/edit_allowance_success_uimodel.dart';
Expand Down Expand Up @@ -35,7 +36,9 @@ class ChildDetailsPage extends StatelessWidget {
return BlocConsumer<ChildDetailsCubit, ChildDetailsState>(
listenWhen: (previous, current) {
return current is ChildDetailsErrorState ||
current is ChildEditGivingAllowanceSuccessState;
current is ChildEditGivingAllowanceSuccessState ||
current is ChildTopUpFundsErrorState ||
current is ChildTopUpSuccessState;
},
buildWhen: (previous, current) {
return current is ChildDetailsFetchingState ||
Expand All @@ -57,6 +60,10 @@ class ChildDetailsPage extends StatelessWidget {
),
).toRoute(context),
);
} else if (state is ChildTupUpFundsErrorState) {
// TODO Kids-845
} else if (state is ChildTopUpSuccessState) {
// TODO Kids-844
}
},
builder: (context, state) {
Expand Down Expand Up @@ -171,7 +178,15 @@ class ChildDetailsPage extends StatelessWidget {
Future<void> _navigateToTopUpScreen(
BuildContext context,
) async {
// TODO: Implement kids-843
final dynamic result = await Navigator.push(
context,
const AddTopUpPage(
currency: r'$',
).toRoute(context),
);
if (result != null && result is int && context.mounted) {
await context.read<ChildDetailsCubit>().topUp(result);
}
}

Future<void> _navigateToEditAllowanceScreen(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:givt_app/l10n/l10n.dart';
import 'package:givt_app/shared/widgets/common_icons.dart';
import 'package:givt_app/utils/app_theme.dart';

class ChildTopUpCard extends StatelessWidget {
Expand Down Expand Up @@ -35,11 +35,7 @@ class ChildTopUpCard extends StatelessWidget {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const FaIcon(
FontAwesomeIcons.plus,
size: 40,
color: AppTheme.givtLightGreen,
),
plusIcon(size: 40),
const SizedBox(height: 10),
Text(
context.l10n.topUp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ mixin EditChildRepository {
Future<bool> editChild(String childGUID, EditChild child);

Future<bool> editChildAllowance(String childGUID, int allowance);

Future<bool> topUpChild(String childGUID, int amount);
}

class EditChildRepositoryImpl with EditChildRepository {
Expand All @@ -22,4 +24,9 @@ class EditChildRepositoryImpl with EditChildRepository {
Future<bool> editChildAllowance(String childGUID, int allowance) async {
return apiService.editChildAllowance(childGUID, allowance);
}

@override
Future<bool> topUpChild(String childGUID, int amount) async {
return apiService.topUpChild(childGUID, amount);
}
}
112 changes: 112 additions & 0 deletions lib/features/children/overview/pages/add_top_up_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import 'package:flutter/material.dart';
import 'package:givt_app/features/children/add_member/widgets/allowance_counter.dart';
import 'package:givt_app/l10n/l10n.dart';
import 'package:givt_app/shared/widgets/common_icons.dart';
import 'package:givt_app/shared/widgets/custom_green_elevated_button.dart';
import 'package:givt_app/utils/app_theme.dart';

class AddTopUpPage extends StatefulWidget {
const AddTopUpPage({
required this.currency,
this.initialAmount,
super.key,
});

final String currency;
final int? initialAmount;

@override
State<AddTopUpPage> createState() => _AddTopUpPageState();
}

class _AddTopUpPageState extends State<AddTopUpPage> {
late int _amount;

@override
void initState() {
super.initState();
_amount = widget.initialAmount ?? 5;
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(20),
child: Stack(
children: [
Align(
child: Padding(
padding: const EdgeInsets.only(bottom: 80),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
plusIcon(
size: 40,
),
const SizedBox(height: 16),
Text(
context.l10n.topUp,
style:
Theme.of(context).textTheme.titleLarge!.copyWith(
color: AppTheme.inputFieldBorderSelected,
fontFamily: 'Raleway',
fontWeight: FontWeight.w800,
height: 1.2,
),
),
const SizedBox(height: 8),
Text(
context.l10n.topUpScreenInfo,
textAlign: TextAlign.center,
style:
Theme.of(context).textTheme.titleMedium!.copyWith(
color: AppTheme.childGivingAllowanceHint,
fontFamily: 'Raleway',
fontWeight: FontWeight.w500,
fontSize: 16,
height: 1.2,
),
),
const SizedBox(height: 12),
AllowanceCounter(
currency: widget.currency,
initialAllowance: _amount,
onAllowanceChanged: (amount) => setState(() {
_amount = amount;
}),
),
const SizedBox(height: 12),
Text(
'Choose an amount between ${widget.currency}1 and '
'${widget.currency}999.',
style:
Theme.of(context).textTheme.titleSmall!.copyWith(
color: AppTheme.childGivingAllowanceHint,
),
),
],
),
),
),
Align(
alignment: Alignment.bottomCenter,
child: CustomGreenElevatedButton(
title: context.l10n.confirm,
onPressed: () {
Navigator.of(context).pop(_amount);
},
),
),
],
),
),
),
),
);
}
}
Loading